Merge "Add mouse interaction UI test for TextView."
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 c3a4a0c..e912440 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 = 16844031; // 0x10104ff
     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 = 16844032; // 0x1010500
     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 = 16844034; // 0x1010502
     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 = 16844033; // 0x1010501
     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 = 16844030; // 0x10104fe
     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
@@ -1173,6 +1174,7 @@
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
+    field public static final int supportsPictureInPicture = 16844024; // 0x10104f8
     field public static final int supportsRtl = 16843695; // 0x10103af
     field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
     field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1221,7 +1223,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 = 16844035; // 0x1010503
     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 +1292,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 = 16844025; // 0x10104f9
+    field public static final int titleMarginBottom = 16844029; // 0x10104fd
+    field public static final int titleMarginEnd = 16844027; // 0x10104fb
+    field public static final int titleMarginStart = 16844026; // 0x10104fa
+    field public static final int titleMarginTop = 16844028; // 0x10104fc
     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 +1396,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 = 16844036; // 0x1010504
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -2876,6 +2879,7 @@
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
     method public abstract long getStartDelay();
+    method public long getTotalDuration();
     method public boolean isPaused();
     method public abstract boolean isRunning();
     method public boolean isStarted();
@@ -2891,6 +2895,7 @@
     method public void setupEndValues();
     method public void setupStartValues();
     method public void start();
+    field public static final long DURATION_INFINITE = -1L; // 0xffffffffffffffffL
   }
 
   public static abstract interface Animator.AnimatorListener {
@@ -4021,6 +4026,8 @@
     ctor public AutomaticZenRule(android.os.Parcel);
     method public int describeContents();
     method public android.net.Uri getConditionId();
+    method public long getCreationTime();
+    method public java.lang.String getId();
     method public int getInterruptionFilter();
     method public java.lang.String getName();
     method public android.content.ComponentName getOwner();
@@ -4754,6 +4761,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";
@@ -4918,6 +4926,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();
@@ -5025,6 +5034,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);
@@ -5078,7 +5097,7 @@
   }
 
   public class NotificationManager {
-    method public boolean addOrUpdateAutomaticZenRule(android.app.AutomaticZenRule);
+    method public android.app.AutomaticZenRule addAutomaticZenRule(android.app.AutomaticZenRule);
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -5091,9 +5110,9 @@
     method public void notify(int, android.app.Notification);
     method public void notify(java.lang.String, int, android.app.Notification);
     method public boolean removeAutomaticZenRule(java.lang.String);
-    method public boolean renameAutomaticZenRule(java.lang.String, java.lang.String);
     method public final void setInterruptionFilter(int);
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
+    method public boolean updateAutomaticZenRule(android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
@@ -5629,7 +5648,7 @@
     method public void onPasswordFailed(android.content.Context, android.content.Intent);
     method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
-    method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
+    method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
@@ -8920,8 +8939,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;
@@ -8934,11 +8953,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;
   }
@@ -18171,6 +18191,24 @@
   public class MtpEvent {
     ctor public MtpEvent();
     method public int getEventCode();
+    field public static final int EVENT_CANCEL_TRANSACTION = 16385; // 0x4001
+    field public static final int EVENT_CAPTURE_COMPLETE = 16397; // 0x400d
+    field public static final int EVENT_DEVICE_INFO_CHANGED = 16392; // 0x4008
+    field public static final int EVENT_DEVICE_PROP_CHANGED = 16390; // 0x4006
+    field public static final int EVENT_DEVICE_RESET = 16395; // 0x400b
+    field public static final int EVENT_OBJECT_ADDED = 16386; // 0x4002
+    field public static final int EVENT_OBJECT_INFO_CHANGED = 16391; // 0x4007
+    field public static final int EVENT_OBJECT_PROP_CHANGED = 51201; // 0xc801
+    field public static final int EVENT_OBJECT_PROP_DESC_CHANGED = 51202; // 0xc802
+    field public static final int EVENT_OBJECT_REFERENCES_CHANGED = 51203; // 0xc803
+    field public static final int EVENT_OBJECT_REMOVED = 16387; // 0x4003
+    field public static final int EVENT_REQUEST_OBJECT_TRANSFER = 16393; // 0x4009
+    field public static final int EVENT_STORAGE_INFO_CHANGED = 16396; // 0x400c
+    field public static final int EVENT_STORE_ADDED = 16388; // 0x4004
+    field public static final int EVENT_STORE_FULL = 16394; // 0x400a
+    field public static final int EVENT_STORE_REMOVED = 16389; // 0x4005
+    field public static final int EVENT_UNDEFINED = 16384; // 0x4000
+    field public static final int EVENT_UNREPORTED_STATUS = 16398; // 0x400e
   }
 
   public final class MtpObjectInfo {
@@ -22848,6 +22886,7 @@
     field public static final int LOLLIPOP = 21; // 0x15
     field public static final int LOLLIPOP_MR1 = 22; // 0x16
     field public static final int M = 23; // 0x17
+    field public static final int N = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -24997,6 +25036,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
@@ -28848,10 +28888,8 @@
     method public abstract void onRequestConditions(int);
     method public abstract void onSubscribe(android.net.Uri);
     method public abstract void onUnsubscribe(android.net.Uri);
-    field public static final java.lang.String EXTRA_CONDITION_ID = "android.content.automatic.conditionId";
-    field public static final java.lang.String EXTRA_RULE_NAME = "android.content.automatic.ruleName";
+    field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
     field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
-    field public static final java.lang.String META_DATA_DEFAULT_CONDITION_ID = "android.service.zen.automatic.defaultConditionId";
     field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
@@ -30477,6 +30515,7 @@
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public android.net.Uri getAddress();
     method public int getCapabilities();
+    method public android.os.Bundle getExtras();
     method public int getHighlightColor();
     method public android.graphics.drawable.Icon getIcon();
     method public java.lang.CharSequence getLabel();
@@ -30495,6 +30534,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";
@@ -30509,6 +30550,7 @@
     method public android.telecom.PhoneAccount build();
     method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri);
     method public android.telecom.PhoneAccount.Builder setCapabilities(int);
+    method public android.telecom.PhoneAccount.Builder setExtras(android.os.Bundle);
     method public android.telecom.PhoneAccount.Builder setHighlightColor(int);
     method public android.telecom.PhoneAccount.Builder setIcon(android.graphics.drawable.Icon);
     method public android.telecom.PhoneAccount.Builder setShortDescription(java.lang.CharSequence);
@@ -35292,8 +35334,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
@@ -35411,6 +35455,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
@@ -39191,6 +39236,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();
@@ -39234,6 +39280,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);
@@ -39273,6 +39320,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/removed.txt b/api/removed.txt
index 642d2a8..f12e61e 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -6,6 +6,15 @@
 
 }
 
+package android.app.admin {
+
+  public class DevicePolicyManager {
+    method public deprecated java.lang.String getDeviceInitializerApp();
+    method public deprecated android.content.ComponentName getDeviceInitializerComponent();
+  }
+
+}
+
 package android.content.pm {
 
   public class PackageInfo implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 325aa28..b996af3 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 = 16844031; // 0x10104ff
     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 = 16844032; // 0x1010500
     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 = 16844034; // 0x1010502
     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 = 16844033; // 0x1010501
     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 = 16844030; // 0x10104fe
     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
@@ -1269,6 +1270,7 @@
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
+    field public static final int supportsPictureInPicture = 16844024; // 0x10104f8
     field public static final int supportsRtl = 16843695; // 0x10103af
     field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
     field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1317,7 +1319,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 = 16844035; // 0x1010503
     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 +1388,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 = 16844025; // 0x10104f9
+    field public static final int titleMarginBottom = 16844029; // 0x10104fd
+    field public static final int titleMarginEnd = 16844027; // 0x10104fb
+    field public static final int titleMarginStart = 16844026; // 0x10104fa
+    field public static final int titleMarginTop = 16844028; // 0x10104fc
     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 +1492,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 = 16844036; // 0x1010504
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -2975,6 +2978,7 @@
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
     method public abstract long getStartDelay();
+    method public long getTotalDuration();
     method public boolean isPaused();
     method public abstract boolean isRunning();
     method public boolean isStarted();
@@ -2990,6 +2994,7 @@
     method public void setupEndValues();
     method public void setupStartValues();
     method public void start();
+    field public static final long DURATION_INFINITE = -1L; // 0xffffffffffffffffL
   }
 
   public static abstract interface Animator.AnimatorListener {
@@ -4132,6 +4137,8 @@
     ctor public AutomaticZenRule(android.os.Parcel);
     method public int describeContents();
     method public android.net.Uri getConditionId();
+    method public long getCreationTime();
+    method public java.lang.String getId();
     method public int getInterruptionFilter();
     method public java.lang.String getName();
     method public android.content.ComponentName getOwner();
@@ -4871,6 +4878,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";
@@ -5035,6 +5043,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();
@@ -5142,6 +5151,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);
@@ -5195,7 +5214,7 @@
   }
 
   public class NotificationManager {
-    method public boolean addOrUpdateAutomaticZenRule(android.app.AutomaticZenRule);
+    method public android.app.AutomaticZenRule addAutomaticZenRule(android.app.AutomaticZenRule);
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -5208,9 +5227,9 @@
     method public void notify(int, android.app.Notification);
     method public void notify(java.lang.String, int, android.app.Notification);
     method public boolean removeAutomaticZenRule(java.lang.String);
-    method public boolean renameAutomaticZenRule(java.lang.String, java.lang.String);
     method public final void setInterruptionFilter(int);
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
+    method public boolean updateAutomaticZenRule(android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
@@ -5750,7 +5769,7 @@
     method public void onPasswordFailed(android.content.Context, android.content.Intent);
     method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
-    method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
+    method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
@@ -5792,8 +5811,8 @@
     method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
-    method public java.lang.String getDeviceInitializerApp();
-    method public android.content.ComponentName getDeviceInitializerComponent();
+    method public deprecated java.lang.String getDeviceInitializerApp();
+    method public deprecated android.content.ComponentName getDeviceInitializerComponent();
     method public java.lang.String getDeviceOwner();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -9177,8 +9196,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;
@@ -9191,11 +9210,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;
   }
@@ -9517,6 +9537,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);
@@ -19683,6 +19704,24 @@
   public class MtpEvent {
     ctor public MtpEvent();
     method public int getEventCode();
+    field public static final int EVENT_CANCEL_TRANSACTION = 16385; // 0x4001
+    field public static final int EVENT_CAPTURE_COMPLETE = 16397; // 0x400d
+    field public static final int EVENT_DEVICE_INFO_CHANGED = 16392; // 0x4008
+    field public static final int EVENT_DEVICE_PROP_CHANGED = 16390; // 0x4006
+    field public static final int EVENT_DEVICE_RESET = 16395; // 0x400b
+    field public static final int EVENT_OBJECT_ADDED = 16386; // 0x4002
+    field public static final int EVENT_OBJECT_INFO_CHANGED = 16391; // 0x4007
+    field public static final int EVENT_OBJECT_PROP_CHANGED = 51201; // 0xc801
+    field public static final int EVENT_OBJECT_PROP_DESC_CHANGED = 51202; // 0xc802
+    field public static final int EVENT_OBJECT_REFERENCES_CHANGED = 51203; // 0xc803
+    field public static final int EVENT_OBJECT_REMOVED = 16387; // 0x4003
+    field public static final int EVENT_REQUEST_OBJECT_TRANSFER = 16393; // 0x4009
+    field public static final int EVENT_STORAGE_INFO_CHANGED = 16396; // 0x400c
+    field public static final int EVENT_STORE_ADDED = 16388; // 0x4004
+    field public static final int EVENT_STORE_FULL = 16394; // 0x400a
+    field public static final int EVENT_STORE_REMOVED = 16389; // 0x4005
+    field public static final int EVENT_UNDEFINED = 16384; // 0x4000
+    field public static final int EVENT_UNREPORTED_STATUS = 16398; // 0x400e
   }
 
   public final class MtpObjectInfo {
@@ -24792,6 +24831,7 @@
     field public static final int LOLLIPOP = 21; // 0x15
     field public static final int LOLLIPOP_MR1 = 22; // 0x16
     field public static final int M = 23; // 0x17
+    field public static final int N = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -25718,7 +25758,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);
@@ -26953,6 +26994,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
@@ -30937,10 +30979,8 @@
     method public abstract void onRequestConditions(int);
     method public abstract void onSubscribe(android.net.Uri);
     method public abstract void onUnsubscribe(android.net.Uri);
-    field public static final java.lang.String EXTRA_CONDITION_ID = "android.content.automatic.conditionId";
-    field public static final java.lang.String EXTRA_RULE_NAME = "android.content.automatic.ruleName";
+    field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
     field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
-    field public static final java.lang.String META_DATA_DEFAULT_CONDITION_ID = "android.service.zen.automatic.defaultConditionId";
     field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
@@ -32676,6 +32716,7 @@
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public android.net.Uri getAddress();
     method public int getCapabilities();
+    method public android.os.Bundle getExtras();
     method public int getHighlightColor();
     method public android.graphics.drawable.Icon getIcon();
     method public java.lang.CharSequence getLabel();
@@ -32695,6 +32736,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";
@@ -32709,6 +32752,7 @@
     method public android.telecom.PhoneAccount build();
     method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri);
     method public android.telecom.PhoneAccount.Builder setCapabilities(int);
+    method public android.telecom.PhoneAccount.Builder setExtras(android.os.Bundle);
     method public android.telecom.PhoneAccount.Builder setHighlightColor(int);
     method public android.telecom.PhoneAccount.Builder setIcon(android.graphics.drawable.Icon);
     method public android.telecom.PhoneAccount.Builder setShortDescription(java.lang.CharSequence);
@@ -37587,8 +37631,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
@@ -37706,6 +37752,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
@@ -41551,6 +41598,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();
@@ -41600,6 +41648,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);
@@ -41644,6 +41693,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
@@ -41879,6 +41932,7 @@
     method public void super_scrollTo(int, int);
     method public boolean super_setFrame(int, int, int, int);
     method public void super_setLayoutParams(android.view.ViewGroup.LayoutParams);
+    method public void super_startActivityForResult(android.content.Intent, int);
   }
 
   public static abstract class WebView.VisualStateCallback {
@@ -42113,6 +42167,7 @@
   public static abstract interface WebViewProvider.ViewDelegate {
     method public abstract boolean dispatchKeyEvent(android.view.KeyEvent);
     method public abstract android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider();
+    method public abstract void onActivityResult(int, int, android.content.Intent);
     method public abstract void onAttachedToWindow();
     method public abstract void onConfigurationChanged(android.content.res.Configuration);
     method public abstract android.view.inputmethod.InputConnection onCreateInputConnection(android.view.inputmethod.EditorInfo);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 4a13136..12780a8 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" +
@@ -154,7 +154,7 @@
                 "       am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
                 "       am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
                 "       am stack size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]\n" +
-                "       am stack split <STACK_ID> <v|h> [INTENT]\n" +
+                "       am stack move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
                 "       am stack positiontask <TASK_ID> <STACK_ID> <POSITION>\n" +
                 "       am stack list\n" +
                 "       am stack info <STACK_ID>\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" +
@@ -298,11 +297,9 @@
                 "   <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom\n" +
                 "   applying the optional [DELAY_MS] between each step.\n" +
                 "\n" +
-                "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" +
-                "   starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" +
-                "   specified and the current stack has more than one task, then the top task\n" +
-                "   of the current task will be moved to the new stack. Command will also force\n" +
-                "   all current tasks in both stacks to be resizeable.\n" +
+                "am stack move-top-activity-to-pinned-stack: moves the top activity from\n" +
+                "   <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the\n" +
+                "   bounds of the pinned stack.\n" +
                 "\n" +
                 "am stack positiontask: place <TASK_ID> in <STACK_ID> at <POSITION>" +
                 "\n" +
@@ -739,8 +736,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")) {
@@ -1957,25 +1952,34 @@
 
     private void runStack() throws Exception {
         String op = nextArgRequired();
-        if (op.equals("start")) {
-            runStackStart();
-        } else if (op.equals("movetask")) {
-            runStackMoveTask();
-        } else if (op.equals("resize")) {
-            runStackResize();
-        } else if (op.equals("positiontask")) {
-            runStackPositionTask();
-        } else if (op.equals("list")) {
-            runStackList();
-        } else if (op.equals("info")) {
-            runStackInfo();
-        } else if (op.equals("split")) {
-            runStackSplit();
-        } else if (op.equals("size-docked-stack-test")) {
-            runStackSizeDockedStackTest();
-        } else {
-            showError("Error: unknown command '" + op + "'");
-            return;
+        switch (op) {
+            case "start":
+                runStackStart();
+                break;
+            case "movetask":
+                runStackMoveTask();
+                break;
+            case "resize":
+                runStackResize();
+                break;
+            case "positiontask":
+                runStackPositionTask();
+                break;
+            case "list":
+                runStackList();
+                break;
+            case "info":
+                runStackInfo();
+                break;
+            case "move-top-activity-to-pinned-stack":
+                runMoveTopActivityToPinnedStack();
+                break;
+            case "size-docked-stack-test":
+                runStackSizeDockedStackTest();
+                break;
+            default:
+                showError("Error: unknown command '" + op + "'");
+                break;
         }
     }
 
@@ -2033,7 +2037,7 @@
         }
 
         try {
-            mAm.resizeStack(stackId, bounds);
+            mAm.resizeStack(stackId, bounds, false);
             Thread.sleep(delayMs);
         } catch (RemoteException e) {
             showError("Error: resizing stack " + e);
@@ -2075,61 +2079,21 @@
         }
     }
 
-    private void runStackSplit() throws Exception {
-        final int stackId = Integer.valueOf(nextArgRequired());
-        final String splitDirection = nextArgRequired();
-        Intent intent = null;
-        try {
-            intent = makeIntent(UserHandle.USER_CURRENT);
-        } catch (IllegalArgumentException e) {
-            // no intent supplied.
+    private void runMoveTopActivityToPinnedStack() throws Exception {
+        int stackId = Integer.valueOf(nextArgRequired());
+        final Rect bounds = getBounds();
+        if (bounds == null) {
+            System.err.println("Error: invalid input bounds");
+            return;
         }
 
         try {
-            final StackInfo currentStackInfo = mAm.getStackInfo(stackId);
-            // Calculate bounds for new and current stack.
-            final Rect currentStackBounds = new Rect(currentStackInfo.bounds);
-            final Rect newStackBounds = new Rect(currentStackInfo.bounds);
-            if ("v".equals(splitDirection)) {
-                currentStackBounds.right = newStackBounds.left = currentStackInfo.bounds.centerX();
-            } else if ("h".equals(splitDirection)) {
-                currentStackBounds.bottom = newStackBounds.top = currentStackInfo.bounds.centerY();
-            } else {
-                showError("Error: unknown split direction '" + splitDirection + "'");
-                return;
+            if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) {
+                showError("Didn't move top activity to pinned stack.");
             }
-
-            // Create new stack
-            IActivityContainer container = mAm.createStackOnDisplay(currentStackInfo.displayId);
-            if (container == null) {
-                showError("Error: Unable to create new stack...");
-            }
-
-            final int newStackId = container.getStackId();
-
-            if (intent != null) {
-                container.startActivity(intent);
-            } else if (currentStackInfo.taskIds != null && currentStackInfo.taskIds.length > 1) {
-                // Move top task over to new stack
-                mAm.moveTaskToStack(currentStackInfo.taskIds[currentStackInfo.taskIds.length - 1],
-                        newStackId, true);
-            }
-
-            final StackInfo newStackInfo = mAm.getStackInfo(newStackId);
-
-            // Make all tasks in the stacks resizeable.
-            for (int taskId : currentStackInfo.taskIds) {
-                mAm.setTaskResizeable(taskId, true);
-            }
-
-            for (int taskId : newStackInfo.taskIds) {
-                mAm.setTaskResizeable(taskId, true);
-            }
-
-            // Resize stacks
-            mAm.resizeStack(currentStackInfo.stackId, currentStackBounds);
-            mAm.resizeStack(newStackInfo.stackId, newStackBounds);
         } catch (RemoteException e) {
+            showError("Unable to move top activity: " + e);
+            return;
         }
     }
 
diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
index d1f7a00..c9b9e58 100644
--- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
+++ b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
@@ -168,7 +168,12 @@
         final IPackageManager pm = ActivityThread.getPackageManager();
         final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid = pm.getPackageUid(packageName, userId);
+        final int uid;
+        if ("root".equals(packageName)) {
+            uid = 0;
+        } else {
+            uid = pm.getPackageUid(packageName, userId);
+        }
         if (uid < 0) {
             System.err.println("Error: No UID for " + packageName + " in user " + userId);
             return;
@@ -211,7 +216,12 @@
         final IPackageManager pm = ActivityThread.getPackageManager();
         final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid = pm.getPackageUid(packageName, userId);
+        final int uid;
+        if ("root".equals(packageName)) {
+            uid = 0;
+        } else {
+            uid = pm.getPackageUid(packageName, userId);
+        }
         if (uid < 0) {
             System.err.println("Error: No UID for " + packageName + " in user " + userId);
             return;
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index 214dc5d..ea53009 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -44,6 +44,7 @@
 
     private IDevicePolicyManager mDevicePolicyManager;
     private int mUserId = UserHandle.USER_SYSTEM;
+    private String mName = "";
     private ComponentName mComponent = null;
 
     @Override
@@ -52,8 +53,8 @@
                 "usage: dpm [subcommand] [options]\n" +
                 "usage: dpm set-active-admin [ --user <USER_ID> ] <COMPONENT>\n" +
                 // STOPSHIP Finalize it
-                "usage: dpm set-device-owner [ --user <USER_ID> *EXPERIMENTAL* ] <COMPONENT>\n" +
-                "usage: dpm set-profile-owner [ --user <USER_ID> ] <COMPONENT>\n" +
+                "usage: dpm set-device-owner [ --user <USER_ID> *EXPERIMENTAL* ] [ --name <NAME> ] <COMPONENT>\n" +
+                "usage: dpm set-profile-owner [ --user <USER_ID> ] [ --name <NAME> ] <COMPONENT>\n" +
                 "\n" +
                 "dpm set-active-admin: Sets the given component as active admin" +
                 " for an existing user.\n" +
@@ -90,47 +91,51 @@
         }
     }
 
-    private void parseArgs(boolean canHaveUser) {
-        String nextArg = nextArgRequired();
-        if (canHaveUser && "--user".equals(nextArg)) {
-            mUserId = parseInt(nextArgRequired());
-            nextArg = nextArgRequired();
+    private void parseArgs(boolean canHaveUser, boolean canHaveName) {
+        String opt;
+        while ((opt = nextOption()) != null) {
+            if (canHaveUser && "--user".equals(opt)) {
+                mUserId = parseInt(nextArgRequired());
+            } else if (canHaveName && "--name".equals(opt)) {
+                mName = nextArgRequired();
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
         }
-        mComponent = parseComponentName(nextArg);
+        mComponent = parseComponentName(nextArgRequired());
     }
 
     private void runSetActiveAdmin() throws RemoteException {
-        parseArgs(true);
+        parseArgs(/*canHaveUser=*/ true, /*canHaveName=*/ false);
         mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId);
 
         System.out.println("Success: Active admin set to component " + mComponent.toShortString());
     }
 
     private void runSetDeviceOwner() throws RemoteException {
-        parseArgs(true);
+        parseArgs(/*canHaveUser=*/ true, /*canHaveName=*/ true);
         mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId);
 
-        String packageName = mComponent.getPackageName();
         try {
-            if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/, mUserId)) {
+            if (!mDevicePolicyManager.setDeviceOwner(mComponent, mName, mUserId)) {
                 throw new RuntimeException(
-                        "Can't set package " + packageName + " as device owner.");
+                        "Can't set package " + mComponent + " as device owner.");
             }
         } catch (Exception e) {
             // Need to remove the admin that we just added.
             mDevicePolicyManager.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM);
             throw e;
         }
-        System.out.println("Success: Device owner set to package " + packageName);
+        System.out.println("Success: Device owner set to package " + mComponent);
         System.out.println("Active admin set to component " + mComponent.toShortString());
     }
 
     private void runSetProfileOwner() throws RemoteException {
-        parseArgs(true);
+        parseArgs(/*canHaveUser=*/ true, /*canHaveName=*/ true);
         mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId);
 
         try {
-            if (!mDevicePolicyManager.setProfileOwner(mComponent, "" /*ownerName*/, mUserId)) {
+            if (!mDevicePolicyManager.setProfileOwner(mComponent, mName, mUserId)) {
                 throw new RuntimeException("Can't set component " + mComponent.toShortString() +
                         " as profile owner for user " + mUserId);
             }
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 41395f1..6b2f460 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -41,7 +41,7 @@
             ALOGD("error: fchmod %s: %s\n", path, strerror(errno));
             goto fail;
         }
-        if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX | LOCK_NB)) != 0) {
+        if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX)) != 0) {
             ALOGD("error: flock %s: %s\n", path, strerror(errno));
             goto fail;
         }
@@ -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/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
index 90cfa2c..3ab1915 100644
--- a/cmds/idmap/idmap.cpp
+++ b/cmds/idmap/idmap.cpp
@@ -13,7 +13,8 @@
       idmap --help \n\
       idmap --fd target overlay fd \n\
       idmap --path target overlay idmap \n\
-      idmap --scan dir-to-scan target-to-look-for target dir-to-hold-idmaps \n\
+      idmap --scan target-package-name-to-look-for path-to-target-apk dir-to-hold-idmaps \\\
+                   dir-to-scan [additional-dir-to-scan [additional-dir-to-scan [...]]]\n\
       idmap --inspect idmap \n\
 \n\
 DESCRIPTION \n\
@@ -49,9 +50,9 @@
               'overlay' (path to apk); write results to 'idmap' (path). \n\
 \n\
       --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\
-              target package 'target-to-look-for' (package name) present at 'target' (path to \n\
-              apk). For each overlay package found, create an idmap file in 'dir-to-hold-idmaps' \n\
-              (path). \n\
+              target package 'target-package-name-to-look-for' (package name) present at\n\
+              'path-to-target-apk' (path to apk). For each overlay package found, create an\n\
+              idmap file in 'dir-to-hold-idmaps' (path). \n\
 \n\
       --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\
                  debug-friendly format. \n\
@@ -166,19 +167,14 @@
         return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
     }
 
-    int maybe_scan(const char *overlay_dir, const char *target_package_name,
-            const char *target_apk_path, const char *idmap_dir)
+    int maybe_scan(const char *target_package_name, const char *target_apk_path,
+            const char *idmap_dir, const android::Vector<const char *> *overlay_dirs)
     {
         if (!verify_root_or_system()) {
             fprintf(stderr, "error: permission denied: not user root or user system\n");
             return -1;
         }
 
-        if (!verify_directory_readable(overlay_dir)) {
-            ALOGD("error: no read access to %s: %s\n", overlay_dir, strerror(errno));
-            return -1;
-        }
-
         if (!verify_file_readable(target_apk_path)) {
             ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
             return -1;
@@ -189,7 +185,16 @@
             return -1;
         }
 
-        return idmap_scan(overlay_dir, target_package_name, target_apk_path, idmap_dir);
+        const size_t N = overlay_dirs->size();
+        for (size_t i = 0; i < N; i++) {
+            const char *dir = overlay_dirs->itemAt(i);
+            if (!verify_directory_readable(dir)) {
+                ALOGD("error: no read access to %s: %s\n", dir, strerror(errno));
+                return -1;
+            }
+        }
+
+        return idmap_scan(target_package_name, target_apk_path, idmap_dir, overlay_dirs);
     }
 
     int maybe_inspect(const char *idmap_path)
@@ -230,8 +235,12 @@
         return maybe_create_path(argv[2], argv[3], argv[4]);
     }
 
-    if (argc == 6 && !strcmp(argv[1], "--scan")) {
-        return maybe_scan(argv[2], argv[3], argv[4], argv[5]);
+    if (argc >= 6 && !strcmp(argv[1], "--scan")) {
+        android::Vector<const char *> v;
+        for (int i = 5; i < argc; i++) {
+            v.push(argv[i]);
+        }
+        return maybe_scan(argv[2], argv[3], argv[4], &v);
     }
 
     if (argc == 3 && !strcmp(argv[1], "--inspect")) {
diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h
index f507dd8..8d4210b 100644
--- a/cmds/idmap/idmap.h
+++ b/cmds/idmap/idmap.h
@@ -1,9 +1,11 @@
+
 #ifndef _IDMAP_H_
 #define _IDMAP_H_
 
 #define LOG_TAG "idmap"
 
 #include <utils/Log.h>
+#include <utils/Vector.h>
 
 #include <errno.h>
 #include <stdio.h>
@@ -26,8 +28,8 @@
 // Regarding target_package_name: the idmap_scan implementation should
 // be able to extract this from the manifest in target_apk_path,
 // simplifying the external API.
-int idmap_scan(const char *overlay_dir, const char *target_package_name,
-        const char *target_apk_path, const char *idmap_dir);
+int idmap_scan(const char *target_package_name, const char *target_apk_path,
+        const char *idmap_dir, const android::Vector<const char *> *overlay_dirs);
 
 int idmap_inspect(const char *idmap_path);
 
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index 612a7eb..f1b2f9e 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -25,8 +25,7 @@
 
         bool operator<(Overlay const& rhs) const
         {
-            // Note: order is reversed by design
-            return rhs.priority < priority;
+            return rhs.priority > priority;
         }
 
         String8 apk_path;
@@ -167,8 +166,8 @@
     }
 }
 
-int idmap_scan(const char *overlay_dir, const char *target_package_name,
-        const char *target_apk_path, const char *idmap_dir)
+int idmap_scan(const char *target_package_name, const char *target_apk_path,
+        const char *idmap_dir, const android::Vector<const char *> *overlay_dirs)
 {
     String8 filename = String8(idmap_dir);
     filename.appendPath("overlays.list");
@@ -176,45 +175,49 @@
         return EXIT_FAILURE;
     }
 
-    DIR *dir = opendir(overlay_dir);
-    if (dir == NULL) {
-        return EXIT_FAILURE;
-    }
-
     SortedVector<Overlay> overlayVector;
-    struct dirent *dirent;
-    while ((dirent = readdir(dir)) != NULL) {
-        struct stat st;
-        char overlay_apk_path[PATH_MAX + 1];
-        snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name);
-        if (stat(overlay_apk_path, &st) < 0) {
-            continue;
-        }
-        if (!S_ISREG(st.st_mode)) {
-            continue;
+    const size_t N = overlay_dirs->size();
+    for (size_t i = 0; i < N; ++i) {
+        const char *overlay_dir = overlay_dirs->itemAt(i);
+        DIR *dir = opendir(overlay_dir);
+        if (dir == NULL) {
+            return EXIT_FAILURE;
         }
 
-        int priority = parse_apk(overlay_apk_path, target_package_name);
-        if (priority < 0) {
-            continue;
+        struct dirent *dirent;
+        while ((dirent = readdir(dir)) != NULL) {
+            struct stat st;
+            char overlay_apk_path[PATH_MAX + 1];
+            snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name);
+            if (stat(overlay_apk_path, &st) < 0) {
+                continue;
+            }
+            if (!S_ISREG(st.st_mode)) {
+                continue;
+            }
+
+            int priority = parse_apk(overlay_apk_path, target_package_name);
+            if (priority < 0) {
+                continue;
+            }
+
+            String8 idmap_path(idmap_dir);
+            idmap_path.appendPath(flatten_path(overlay_apk_path + 1));
+            idmap_path.append("@idmap");
+
+            if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) {
+                ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n",
+                        target_apk_path, overlay_apk_path, idmap_path.string());
+                continue;
+            }
+
+            Overlay overlay(String8(overlay_apk_path), idmap_path, priority);
+            overlayVector.add(overlay);
         }
 
-        String8 idmap_path(idmap_dir);
-        idmap_path.appendPath(flatten_path(overlay_apk_path + 1));
-        idmap_path.append("@idmap");
-
-        if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) {
-            ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n",
-                    target_apk_path, overlay_apk_path, idmap_path.string());
-            continue;
-        }
-
-        Overlay overlay(String8(overlay_apk_path), idmap_path, priority);
-        overlayVector.add(overlay);
+        closedir(dir);
     }
 
-    closedir(dir);
-
     if (!writePackagesList(filename.string(), overlayVector)) {
         return EXIT_FAILURE;
     }
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ebf5085..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;
@@ -624,7 +624,7 @@
                 }
             }
 
-            String grp = nextOption();
+            String grp = nextArg();
             ArrayList<String> groupList = new ArrayList<String>();
             if (groups) {
                 List<PermissionGroupInfo> infos =
@@ -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/Animator.java b/core/java/android/animation/Animator.java
index d331c2a..844063c 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -28,7 +28,6 @@
 
     /**
      * The value used to indicate infinite duration (e.g. when Animators repeat infinitely).
-     * @hide
      */
     public static final long DURATION_INFINITE = -1;
     /**
@@ -191,11 +190,18 @@
     /**
      * Gets the total duration of the animation, accounting for animation sequences, start delay,
      * and repeating. Return {@link #DURATION_INFINITE} if the duration is infinite.
-     * @hide
-     * TODO: Unhide
+     *
+     * @return  Total time an animation takes to finish, starting from the time {@link #start()}
+     *          is called. {@link #DURATION_INFINITE} will be returned if the animation or any
+     *          child animation repeats infinite times.
      */
     public long getTotalDuration() {
-        return getStartDelay() + getDuration();
+        long duration = getDuration();
+        if (duration == DURATION_INFINITE) {
+            return DURATION_INFINITE;
+        } else {
+            return getStartDelay() + duration;
+        }
     }
 
     /**
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index d444638..1ab55dd 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1030,9 +1030,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     @Override
     public long getTotalDuration() {
         updateAnimatorsDuration();
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/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 1995ef5..6f65889 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -126,12 +126,6 @@
             new AccelerateDecelerateInterpolator();
 
     /**
-     * Used to indicate whether the animation is currently playing in reverse. This causes the
-     * elapsed fraction to be inverted to calculate the appropriate values.
-     */
-    private boolean mPlayingBackwards = false;
-
-    /**
      * Flag to indicate whether this animator is playing in reverse mode, specifically
      * by being started or interrupted by a call to reverse(). This flag is different than
      * mPlayingBackwards, which indicates merely whether the current iteration of the
@@ -141,13 +135,13 @@
     private boolean mReversing;
 
     /**
-     * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
-     * repeatCount (if repeatCount!=INFINITE), the animation ends
+     * Tracks the overall fraction of the animation, ranging from 0 to mRepeatCount + 1
      */
-    private int mCurrentIteration = 0;
+    private float mOverallFraction = 0f;
 
     /**
      * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
+     * This is calculated by interpolating the fraction (range: [0, 1]) in the current iteration.
      */
     private float mCurrentFraction = 0f;
 
@@ -195,12 +189,12 @@
     //
 
     // How long the animation should last in ms
-    private long mUnscaledDuration = 300;
-    private long mDuration = (long)(mUnscaledDuration * sDurationScale);
+    private long mDuration = 300;
 
-    // The amount of time in ms to delay starting the animation after start() is called
-    long mStartDelay = 0;
-    private long mUnscaledStartDelay = 0;
+    // The amount of time in ms to delay starting the animation after start() is called. Note
+    // that this start delay is unscaled. When there is a duration scale set on the animator, the
+    // scaling factor will be applied to this delay.
+    private long mStartDelay = 0;
 
     // The number of times the animation will repeat. The default is 0, which means the animation
     // will play only once
@@ -524,13 +518,12 @@
             throw new IllegalArgumentException("Animators cannot have negative duration: " +
                     duration);
         }
-        mUnscaledDuration = duration;
-        updateScaledDuration();
+        mDuration = duration;
         return this;
     }
 
-    private void updateScaledDuration() {
-        mDuration = (long)(mUnscaledDuration * sDurationScale);
+    private long getScaledDuration() {
+        return (long)(mDuration * sDurationScale);
     }
 
     /**
@@ -540,18 +533,15 @@
      */
     @Override
     public long getDuration() {
-        return mUnscaledDuration;
+        return mDuration;
     }
 
-    /**
-     * @hide
-     */
     @Override
     public long getTotalDuration() {
         if (mRepeatCount == INFINITE) {
             return DURATION_INFINITE;
         } else {
-            return mUnscaledStartDelay + (mUnscaledDuration * (mRepeatCount + 1));
+            return mStartDelay + (mDuration * (mRepeatCount + 1));
         }
     }
 
@@ -566,7 +556,7 @@
      * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
      */
     public void setCurrentPlayTime(long playTime) {
-        float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1;
+        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
         setCurrentFraction(fraction);
     }
 
@@ -589,51 +579,102 @@
      */
     public void setCurrentFraction(float fraction) {
         initAnimation();
-        if (fraction < 0) {
-            fraction = 0;
-        }
-        int iteration = (int) fraction;
-        if (fraction == 1) {
-            iteration -= 1;
-        } else if (fraction > 1) {
-            if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE) {
-                if (mRepeatMode == REVERSE) {
-                    mPlayingBackwards = (iteration % 2) != 0;
-                }
-                fraction = fraction % 1f;
-            } else {
-                fraction = 1;
-                iteration -= 1;
-            }
-        } else {
-            mPlayingBackwards = mReversing;
-        }
-        mCurrentIteration = iteration;
-        long seekTime = (long) (mDuration * fraction);
+        fraction = clampFraction(fraction);
+        long seekTime = (long) (getScaledDuration() * fraction);
         long currentTime = AnimationUtils.currentAnimationTimeMillis();
         mStartTime = currentTime - seekTime;
         mStartTimeCommitted = true; // do not allow start time to be compensated for jank
         if (!mRunning) {
             mSeekFraction = fraction;
         }
-        if (mPlayingBackwards) {
-            fraction = 1f - fraction;
+        mOverallFraction = fraction;
+        final float currentIterationFraction = getCurrentIterationFraction(fraction);
+        animateValue(currentIterationFraction);
+    }
+
+    /**
+     * Calculates current iteration based on the overall fraction. The overall fraction will be
+     * in the range of [0, mRepeatCount + 1]. Both current iteration and fraction in the current
+     * iteration can be derived from it.
+     */
+    private int getCurrentIteration(float fraction) {
+        fraction = clampFraction(fraction);
+        // If the overall fraction is a positive integer, we consider the current iteration to be
+        // complete. In other words, the fraction for the current iteration would be 1, and the
+        // current iteration would be overall fraction - 1.
+        double iteration = Math.floor(fraction);
+        if (fraction == iteration && fraction > 0) {
+            iteration--;
         }
-        animateValue(fraction);
+        return (int) iteration;
+    }
+
+    /**
+     * Calculates the fraction of the current iteration, taking into account whether the animation
+     * should be played backwards. E.g. When the animation is played backwards in an iteration,
+     * the fraction for that iteration will go from 1f to 0f.
+     */
+    private float getCurrentIterationFraction(float fraction) {
+        fraction = clampFraction(fraction);
+        int iteration = getCurrentIteration(fraction);
+        float currentFraction = fraction - iteration;
+        return shouldPlayBackward(iteration) ? 1f - currentFraction : currentFraction;
+    }
+
+    /**
+     * Clamps fraction into the correct range: [0, mRepeatCount + 1]. If repeat count is infinite,
+     * no upper bound will be set for the fraction.
+     *
+     * @param fraction fraction to be clamped
+     * @return fraction clamped into the range of [0, mRepeatCount + 1]
+     */
+    private float clampFraction(float fraction) {
+        if (fraction < 0) {
+            fraction = 0;
+        } else if (mRepeatCount != INFINITE) {
+            fraction = Math.min(fraction, mRepeatCount + 1);
+        }
+        return fraction;
+    }
+
+    /**
+     * Calculates the direction of animation playing (i.e. forward or backward), based on 1)
+     * whether the entire animation is being reversed, 2) repeat mode applied to the current
+     * iteration.
+     */
+    private boolean shouldPlayBackward(int iteration) {
+        if (iteration > 0 && mRepeatMode == REVERSE &&
+                (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+            // if we were seeked to some other iteration in a reversing animator,
+            // figure out the correct direction to start playing based on the iteration
+            if (mReversing) {
+                return (iteration % 2) == 0;
+            } else {
+                return (iteration % 2) != 0;
+            }
+        } else {
+            return mReversing;
+        }
     }
 
     /**
      * Gets the current position of the animation in time, which is equal to the current
      * time minus the time that the animation started. An animation that is not yet started will
-     * return a value of zero.
+     * return a value of zero, unless the animation has has its play time set via
+     * {@link #setCurrentPlayTime(long)} or {@link #setCurrentFraction(float)}, in which case
+     * it will return the time that was set.
      *
      * @return The current position in time of the animation.
      */
     public long getCurrentPlayTime() {
-        if (!mInitialized || !mStarted) {
+        if (!mInitialized || (!mStarted && mSeekFraction < 0)) {
             return 0;
         }
-        return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
+        if (mSeekFraction >= 0) {
+            return (long) (mDuration * mSeekFraction);
+        }
+        float durationScale = sDurationScale == 0 ? 1 : sDurationScale;
+        return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
     }
 
     /**
@@ -644,7 +685,7 @@
      */
     @Override
     public long getStartDelay() {
-        return mUnscaledStartDelay;
+        return mStartDelay;
     }
 
     /**
@@ -655,8 +696,7 @@
      */
     @Override
     public void setStartDelay(long startDelay) {
-        this.mStartDelay = (long)(startDelay * sDurationScale);
-        mUnscaledStartDelay = startDelay;
+        mStartDelay = startDelay;
     }
 
     /**
@@ -902,40 +942,27 @@
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
         mReversing = playBackwards;
-        mPlayingBackwards = playBackwards;
-        if (playBackwards && mSeekFraction != -1) {
-            if (mSeekFraction == 0 && mCurrentIteration == 0) {
-                // special case: reversing from seek-to-0 should act as if not seeked at all
-                mSeekFraction = 0;
-            } else if (mRepeatCount == INFINITE) {
-                mSeekFraction = 1 - (mSeekFraction % 1);
+        // Special case: reversing from seek-to-0 should act as if not seeked at all.
+        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
+            if (mRepeatCount == INFINITE) {
+                // Calculate the fraction of the current iteration.
+                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
+                mSeekFraction = 1 - fraction;
             } else {
-                mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
-            }
-            mCurrentIteration = (int) mSeekFraction;
-            mSeekFraction = mSeekFraction % 1;
-        }
-        if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
-                (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
-            // if we were seeked to some other iteration in a reversing animator,
-            // figure out the correct direction to start playing based on the iteration
-            if (playBackwards) {
-                mPlayingBackwards = (mCurrentIteration % 2) == 0;
-            } else {
-                mPlayingBackwards = (mCurrentIteration % 2) != 0;
+                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
             }
         }
         mStarted = true;
         mPaused = false;
         mRunning = false;
         mAnimationEndRequested = false;
-        updateScaledDuration(); // in case the scale factor has changed since creation time
         AnimationHandler animationHandler = AnimationHandler.getInstance();
-        animationHandler.addAnimationFrameCallback(this, mStartDelay);
+        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
 
         if (mStartDelay == 0) {
             // If there's no start delay, init the animation and notify start listeners right away
-            // Otherwise, postpone this until the first frame after the start delay.
+            // to be consistent with the previous behavior. Otherwise, postpone this until the first
+            // frame after the start delay.
             startAnimation();
             setCurrentFraction(mSeekFraction == -1 ? 0 : mSeekFraction);
         }
@@ -988,7 +1015,7 @@
         } else if (!mInitialized) {
             initAnimation();
         }
-        animateValue(mPlayingBackwards ? 0f : 1f);
+        animateValue(shouldPlayBackward(mRepeatCount) ? 0f : 1f);
         endAnimation();
     }
 
@@ -1029,11 +1056,10 @@
      */
     @Override
     public void reverse() {
-        mPlayingBackwards = !mPlayingBackwards;
         if (mRunning) {
             long currentTime = AnimationUtils.currentAnimationTimeMillis();
             long currentPlayTime = currentTime - mStartTime;
-            long timeLeft = mDuration - currentPlayTime;
+            long timeLeft = getScaledDuration() - currentPlayTime;
             mStartTime = currentTime - timeLeft;
             mStartTimeCommitted = true; // do not allow start time to be compensated for jank
             mReversing = !mReversing;
@@ -1080,10 +1106,8 @@
         mRunning = false;
         mStarted = false;
         mStartListenersCalled = false;
-        mPlayingBackwards = false;
         mReversing = false;
         mLastFrameTime = 0;
-        mCurrentIteration = 0;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                     System.identityHashCode(this));
@@ -1101,6 +1125,11 @@
         }
         initAnimation();
         mRunning = true;
+        if (mSeekFraction >= 0) {
+            mOverallFraction = mSeekFraction;
+        } else {
+            mOverallFraction = 0f;
+        }
         if (mListeners != null) {
             notifyStartListeners();
         }
@@ -1146,40 +1175,26 @@
     boolean animateBasedOnTime(long currentTime) {
         boolean done = false;
         if (mRunning) {
-            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
-            if (mDuration == 0 && mRepeatCount != INFINITE) {
-                // Skip to the end
-                mCurrentIteration = mRepeatCount;
-                if (!mReversing) {
-                    mPlayingBackwards = false;
-                }
-            }
-            if (fraction >= 1f) {
-                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
-                    // Time to repeat
-                    if (mListeners != null) {
-                        int numListeners = mListeners.size();
-                        for (int i = 0; i < numListeners; ++i) {
-                            mListeners.get(i).onAnimationRepeat(this);
-                        }
+            final float fraction = getScaledDuration() > 0 ?
+                    (float)(currentTime - mStartTime) / getScaledDuration() : 1f;
+            final float lastFraction = mOverallFraction;
+            final boolean newIteration = (int) fraction > (int) lastFraction;
+            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
+                    (mRepeatCount != INFINITE);
+            if (newIteration && !lastIterationFinished) {
+                // Time to repeat
+                if (mListeners != null) {
+                    int numListeners = mListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        mListeners.get(i).onAnimationRepeat(this);
                     }
-                    if (mRepeatMode == REVERSE) {
-                        mPlayingBackwards = !mPlayingBackwards;
-                    }
-                    mCurrentIteration += (int) fraction;
-                    fraction = fraction % 1f;
-                    mStartTime += mDuration;
-                    // Note: We do not need to update the value of mStartTimeCommitted here
-                    // since we just added a duration offset.
-                } else {
-                    done = true;
-                    fraction = Math.min(fraction, 1.0f);
                 }
+            } else if (lastIterationFinished) {
+                done = true;
             }
-            if (mPlayingBackwards) {
-                fraction = 1f - fraction;
-            }
-            animateValue(fraction);
+            mOverallFraction = clampFraction(fraction);
+            float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
+            animateValue(currentIterationFraction);
         }
         return done;
     }
@@ -1202,7 +1217,7 @@
             if (mSeekFraction < 0) {
                 mStartTime = frameTime;
             } else {
-                long seekTime = (long) (mDuration * mSeekFraction);
+                long seekTime = (long) (getScaledDuration() * mSeekFraction);
                 mStartTime = frameTime - seekTime;
                 mSeekFraction = -1;
             }
@@ -1280,9 +1295,7 @@
             anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
         }
         anim.mSeekFraction = -1;
-        anim.mPlayingBackwards = false;
         anim.mReversing = false;
-        anim.mCurrentIteration = 0;
         anim.mInitialized = false;
         anim.mStarted = false;
         anim.mRunning = false;
@@ -1294,6 +1307,7 @@
         anim.mAnimationEndRequested = false;
         anim.mPauseTime = 0;
         anim.mLastFrameTime = 0;
+        anim.mOverallFraction = 0;
         anim.mCurrentFraction = 0;
 
         PropertyValuesHolder[] oldValues = mValues;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f60250c..2f0849f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -687,9 +687,15 @@
 
     /** @hide Task isn't finished when activity is finished */
     public static final int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
-    /** @hide Task is finished if the finishing activity is the root of the task */
+    /**
+     * @hide Task is finished if the finishing activity is the root of the task. To preserve the
+     * past behavior the task is also removed from recents.
+     */
     public static final int FINISH_TASK_WITH_ROOT_ACTIVITY = 1;
-    /** @hide Task is finished along with the finishing activity*/
+    /**
+     * @hide Task is finished along with the finishing activity, but it is not removed from
+     * recents.
+     */
     public static final int FINISH_TASK_WITH_ACTIVITY = 2;
 
     static final String FRAGMENTS_TAG = "android:fragments";
@@ -699,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:";
 
@@ -807,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;
@@ -1308,6 +1318,7 @@
         onSaveInstanceState(outState);
         saveManagedDialogs(outState);
         mActivityTransitionState.saveState(outState);
+        storeHasCurrentPermissionRequest(outState);
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
     }
 
@@ -1323,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);
     }
@@ -3856,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;
     }
 
     /**
@@ -6295,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();
@@ -6483,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(
@@ -6610,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..6ae32d0 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!
@@ -441,10 +434,17 @@
     public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
 
     /**
+     * ID of stack that always on top (always visible) when it exist.
+     * Mainly used for this in Picture-in-Picture mode.
+     * @hide
+     */
+    public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
+
+    /**
      * Last static stack stack ID.
      * @hide
      */
-    public static final int LAST_STATIC_STACK_ID = DOCKED_STACK_ID;
+    public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
 
     /**
      * Start of ID range used by stacks that are created dynamically.
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index cb1a89f..70f1bcc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -330,9 +330,11 @@
         case START_ACTIVITY_FROM_RECENTS_TRANSACTION:
         {
             data.enforceInterface(IActivityManager.descriptor);
-            int taskId = data.readInt();
-            Bundle options = data.readInt() == 0 ? null : Bundle.CREATOR.createFromParcel(data);
-            int result = startActivityFromRecents(taskId, options);
+            final int taskId = data.readInt();
+            final int launchStackId = data.readInt();
+            final Bundle options =
+                    data.readInt() == 0 ? null : Bundle.CREATOR.createFromParcel(data);
+            final int result = startActivityFromRecents(taskId, launchStackId, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -753,11 +755,22 @@
             return true;
         }
 
+        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int stackId = data.readInt();
+            final Rect r = Rect.CREATOR.createFromParcel(data);
+            final boolean res = moveTopActivityToPinnedStack(stackId, r);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
         case RESIZE_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
-            int stackId = data.readInt();
+            final int stackId = data.readInt();
             Rect r = Rect.CREATOR.createFromParcel(data);
-            resizeStack(stackId, r);
+            final boolean allowResizeInDockedMode = data.readInt() == 1;
+            resizeStack(stackId, r, allowResizeInDockedMode);
             reply.writeNoException();
             return true;
         }
@@ -2689,6 +2702,13 @@
             reply.writeNoException();
             return true;
         }
+        case REMOVE_STACK: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int stackId = data.readInt();
+            removeStack(stackId);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -2984,11 +3004,13 @@
         data.recycle();
         return result != 0;
     }
-    public int startActivityFromRecents(int taskId, Bundle options) throws RemoteException {
+    public int startActivityFromRecents(int taskId, int launchStackId, Bundle options)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(taskId);
+        data.writeInt(launchStackId);
         if (options == null) {
             data.writeInt(0);
         } else {
@@ -3543,13 +3565,31 @@
         reply.recycle();
     }
     @Override
-    public void resizeStack(int stackId, Rect r) throws RemoteException
+    public boolean moveTopActivityToPinnedStack(int stackId, Rect r)
+        throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
         r.writeToParcel(data, 0);
+        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+        reply.readException();
+        final boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    @Override
+    public void resizeStack(int stackId, Rect r, boolean allowResizeInDockedMode)
+            throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(stackId);
+        r.writeToParcel(data, 0);
+        data.writeInt(allowResizeInDockedMode ? 1 : 0);
         mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -6235,5 +6275,17 @@
         reply.recycle();
     }
 
+    @Override
+    public void removeStack(int stackId) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(stackId);
+        mRemote.transact(REMOVE_STACK, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4b8efab..b9292de 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -101,11 +101,13 @@
 import android.renderscript.RenderScriptCacheDir;
 import android.security.keystore.AndroidKeyStoreProvider;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastPrintWriter;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
 import com.android.org.conscrypt.TrustedCertificateStore;
@@ -163,6 +165,7 @@
     private static final boolean DEBUG_SERVICE = false;
     private static final boolean DEBUG_MEMORY_TRIM = false;
     private static final boolean DEBUG_PROVIDER = false;
+    private static final boolean DEBUG_ORDER = false;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
     private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
@@ -175,6 +178,10 @@
     /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
     public static final int SERVICE_DONE_EXECUTING_STOP = 2;
 
+    // Details for pausing activity.
+    private static final int USER_LEAVING = 1;
+    private static final int DONT_REPORT = 2;
+
     private ContextImpl mSystemContext;
 
     static IPackageManager sPackageManager;
@@ -231,6 +238,12 @@
     final ArrayList<ActivityClientRecord> mRelaunchingActivities
             = new ArrayList<ActivityClientRecord>();
     Configuration mPendingConfiguration = null;
+    // Because we merge activity relaunch operations we can't depend on the ordering provided by
+    // the handler messages. We need to introduce secondary ordering mechanism, which will allow
+    // us to drop certain events, if we know that they happened before relaunch we already executed.
+    // This represents the order of receiving the request from AM.
+    @GuardedBy("mResourcesManager")
+    int mLifecycleSeq = 0;
 
     private final ResourcesManager mResourcesManager;
 
@@ -319,6 +332,14 @@
         WindowManager mPendingRemoveWindowManager;
         boolean mPreserveWindow;
 
+        // Set for relaunch requests, indicates the order number of the relaunch operation, so it
+        // can be compared with other lifecycle operations.
+        int relaunchSeq = 0;
+
+        // Can only be accessed from the UI thread. This represents the latest processed message
+        // that is related to lifecycle events/
+        int lastProcessedSeq = 0;
+
         ActivityClientRecord() {
             parent = null;
             embeddedID = null;
@@ -448,7 +469,6 @@
         IUiAutomationConnection instrumentationUiAutomationConnection;
         int debugMode;
         boolean enableBinderTracking;
-        boolean enableOpenGlTrace;
         boolean trackAllocation;
         boolean restrictedBackupMode;
         boolean persistent;
@@ -592,18 +612,25 @@
 
         public final void schedulePauseActivity(IBinder token, boolean finished,
                 boolean userLeaving, int configChanges, boolean dontReport) {
+            int seq = getLifecycleSeq();
+            if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
+                    + " operation received seq: " + seq);
             sendMessage(
                     finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                     token,
-                    (userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
-                    configChanges);
+                    (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
+                    configChanges,
+                    seq);
         }
 
         public final void scheduleStopActivity(IBinder token, boolean showWindow,
                 int configChanges) {
-           sendMessage(
+            int seq = getLifecycleSeq();
+            if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this
+                    + " operation received seq: " + seq);
+            sendMessage(
                 showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
-                token, 0, configChanges);
+                token, 0, configChanges, seq);
         }
 
         public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
@@ -618,8 +645,11 @@
 
         public final void scheduleResumeActivity(IBinder token, int processState,
                 boolean isForward, Bundle resumeArgs) {
+            int seq = getLifecycleSeq();
+            if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
+                    + " operation received seq: " + seq);
             updateProcessState(processState, false);
-            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
         }
 
         public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -774,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
@@ -833,7 +862,6 @@
             data.instrumentationUiAutomationConnection = instrumentationUiConnection;
             data.debugMode = debugMode;
             data.enableBinderTracking = enableBinderTracking;
-            data.enableOpenGlTrace = enableOpenGlTrace;
             data.trackAllocation = trackAllocation;
             data.restrictedBackupMode = isRestrictedBackupMode;
             data.persistent = persistent;
@@ -1245,6 +1273,12 @@
         }
     }
 
+    private int getLifecycleSeq() {
+        synchronized (mResourcesManager) {
+            return mLifecycleSeq++;
+        }
+    }
+
     private class H extends Handler {
         public static final int LAUNCH_ACTIVITY         = 100;
         public static final int PAUSE_ACTIVITY          = 101;
@@ -1373,29 +1407,34 @@
                     handleRelaunchActivity(r);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                 } break;
-                case PAUSE_ACTIVITY:
+                case PAUSE_ACTIVITY: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
-                            (msg.arg1&2) != 0);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handlePauseActivity((IBinder) args.arg1, false,
+                            (args.argi1 & USER_LEAVING) != 0, args.argi2,
+                            (args.argi1 & DONT_REPORT) != 0, args.argi3);
                     maybeSnapshot();
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case PAUSE_ACTIVITY_FINISHING:
+                } break;
+                case PAUSE_ACTIVITY_FINISHING: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
-                            (msg.arg1&1) != 0);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
+                            args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case STOP_ACTIVITY_SHOW:
+                } break;
+                case STOP_ACTIVITY_SHOW: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                case STOP_ACTIVITY_HIDE:
+                } break;
+                case STOP_ACTIVITY_HIDE: {
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
+                } break;
                 case SHOW_WINDOW:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
                     handleWindowVisibility((IBinder)msg.obj, true);
@@ -1408,7 +1447,9 @@
                     break;
                 case RESUME_ACTIVITY:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
-                    handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
+                            args.argi3);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 case SEND_RESULT:
@@ -1587,6 +1628,10 @@
                     handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
                     break;
             }
+            Object obj = msg.obj;
+            if (obj instanceof SomeArgs) {
+                ((SomeArgs) obj).recycle();
+            }
             if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
         }
 
@@ -2311,6 +2356,21 @@
         mH.sendMessage(msg);
     }
 
+    private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
+        if (DEBUG_MESSAGES) Slog.v(
+                TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
+                        "seq= " + seq);
+        Message msg = Message.obtain();
+        msg.what = what;
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = obj;
+        args.argi1 = arg1;
+        args.argi2 = arg2;
+        args.argi3 = seq;
+        msg.obj = args;
+        mH.sendMessage(msg);
+    }
+
     final void scheduleContextCleanup(ContextImpl context, String who,
             String what) {
         ContextCleanupInfo cci = new ContextCleanupInfo();
@@ -2516,7 +2576,7 @@
             reportSizeConfigurations(r);
             Bundle oldState = r.state;
             handleResumeActivity(r.token, false, r.isForward,
-                    !r.activity.mFinished && !r.startsNotResumed);
+                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq);
 
             if (!r.activity.mFinished && r.startsNotResumed) {
                 // The activity manager actually wants this one to start out
@@ -3216,14 +3276,19 @@
     }
 
     final void handleResumeActivity(IBinder token,
-            boolean clearHide, boolean isForward, boolean reallyResume) {
+            boolean clearHide, boolean isForward, boolean reallyResume, int seq) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
+            return;
+        }
+
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
         mSomeActivitiesChanged = true;
 
         // TODO Push resumeArgs into the activity for consideration
-        ActivityClientRecord r = performResumeActivity(token, clearHide);
+        r = performResumeActivity(token, clearHide);
 
         if (r != null) {
             final Activity a = r.activity;
@@ -3400,8 +3465,11 @@
     }
 
     private void handlePauseActivity(IBinder token, boolean finished,
-            boolean userLeaving, int configChanges, boolean dontReport) {
+            boolean userLeaving, int configChanges, boolean dontReport, int seq) {
         ActivityClientRecord r = mActivities.get(token);
+        if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) {
+            return;
+        }
         if (r != null) {
             //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
             if (userLeaving) {
@@ -3639,8 +3707,11 @@
         }
     }
 
-    private void handleStopActivity(IBinder token, boolean show, int configChanges) {
+    private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
         ActivityClientRecord r = mActivities.get(token);
+        if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) {
+            return;
+        }
         r.activity.mConfigChangeFlags |= configChanges;
 
         StopInfo info = new StopInfo();
@@ -3669,6 +3740,20 @@
         mSomeActivitiesChanged = true;
     }
 
+    private static boolean checkAndUpdateLifecycleSeq(int seq, ActivityClientRecord r,
+            String action) {
+        if (r == null) {
+            return true;
+        }
+        if (seq < r.lastProcessedSeq) {
+            if (DEBUG_ORDER) Slog.d(TAG, action + " for " + r + " ignored, because seq=" + seq
+                    + " < mCurrentLifecycleSeq=" + r.lastProcessedSeq);
+            return false;
+        }
+        r.lastProcessedSeq = seq;
+        return true;
+    }
+
     final void performRestartActivity(IBinder token) {
         ActivityClientRecord r = mActivities.get(token);
         if (r.stopped) {
@@ -3956,13 +4041,12 @@
                 }
                 IBinder wtoken = v.getWindowToken();
                 if (r.activity.mWindowAdded) {
-                    boolean reuseForResize = r.window.hasNonClientDecorView() && r.mPreserveWindow;
-                    if (r.onlyLocalRequest || reuseForResize) {
+                    if (r.onlyLocalRequest || r.mPreserveWindow) {
                         // Hold off on removing this until the new activity's
                         // window is being added.
                         r.mPendingRemoveWindow = r.window;
                         r.mPendingRemoveWindowManager = wm;
-                        if (reuseForResize) {
+                        if (r.mPreserveWindow) {
                             // We can only keep the part of the view hierarchy that we control,
                             // everything else must be removed, because it might not be able to
                             // behave properly when activity is relaunching.
@@ -4071,7 +4155,10 @@
                 target.overrideConfig = overrideConfig;
             }
             target.pendingConfigChanges |= configChanges;
+            target.relaunchSeq = getLifecycleSeq();
         }
+        if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this
+                + " operation received seq: " + target.relaunchSeq);
     }
 
     private void handleRelaunchActivity(ActivityClientRecord tmp) {
@@ -4116,6 +4203,12 @@
             }
         }
 
+        if (tmp.lastProcessedSeq > tmp.relaunchSeq) {
+            Slog.wtf(TAG, "For some reason target: " + tmp + " has lower sequence: "
+                    + tmp.relaunchSeq + " than current sequence: " + tmp.lastProcessedSeq);
+        } else {
+            tmp.lastProcessedSeq = tmp.relaunchSeq;
+        }
         if (tmp.createdConfig != null) {
             // If the activity manager is passing us its current config,
             // assume that is really what we want regardless of what we
@@ -4149,6 +4242,8 @@
         r.activity.mConfigChangeFlags |= configChanges;
         r.onlyLocalRequest = tmp.onlyLocalRequest;
         r.mPreserveWindow = tmp.mPreserveWindow;
+        r.lastProcessedSeq = tmp.lastProcessedSeq;
+        r.relaunchSeq = tmp.relaunchSeq;
         Intent currentIntent = r.activity.mIntent;
 
         r.activity.mChangingConfigurations = true;
@@ -4678,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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f29dba2..e500e15 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1646,7 +1646,8 @@
             return false;
         }
         if (!forceAllowOnExternal
-                && app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+                && (app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY
+                        || app.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED)) {
             return false;
         }
 
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/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index fea5624..e5fa02b 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -33,6 +33,8 @@
     private int interruptionFilter;
     private Uri conditionId;
     private ComponentName owner;
+    private String id;
+    private long creationTime;
 
     /**
      * Creates an automatic zen rule.
@@ -55,6 +57,17 @@
         this.enabled = enabled;
     }
 
+    /**
+     * @SystemApi
+     * @hide
+     */
+    public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
+            int interruptionFilter, boolean enabled, String id, long creationTime) {
+        this(name, owner, conditionId, interruptionFilter, enabled);
+        this.id = id;
+        this.creationTime = creationTime;
+    }
+
     public AutomaticZenRule(Parcel source) {
         enabled = source.readInt() == 1;
         if (source.readInt() == 1) {
@@ -63,6 +76,10 @@
         interruptionFilter = source.readInt();
         conditionId = source.readParcelable(null);
         owner = source.readParcelable(null);
+        if (source.readInt() == 1) {
+            id = source.readString();
+        }
+        creationTime = source.readLong();
     }
 
     /**
@@ -101,6 +118,20 @@
     }
 
     /**
+     * Returns the wall time in milliseconds when this rule was created, if known.
+     */
+    public long getCreationTime() {
+      return creationTime;
+    }
+
+    /**
+     * Returns the unique identifier for this rule.
+     */
+    public String getId() {
+      return id;
+    }
+
+    /**
      * Sets the representation of the state that causes this rule to become active.
      */
     public void setConditionId(Uri conditionId) {
@@ -146,6 +177,13 @@
         dest.writeInt(interruptionFilter);
         dest.writeParcelable(conditionId, 0);
         dest.writeParcelable(owner, 0);
+        if (id != null) {
+            dest.writeInt(1);
+            dest.writeString(id);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeLong(creationTime);
     }
 
     @Override
@@ -156,6 +194,8 @@
                 .append(",interruptionFilter=").append(interruptionFilter)
                 .append(",conditionId=").append(conditionId)
                 .append(",owner=").append(owner)
+                .append(",id=").append(id)
+                .append(",creationTime=").append(creationTime)
                 .append(']').toString();
     }
 
@@ -168,12 +208,14 @@
                 && Objects.equals(other.name, name)
                 && other.interruptionFilter == interruptionFilter
                 && Objects.equals(other.conditionId, conditionId)
-                && Objects.equals(other.owner, owner);
+                && Objects.equals(other.owner, owner)
+                && Objects.equals(other.id, id)
+                && other.creationTime == creationTime;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(enabled, name, interruptionFilter, conditionId, owner);
+        return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, id, creationTime);
     }
 
     public static final Parcelable.Creator<AutomaticZenRule> CREATOR
@@ -187,4 +229,4 @@
             return new AutomaticZenRule[size];
         }
     };
-}
\ No newline at end of file
+}
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/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 478fdd1..ebdd302 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -16,8 +16,8 @@
 
 package android.app;
 
-import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.RunningServiceInfo;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -92,7 +92,8 @@
             int userId) throws RemoteException;
     public boolean startNextMatchingActivity(IBinder callingActivity,
             Intent intent, Bundle options) throws RemoteException;
-    public int startActivityFromRecents(int taskId, Bundle options) throws RemoteException;
+    public int startActivityFromRecents(int taskId, int launchStackId, Bundle options)
+            throws RemoteException;
     public boolean finishActivity(IBinder token, int code, Intent data, int finishTask)
             throws RemoteException;
     public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
@@ -142,7 +143,8 @@
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
     public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
             throws RemoteException;
-    public void resizeStack(int stackId, Rect bounds) throws RemoteException;
+    public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
+    public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException;
     public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException;
     public List<StackInfo> getAllStackInfos() throws RemoteException;
     public StackInfo getStackInfo(int stackId) throws RemoteException;
@@ -537,6 +539,8 @@
 
     public void suppressResizeConfigChanges(boolean suppress) throws RemoteException;
 
+    public void removeStack(int stackId) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -894,4 +898,6 @@
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
     int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
     int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
+    int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348;
+    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349;
 }
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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 920fbe9..e95a35a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -93,11 +93,11 @@
     String[] getPackagesRequestingNotificationPolicyAccess();
     boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
     void setNotificationPolicyAccessGranted(String pkg, boolean granted);
-    AutomaticZenRule getAutomaticZenRule(String name);
+    AutomaticZenRule getAutomaticZenRule(String id);
     List<AutomaticZenRule> getAutomaticZenRules();
-    boolean addOrUpdateAutomaticZenRule(in AutomaticZenRule automaticZenRule);
-    boolean renameAutomaticZenRule(String oldName, String newName);
-    boolean removeAutomaticZenRule(String name);
+    AutomaticZenRule addAutomaticZenRule(in AutomaticZenRule automaticZenRule);
+    boolean updateAutomaticZenRule(in AutomaticZenRule automaticZenRule);
+    boolean removeAutomaticZenRule(String id);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
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/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cbf198b..cb0ff33 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -436,48 +436,47 @@
     }
 
     /**
-     * Returns the AutomaticZenRule with the given name, if it exists and the caller has access.
+     * Returns the AutomaticZenRule with the given id, if it exists and the caller has access.
      *
      * <p>
      * Only available if policy access is granted to this package.
      * See {@link #isNotificationPolicyAccessGranted}.
      *
      * <p>
-     * Returns null if there are no zen rules that match the given name, or if the calling package
+     * Returns null if there are no zen rules that match the given id, or if the calling package
      * doesn't own the matching rule. See {@link AutomaticZenRule#getOwner}.
      */
-    public AutomaticZenRule getAutomaticZenRule(String name) {
+    public AutomaticZenRule getAutomaticZenRule(String id) {
         INotificationManager service = getService();
         try {
-            return service.getAutomaticZenRule(name);
+            return service.getAutomaticZenRule(id);
         } catch (RemoteException e) {
         }
         return null;
     }
 
     /**
-     * Creates or updates the given zen rule.
+     * Creates the given zen rule.
      *
      * <p>
      * Only available if policy access is granted to this package.
      * See {@link #isNotificationPolicyAccessGranted}.
      *
-     * <p>
-     * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
-     * @param automaticZenRule the rule to create or update.
-     * @return Whether the rule was successfully created or updated.
+     * @param automaticZenRule the rule to create.
+     * @return A fully populated {@link AutomaticZenRule} if the rule was persisted successfully,
+     * null otherwise.
      */
-    public boolean addOrUpdateAutomaticZenRule(AutomaticZenRule automaticZenRule) {
+    public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule) {
         INotificationManager service = getService();
         try {
-            return service.addOrUpdateAutomaticZenRule(automaticZenRule);
+            return service.addAutomaticZenRule(automaticZenRule);
         } catch (RemoteException e) {
         }
-        return false;
+        return null;
     }
 
     /**
-     * Renames a zen rule.
+     * Updates the given zen rule.
      *
      * <p>
      * Only available if policy access is granted to this package.
@@ -485,21 +484,20 @@
      *
      * <p>
      * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
-     * @param oldName The name of the rule to update.
-     * @param newName The new name for the rule.
+     * @param automaticZenRule the rule to update. 
      * @return Whether the rule was successfully updated.
      */
-    public boolean renameAutomaticZenRule(String oldName, String newName) {
+    public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule) {
         INotificationManager service = getService();
         try {
-            return service.renameAutomaticZenRule(oldName, newName);
+            return service.updateAutomaticZenRule(automaticZenRule);
         } catch (RemoteException e) {
         }
         return false;
     }
 
     /**
-     * Deletes the automatic zen rule with the given name.
+     * Deletes the automatic zen rule with the given id.
      *
      * <p>
      * Only available if policy access is granted to this package.
@@ -507,13 +505,13 @@
      *
      * <p>
      * Callers can only delete rules that they own. See {@link AutomaticZenRule#getOwner}.
-     * @param name the name of the rule to delete.
+     * @param id the id of the rule to delete.
      * @return Whether the rule was successfully deleted.
      */
-    public boolean removeAutomaticZenRule(String name) {
+    public boolean removeAutomaticZenRule(String id) {
         INotificationManager service = getService();
         try {
-            return service.removeAutomaticZenRule(name);
+            return service.removeAutomaticZenRule(id);
         } catch (RemoteException e) {
         }
         return false;
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index a1bb40c..84b6d39 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -227,24 +227,6 @@
     public static final String ACTION_PROFILE_PROVISIONING_COMPLETE =
             "android.app.action.PROFILE_PROVISIONING_COMPLETE";
 
-    /**
-     * @hide
-     * Broadcast Action: This broadcast is sent to indicate that the system is ready for the device
-     * initializer to perform user setup tasks. This is only applicable to devices managed by a
-     * device owner app.
-     *
-     * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in
-     * the device initializer field of the original intent or NFC bump that started the provisioning
-     * process. You will generally handle this in
-     * {@link DeviceAdminReceiver#onReadyForUserInitialization}.
-     *
-     * <p>Input: Nothing.</p>
-     * <p>Output: Nothing</p>
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_READY_FOR_USER_INITIALIZATION =
-            "android.app.action.READY_FOR_USER_INITIALIZATION";
-
     /** @hide */
     public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
 
@@ -435,23 +417,13 @@
 
     /**
      * Called during provisioning of a managed device to allow the device initializer to perform
-     * user setup steps. Only device initializers should override this method.
-     *
-     * <p> Called when the DeviceAdminReceiver receives an
-     * android.app.action.ACTION_READY_FOR_USER_INITIALIZATION broadcast. As a prerequisite for the
-     * execution of this callback the {@link DeviceAdminReceiver} has
-     * to declare an intent filter for android.app.action.ACTION_READY_FOR_USER_INITIALIZATION. Only
-     * the component specified in the device initializer component name field of the
-     * original intent or NFC bump that started the provisioning process will receive this callback.
-     *
-     * <p>It is not assumed that the device initializer is finished when it returns from
-     * this call, as it may do additional setup asynchronously. The device initializer must enable
-     * the current user when it has finished any additional setup (such as adding an account by
-     * using the {@link AccountManager}) in order for the user to be functional.
+     * user setup steps.
      *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
+     * @deprecated Do not use
      */
+    @Deprecated
     @SystemApi
     public void onReadyForUserInitialization(Context context, Intent intent) {
     }
@@ -549,8 +521,6 @@
             onLockTaskModeEntering(context, intent, pkg);
         } else if (ACTION_LOCK_TASK_EXITING.equals(action)) {
             onLockTaskModeExiting(context, intent);
-        } else if (ACTION_READY_FOR_USER_INITIALIZATION.equals(action)) {
-            onReadyForUserInitialization(context, intent);
         } else if (ACTION_NOTIFY_PENDING_SYSTEM_UPDATE.equals(action)) {
             long receivedTime = intent.getLongExtra(EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, -1);
             onSystemUpdatePending(context, intent, receivedTime);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a118f16..9e9d949 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -341,7 +341,8 @@
 
     /**
      * A String extra indicating the security type of the wifi network in
-     * {@link #EXTRA_PROVISIONING_WIFI_SSID}.
+     * {@link #EXTRA_PROVISIONING_WIFI_SSID} and could be one of {@code NONE}, {@code WPA} or
+     * {@code WEP}.
      *
      * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
      * provisioning via an NFC bump.
@@ -497,96 +498,6 @@
              "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
 
     /**
-     * @hide
-     * On devices managed by a device owner app, a {@link ComponentName} extra indicating the
-     * component of the application that is temporarily granted device owner privileges during
-     * device initialization and profile owner privileges during secondary user initialization.
-     *
-     * <p>
-     * It can also be used in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts
-     * device owner provisioning via an NFC bump. For the NFC record, it should be flattened to a
-     * string first.
-     *
-     * @see ComponentName#flattenToShortString()
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
-
-    /**
-     * @hide
-     * A String extra holding an http url that specifies the download location of the device
-     * initializer package. When not provided it is assumed that the device initializer package is
-     * already installed.
-     *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
-     * provisioning via an NFC bump.
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION";
-
-    /**
-     * @hide
-     * An int extra holding a minimum required version code for the device initializer package.
-     * If the initializer is already installed on the device, it will only be re-downloaded from
-     * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION} if the version of
-     * the installed package is less than this version code.
-     *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
-     * provisioning via an NFC bump.
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_MINIMUM_VERSION_CODE
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_MINIMUM_VERSION_CODE";
-
-    /**
-     * @hide
-     * A String extra holding a http cookie header which should be used in the http request to the
-     * url specified in {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}.
-     *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
-     * provisioning via an NFC bump.
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
-
-    /**
-     * @hide
-     * A String extra holding the URL-safe base64 encoded SHA-256 checksum of the file at download
-     * location specified in
-     * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}.
-     *
-     * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_SIGNATURE_CHECKSUM}
-     * should be present. The provided checksum should match the checksum of the file at the
-     * download location. If the checksum doesn't match an error will be shown to the user and the
-     * user will be asked to factory reset the device.
-     *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
-     * provisioning via an NFC bump.
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM";
-
-    /**
-     * @hide
-     * A String extra holding the URL-safe base64 encoded SHA-256 checksum of any signature of the
-     * android package archive at the download location specified in {@link
-     * #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}.
-     *
-     * <p>The signatures of an android package archive can be obtained using
-     * {@link android.content.pm.PackageManager#getPackageArchiveInfo} with flag
-     * {@link android.content.pm.PackageManager#GET_SIGNATURES}.
-     *
-     * <p>Either this extra or {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM}
-     * should be present. The provided checksum should match the checksum of any signature of the
-     * file at the download location. If the checksum doesn't match an error will be shown to the
-     * user and the user will be asked to factory reset the device.
-     *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
-     * provisioning via an NFC bump.
-     */
-    public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_SIGNATURE_CHECKSUM
-        = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_SIGNATURE_CHECKSUM";
-
-    /**
      * This MIME type is used for starting the Device Owner provisioning.
      *
      * <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -629,44 +540,6 @@
     public static final String MIME_TYPE_PROVISIONING_NFC
         = "application/com.android.managedprovisioning";
 
-
-    /**
-     * @hide
-     * This MIME type is used for starting the Device Owner provisioning that requires
-     * new provisioning features introduced in API version
-     * {@link android.os.Build.VERSION_CODES#M} in addition to those supported in earlier
-     * versions.
-     *
-     * <p>During device owner provisioning a device admin app is set as the owner of the device.
-     * A device owner has full control over the device. The device owner can not be modified by the
-     * user.
-     *
-     * <p> A typical use case would be a device that is owned by a company, but used by either an
-     * employee or client.
-     *
-     * <p> The NFC message should be sent to an unprovisioned device.
-     *
-     * <p>The NFC record must contain a serialized {@link java.util.Properties} object which
-     * contains the following properties in addition to properties listed at
-     * {@link #MIME_TYPE_PROVISIONING_NFC}:
-     * <ul>
-     * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
-     * Replaces {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}. The value of the property
-     * should be converted to a String via
-     * {@link android.content.ComponentName#flattenToString()}</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE}, optional</li></ul>
-     *
-     * <p> When device owner provisioning has completed, an intent of the type
-     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the
-     * device owner.
-     *
-     * <p>
-     * If provisioning fails, the device is factory reset.
-     */
-    public static final String MIME_TYPE_PROVISIONING_NFC_V2
-            = "application/com.android.managedprovisioning.v2";
-
     /**
      * Activity action: ask the user to add a new device administrator to the system.
      * The desired policy is the ComponentName of the policy in the
@@ -2701,28 +2574,28 @@
     /**
      * @hide
      * Sets the given package as the device owner.
-     * Same as {@link #setDeviceOwner(String, String)} but without setting a device owner name.
-     * @param packageName the package name of the application to be registered as the device owner.
+     * Same as {@link #setDeviceOwner(ComponentName, String)} but without setting a device owner name.
+     * @param who the component name to be registered as device owner.
      * @return whether the package was successfully registered as the device owner.
      * @throws IllegalArgumentException if the package name is null or invalid
      * @throws IllegalStateException If the preconditions mentioned are not met.
      */
-    public boolean setDeviceOwner(String packageName) {
-        return setDeviceOwner(packageName, null);
+    public boolean setDeviceOwner(ComponentName who) {
+        return setDeviceOwner(who, null);
     }
 
     /**
      * @hide
      */
-    public boolean setDeviceOwner(String packageName, int userId)  {
-        return setDeviceOwner(packageName, null, userId);
+    public boolean setDeviceOwner(ComponentName who, int userId)  {
+        return setDeviceOwner(who, null, userId);
     }
 
     /**
      * @hide
      */
-    public boolean setDeviceOwner(String packageName, String ownerName) {
-        return setDeviceOwner(packageName, ownerName, UserHandle.USER_SYSTEM);
+    public boolean setDeviceOwner(ComponentName who, String ownerName) {
+        return setDeviceOwner(who, ownerName, UserHandle.USER_SYSTEM);
     }
 
     /**
@@ -2733,18 +2606,18 @@
      * this method.
      * Calling this after the setup phase of the primary user has completed is allowed only if
      * the caller is the shell uid, and there are no additional users and no accounts.
-     * @param packageName the package name of the application to be registered as the device owner.
+     * @param who the component name to be registered as device owner.
      * @param ownerName the human readable name of the institution that owns this device.
      * @param userId ID of the user on which the device owner runs.
      * @return whether the package was successfully registered as the device owner.
      * @throws IllegalArgumentException if the package name is null or invalid
      * @throws IllegalStateException If the preconditions mentioned are not met.
      */
-    public boolean setDeviceOwner(String packageName, String ownerName, int userId)
+    public boolean setDeviceOwner(ComponentName who, String ownerName, int userId)
             throws IllegalArgumentException, IllegalStateException {
         if (mService != null) {
             try {
-                return mService.setDeviceOwner(packageName, ownerName, userId);
+                return mService.setDeviceOwner(who, ownerName, userId);
             } catch (RemoteException re) {
                 Log.w(TAG, "Failed to set device owner");
             }
@@ -2763,14 +2636,15 @@
      * the setup process.
      * @param packageName the package name of the app, to compare with the registered device owner
      * app, if any.
-     * @return whether or not the package is registered as the device owner app.
+     * @return whether or not the package is registered as the device owner app.  Note this method
+     * does *not* check weather the device owner is actually running on the current user.
      */
     public boolean isDeviceOwnerApp(String packageName) {
         if (mService != null) {
             try {
-                return mService.isDeviceOwner(packageName);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to check device owner");
+                return mService.isDeviceOwnerPackage(packageName);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
             }
         }
         return false;
@@ -2785,6 +2659,17 @@
     }
 
     /**
+     * Check whether a given component is registered as a device owner.
+     * Note this method does *not* check weather the device owner is actually running on the current
+     * user.
+     *
+     * @hide
+     */
+    public boolean isDeviceOwner(ComponentName who) {
+        return (who != null) && who.equals(getDeviceOwner());
+    }
+
+    /**
      * Clears the current device owner.  The caller must be the device owner.
      *
      * This function should be used cautiously as once it is called it cannot
@@ -2803,20 +2688,24 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Returns the device owner package name.  Note this method will still return the device owner
+     * package name even if it's running on a different user.
+     *
+     * @hide
+     */
     @SystemApi
     public String getDeviceOwner() {
-        if (mService != null) {
-            try {
-                return mService.getDeviceOwner();
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to get device owner");
-            }
-        }
-        return null;
+        final ComponentName componentName = getDeviceOwnerComponent();
+        return componentName == null ? null : componentName.getPackageName();
     }
 
-    /** @hide */
+    /**
+     * Returns the device owner name.  Note this method will still return the device owner
+     * name even if it's running on a different user.
+     *
+     * @hide
+     */
     public String getDeviceOwnerName() {
         if (mService != null) {
             try {
@@ -2829,135 +2718,44 @@
     }
 
     /**
+     * Returns the device owner component name.  Note this method will still return the device owner
+     * component name even if it's running on a different user.
+     *
      * @hide
-     * Sets the given component as the device initializer. The package must already be installed and
-     * set as an active device administrator, and there must not be an existing device initializer,
-     * for this call to succeed. This method can only be called by an app holding the
-     * MANAGE_DEVICE_ADMINS permission before the device is provisioned or by a device owner app. A
-     * device initializer app is granted device owner privileges during device initialization and
-     * profile owner privileges during secondary user initialization.
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
-     *              {@code null} if not called by the device owner.
-     * @param initializer Which {@link DeviceAdminReceiver} to make device initializer.
-     * @return whether the component was successfully registered as the device initializer.
-     * @throws IllegalArgumentException if the componentname is null or invalid
-     * @throws IllegalStateException if the caller is not device owner or the device has
-     *         already been provisioned or a device initializer already exists.
      */
-    public boolean setDeviceInitializer(@Nullable ComponentName admin,
-            @NonNull ComponentName initializer)
-            throws IllegalArgumentException, IllegalStateException {
+    public ComponentName getDeviceOwnerComponent() {
         if (mService != null) {
             try {
-                return mService.setDeviceInitializer(admin, initializer);
+                return mService.getDeviceOwner();
             } catch (RemoteException re) {
-                Log.w(TAG, "Failed to set device initializer");
+                Log.w(TAG, "Failed to get device owner");
             }
         }
-        return false;
+        return null;
     }
 
     /**
      * @hide
-     * Used to determine if a particular package has been registered as the device initializer.
-     *
-     * @param packageName the package name of the app, to compare with the registered device
-     *        initializer app, if any.
-     * @return whether or not the caller is registered as the device initializer app.
+     * @deprecated Do not use
+     * @removed
      */
-    public boolean isDeviceInitializerApp(String packageName) {
-        if (mService != null) {
-            try {
-                return mService.isDeviceInitializer(packageName);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to check device initializer");
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     * Removes the device initializer, so that it will not be invoked on user initialization for any
-     * subsequently created users. This method can be called by either the device owner or device
-     * initializer itself. The caller must be an active administrator.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     */
-    public void clearDeviceInitializerApp(@NonNull ComponentName admin) {
-        if (mService != null) {
-            try {
-                mService.clearDeviceInitializer(admin);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to clear device initializer");
-            }
-        }
-    }
-
-    /**
-     * @hide
-     * Gets the device initializer of the system.
-     *
-     * @return the package name of the device initializer.
-     */
+    @Deprecated
     @SystemApi
     public String getDeviceInitializerApp() {
-        if (mService != null) {
-            try {
-                return mService.getDeviceInitializer();
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to get device initializer");
-            }
-        }
         return null;
     }
 
     /**
      * @hide
-     * Gets the device initializer component of the system.
-     *
-     * @return the component name of the device initializer.
+     * @deprecated Do not use
+     * @removed
      */
+    @Deprecated
     @SystemApi
     public ComponentName getDeviceInitializerComponent() {
-        if (mService != null) {
-            try {
-                return mService.getDeviceInitializerComponent();
-            } catch (RemoteException re) {
-                Log.w(TAG, "Failed to get device initializer");
-            }
-        }
         return null;
     }
 
-
-    /**
-     * @hide
-     * Sets the enabled state of the user. A user should be enabled only once it is ready to
-     * be used.
-     *
-     * <p>Device initializer must call this method to mark the user as functional.
-     * Only the device initializer agent can call this.
-     *
-     * <p>When the user is enabled, if the device initializer is not also the device owner, the
-     * device initializer will no longer have elevated permissions to call methods in this class.
-     * Additionally, it will be removed as an active administrator and its
-     * {@link DeviceAdminReceiver} will be disabled.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @return whether the user is now enabled.
-     */
-    public boolean setUserEnabled(@NonNull ComponentName admin) {
-        if (mService != null) {
-            try {
-                return mService.setUserEnabled(admin);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed talking with device policy service", e);
-            }
-        }
-        return false;
-    }
-
     /**
      * @hide
      * @deprecated Use #ACTION_SET_PROFILE_OWNER
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 55a21c6..ccaa8cb 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -113,9 +113,9 @@
     void reportFailedPasswordAttempt(int userHandle);
     void reportSuccessfulPasswordAttempt(int userHandle);
 
-    boolean setDeviceOwner(String packageName, String ownerName, int userId);
-    boolean isDeviceOwner(String packageName);
-    String getDeviceOwner();
+    boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
+    boolean isDeviceOwnerPackage(String packageName);
+    ComponentName getDeviceOwner();
     String getDeviceOwnerName();
     void clearDeviceOwner(String packageName);
 
@@ -211,13 +211,6 @@
 
     boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
 
-    boolean setUserEnabled(in ComponentName who);
-    boolean isDeviceInitializer(String packageName);
-    void clearDeviceInitializer(in ComponentName who);
-    boolean setDeviceInitializer(in ComponentName who, in ComponentName initializer);
-    String getDeviceInitializer();
-    ComponentName getDeviceInitializerComponent();
-
     void setUserIcon(in ComponentName admin, in Bitmap icon);
 
     void setSystemUpdatePolicy(in ComponentName who, in SystemUpdatePolicy policy);
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/Intent.java b/core/java/android/content/Intent.java
index 670ca80..6740d43 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3168,6 +3168,13 @@
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_HOME = "android.intent.category.HOME";
     /**
+     * This is the home activity that is displayed when the device is finished setting up and ready
+     * for use.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN";
+    /**
      * This is the setup wizard activity, that is the first activity that is displayed
      * when the user sets up the device for the first time.
      * @hide
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..7ca39cb 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
@@ -671,6 +671,13 @@
      */
     public boolean resizeable;
 
+    /**
+     * Value indicating if the activity is supports picture-in-picture form of multi-window mode.
+     * See {@link android.R.attr#supportsPictureInPicture}.
+     * @hide
+     */
+    public boolean supportsPip;
+
     /** @hide */
     public static final int LOCK_TASK_LAUNCH_MODE_DEFAULT = 0;
     /** @hide */
@@ -702,7 +709,7 @@
      */
     public int lockTaskLaunchMode;
 
-    public InitialLayout initialLayout;
+    public Layout layout;
 
     public ActivityInfo() {
     }
@@ -723,8 +730,9 @@
         parentActivityName = orig.parentActivityName;
         maxRecents = orig.maxRecents;
         resizeable = orig.resizeable;
+        supportsPip = orig.supportsPip;
         lockTaskLaunchMode = orig.lockTaskLaunchMode;
-        initialLayout = orig.initialLayout;
+        layout = orig.layout;
     }
 
     /**
@@ -769,12 +777,12 @@
         if (uiOptions != 0) {
             pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions));
         }
-        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);
+        pw.println(prefix + "resizeable=" + resizeable + " supportsPip=" + supportsPip);
+        pw.println(prefix + "lockTaskLaunchMode=" + lockTaskLaunchModeToString(lockTaskLaunchMode));
+        if (layout != null) {
+            pw.println(prefix + "initialLayout=" + layout.width + "|"
+                    + layout.widthFraction + ", " + layout.height + "|"
+                    + layout.heightFraction + ", " + layout.gravity);
         }
         super.dumpBack(pw, prefix);
     }
@@ -806,14 +814,16 @@
         dest.writeInt(persistableMode);
         dest.writeInt(maxRecents);
         dest.writeInt(resizeable ? 1 : 0);
+        dest.writeInt(supportsPip ? 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);
         }
@@ -846,28 +856,31 @@
         persistableMode = source.readInt();
         maxRecents = source.readInt();
         resizeable = (source.readInt() == 1);
+        supportsPip = (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 +888,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 04b1a3b..5d73b06 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();
@@ -3201,15 +3217,15 @@
             }
 
             a.info.resizeable = sa.getBoolean(
-                    R.styleable.AndroidManifestActivity_resizeableActivity, false);
-            if (a.info.resizeable) {
-                // Fixed screen orientation isn't supported with resizeable activities.
-                a.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-            } else {
-                a.info.screenOrientation = sa.getInt(
-                        R.styleable.AndroidManifestActivity_screenOrientation,
-                        ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-            }
+                    R.styleable.AndroidManifestActivity_resizeableActivity,
+                    owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N);
+
+            a.info.supportsPip = a.info.resizeable ? sa.getBoolean(
+                    R.styleable.AndroidManifestActivity_supportsPictureInPicture, false) : false;
+
+            a.info.screenOrientation = sa.getInt(
+                    R.styleable.AndroidManifestActivity_screenOrientation,
+                    ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
 
             a.info.lockTaskLaunchMode =
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
@@ -3239,7 +3255,7 @@
                 outError[0] = "Heavy-weight applications can not have receivers in main process";
             }
         }
-        
+
         if (outError[0] != null) {
             return null;
         }
@@ -3285,8 +3301,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 + ":");
@@ -3319,41 +3335,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,
@@ -3391,10 +3410,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();
@@ -3435,7 +3454,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) {
@@ -3543,10 +3562,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();
@@ -3635,7 +3654,7 @@
                 return null;
             }
         }
-        
+
         if (cpname == null) {
             outError[0] = "<provider> does not include authorities attribute";
             return null;
@@ -3678,7 +3697,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);
@@ -3702,7 +3721,7 @@
                 if (str != null) {
                     pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
                 }
-                
+
                 sa.recycle();
 
                 if (pa != null) {
@@ -3749,7 +3768,7 @@
                 if (writePermission == null) {
                     writePermission = permission;
                 }
-                
+
                 boolean havePerm = false;
                 if (readPermission != null) {
                     readPermission = readPermission.intern();
@@ -3772,7 +3791,7 @@
                         return false;
                     }
                 }
-                
+
                 String path = sa.getNonConfigurationString(
                         com.android.internal.R.styleable.AndroidManifestPathPermission_path, 0);
                 if (path != null) {
@@ -3855,10 +3874,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();
@@ -3915,7 +3934,7 @@
                 return null;
             }
         }
-        
+
         int outerDepth = parser.getDepth();
         int type;
         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -3960,7 +3979,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;
@@ -4013,7 +4032,7 @@
         }
 
         name = name.intern();
-        
+
         TypedValue v = sa.peekValue(
                 com.android.internal.R.styleable.AndroidManifestMetaData_resource);
         if (v != null && v.resourceId != 0) {
@@ -4155,7 +4174,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);
 
@@ -4376,7 +4395,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;
 
@@ -4385,7 +4404,7 @@
 
         // The version name declared for this package.
         public String mVersionName;
-        
+
         // The shared user id that this package wants to use.
         public String mSharedUserId;
 
@@ -4607,7 +4626,7 @@
 
         ComponentName componentName;
         String componentShortName;
-        
+
         public Component(Package _owner) {
             owner = _owner;
             intents = null;
@@ -4639,7 +4658,7 @@
                 outInfo.icon = iconVal;
                 outInfo.nonLocalizedLabel = null;
             }
-            
+
             int logoVal = args.sa.getResourceId(args.logoRes, 0);
             if (logoVal != 0) {
                 outInfo.logo = logoVal;
@@ -4679,11 +4698,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);
         }
 
@@ -4694,7 +4713,7 @@
             componentName = clone.componentName;
             componentShortName = clone.componentShortName;
         }
-        
+
         public ComponentName getComponentName() {
             if (componentName != null) {
                 return componentName;
@@ -4719,7 +4738,7 @@
             componentShortName = null;
         }
     }
-    
+
     public final static class Permission extends Component<IntentInfo> {
         public final PermissionInfo info;
         public boolean tree;
@@ -4734,7 +4753,7 @@
             super(_owner);
             info = _info;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -4925,7 +4944,7 @@
             info = _info;
             info.applicationInfo = args.owner.applicationInfo;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -4979,7 +4998,7 @@
             info = _info;
             info.applicationInfo = args.owner.applicationInfo;
         }
-        
+
         public void setPackageName(String packageName) {
             super.setPackageName(packageName);
             info.packageName = packageName;
@@ -5022,7 +5041,7 @@
             info.applicationInfo = args.owner.applicationInfo;
             syncable = false;
         }
-        
+
         public Provider(Provider existingProvider) {
             super(existingProvider);
             this.info = existingProvider.info;
@@ -5073,7 +5092,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/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 20b0be1..8f45f72 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -46,7 +46,7 @@
  * provide substantially improved capabilities over the older camera
  * API. Applications that target the limited level devices will run unchanged on
  * the full-level devices; if your application requires a full-level device for
- * proper operation, declare the "android.hardware.camera2.full" feature in your
+ * proper operation, declare the "android.hardware.camera.level.full" feature in your
  * manifest.</p>
  *
  * @see CameraManager#openCamera
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/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 87c063f..97bd5d2 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -21,7 +21,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.util.Objects;
 
@@ -34,7 +33,7 @@
 public class DhcpResults extends StaticIpConfiguration {
     private static final String TAG = "DhcpResults";
 
-    public InetAddress serverAddress;
+    public Inet4Address serverAddress;
 
     /** Vendor specific information (from RFC 2132). */
     public String vendorInfo;
@@ -142,7 +141,7 @@
     private static void readFromParcel(DhcpResults dhcpResults, Parcel in) {
         StaticIpConfiguration.readFromParcel(dhcpResults, in);
         dhcpResults.leaseDuration = in.readInt();
-        dhcpResults.serverAddress = NetworkUtils.unparcelInetAddress(in);
+        dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in);
         dhcpResults.vendorInfo = in.readString();
     }
 
@@ -183,8 +182,8 @@
 
     public boolean setServerAddress(String addrString) {
         try {
-            serverAddress = NetworkUtils.numericToInetAddress(addrString);
-        } catch (IllegalArgumentException e) {
+            serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
+        } catch (IllegalArgumentException|ClassCastException e) {
             Log.e(TAG, "setServerAddress failed with addrString " + addrString);
             return true;
         }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index c4de4a2..6bfa2a4 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -667,7 +667,8 @@
      * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
      */
     private boolean hasIPv4AddressOnInterface(String iface) {
-        return (mIfaceName.equals(iface) && hasIPv4Address()) ||
+        // mIfaceName can be null.
+        return (Objects.equals(iface, mIfaceName) && hasIPv4Address()) ||
                 (iface != null && mStackedLinks.containsKey(iface) &&
                         mStackedLinks.get(iface).hasIPv4Address());
     }
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3f40484..a83e722 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -50,12 +50,13 @@
     public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2;
 
     /* RULE_* are not masks and they must be exclusive */
+    public static final int RULE_UNKNOWN = -1;
     /** All network traffic should be allowed. */
-    public static final int RULE_ALLOW_ALL = 0x0;
+    public static final int RULE_ALLOW_ALL = 0;
     /** Reject traffic on metered networks. */
-    public static final int RULE_REJECT_METERED = 0x1;
+    public static final int RULE_REJECT_METERED = 1;
     /** Reject traffic on all networks. */
-    public static final int RULE_REJECT_ALL = 0x2;
+    public static final int RULE_REJECT_ALL = 2;
 
     public static final int FIREWALL_RULE_DEFAULT = 0;
     public static final int FIREWALL_RULE_ALLOW = 1;
@@ -326,25 +327,4 @@
         // nothing found above; we can apply policy to UID
         return true;
     }
-
-    /** {@hide} */
-    public static void dumpPolicy(PrintWriter fout, int policy) {
-        fout.write("[");
-        if ((policy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
-            fout.write("REJECT_METERED_BACKGROUND");
-        }
-        fout.write("]");
-    }
-
-    /** {@hide} */
-    public static void dumpRules(PrintWriter fout, int rules) {
-        fout.write("[");
-        if ((rules & RULE_REJECT_METERED) != 0) {
-            fout.write("REJECT_METERED");
-        } else if ((rules & RULE_REJECT_ALL) != 0) {
-            fout.write("REJECT_ALL");
-        }
-        fout.write("]");
-    }
-
 }
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/Binder.java b/core/java/android/os/Binder.java
index c4501ba..1af2034 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 import android.util.Slog;
 import com.android.internal.util.FastPrintWriter;
+import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
@@ -342,11 +343,7 @@
                 try {
                     dump(fd.getFileDescriptor(), args);
                 } finally {
-                    try {
-                        fd.close();
-                    } catch (IOException e) {
-                        // swallowed, not propagated back to the caller
-                    }
+                    IoUtils.closeQuietly(fd);
                 }
             }
             // Write the StrictMode header.
@@ -356,6 +353,31 @@
                 StrictMode.clearGatheredViolations();
             }
             return true;
+        } else if (code == SHELL_COMMAND_TRANSACTION) {
+            ParcelFileDescriptor in = data.readFileDescriptor();
+            ParcelFileDescriptor out = data.readFileDescriptor();
+            ParcelFileDescriptor err = data.readFileDescriptor();
+            String[] args = data.readStringArray();
+            ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
+            try {
+                if (out != null) {
+                    shellCommand(in != null ? in.getFileDescriptor() : null,
+                            out.getFileDescriptor(),
+                            err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
+                            args, resultReceiver);
+                }
+            } finally {
+                IoUtils.closeQuietly(in);
+                IoUtils.closeQuietly(out);
+                IoUtils.closeQuietly(err);
+                // Write the StrictMode header.
+                if (reply != null) {
+                    reply.writeNoException();
+                } else {
+                    StrictMode.clearGatheredViolations();
+                }
+            }
+            return true;
         }
         return false;
     }
@@ -368,33 +390,37 @@
         FileOutputStream fout = new FileOutputStream(fd);
         PrintWriter pw = new FastPrintWriter(fout);
         try {
-            final String disabled;
-            synchronized (Binder.class) {
-                disabled = sDumpDisabled;
-            }
-            if (disabled == null) {
-                try {
-                    dump(fd, pw, args);
-                } catch (SecurityException e) {
-                    pw.println("Security exception: " + e.getMessage());
-                    throw e;
-                } catch (Throwable e) {
-                    // Unlike usual calls, in this case if an exception gets thrown
-                    // back to us we want to print it back in to the dump data, since
-                    // that is where the caller expects all interesting information to
-                    // go.
-                    pw.println();
-                    pw.println("Exception occurred while dumping:");
-                    e.printStackTrace(pw);
-                }
-            } else {
-                pw.println(sDumpDisabled);
-            }
+            doDump(fd, pw, args);
         } finally {
             pw.flush();
         }
     }
-    
+
+    void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final String disabled;
+        synchronized (Binder.class) {
+            disabled = sDumpDisabled;
+        }
+        if (disabled == null) {
+            try {
+                dump(fd, pw, args);
+            } catch (SecurityException e) {
+                pw.println("Security exception: " + e.getMessage());
+                throw e;
+            } catch (Throwable e) {
+                // Unlike usual calls, in this case if an exception gets thrown
+                // back to us we want to print it back in to the dump data, since
+                // that is where the caller expects all interesting information to
+                // go.
+                pw.println();
+                pw.println("Exception occurred while dumping:");
+                e.printStackTrace(pw);
+            }
+        } else {
+            pw.println(sDumpDisabled);
+        }
+    }
+
     /**
      * Like {@link #dump(FileDescriptor, String[])}, but ensures the target
      * executes asynchronously.
@@ -426,6 +452,34 @@
     }
 
     /**
+     * @param in The raw file descriptor that an input data stream can be read from.
+     * @param out The raw file descriptor that normal command messages should be written to.
+     * @param err The raw file descriptor that command error messages should be written to.
+     * @param args Command-line arguments.
+     * @param resultReceiver Called when the command has finished executing, with the result code.
+     * @throws RemoteException
+     * @hide
+     */
+    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        onShellCommand(in, out, err, args, resultReceiver);
+    }
+
+    /**
+     * Handle a call to {@link #shellCommand}.  The default implementation simply prints
+     * an error message.  Override and replace with your own.
+     * @hide
+     */
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        FileOutputStream fout = new FileOutputStream(err != null ? err : out);
+        PrintWriter pw = new FastPrintWriter(fout);
+        pw.println("No shell command implementation.");
+        pw.flush();
+        resultReceiver.send(0, null);
+    }
+
+    /**
      * Default implementation rewinds the parcels and calls onTransact.  On
      * the remote side, transact calls into the binder to do the IPC.
      */
@@ -590,6 +644,24 @@
         }
     }
 
+    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeFileDescriptor(in);
+        data.writeFileDescriptor(out);
+        data.writeFileDescriptor(err);
+        data.writeStringArray(args);
+        resultReceiver.writeToParcel(data, 0);
+        try {
+            transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
+            reply.readException();
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
     BinderProxy() {
         mSelf = new WeakReference(this);
     }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 2374899..f7c8662 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -577,7 +577,7 @@
         public static final int KITKAT = 19;
 
         /**
-         * Android 4.4W: KitKat for watches, snacks on the run.
+         * June 2014: Android 4.4W. KitKat for watches, snacks on the run.
          *
          * <p>Applications targeting this or a later release will get these
          * new changes in behavior:</p>
@@ -595,7 +595,7 @@
         public static final int L = 21;
 
         /**
-         * Lollipop.  A flat one with beautiful shadows.  But still tasty.
+         * November 2014: Lollipop.  A flat one with beautiful shadows.  But still tasty.
          *
          * <p>Applications targeting this or a later release will get these
          * new changes in behavior:</p>
@@ -626,14 +626,45 @@
         public static final int LOLLIPOP = 21;
 
         /**
-         * Lollipop with an extra sugar coating on the outside!
+         * March 2015: Lollipop with an extra sugar coating on the outside!
          */
         public static final int LOLLIPOP_MR1 = 22;
 
         /**
-         * M comes after L.
+         * M is for Marshmallow!
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> Runtime permissions.  Dangerous permissions are no longer granted at
+         * install time, but must be requested by the application at runtime through
+         * {@link android.app.Activity#requestPermissions}.</li>
+         * <li> Bluetooth and Wi-Fi scanning now requires holding the location permission.</li>
+         * <li> {@link android.app.AlarmManager#setTimeZone AlarmManager.setTimeZone} will fail if
+         * the given timezone is non-Olson.</li>
+         * <li> Activity transitions will only return shared
+         * elements mapped in the returned view hierarchy back to the calling activity.</li>
+         * <li> {@link android.view.View} allows a number of behaviors that may break
+         * existing apps: Canvas throws an exception if restore() is called too many times,
+         * widgets may return a hint size when returning UNSPECIFIED measure specs, and it
+         * will respect the attributes {@link android.R.attr#foreground},
+         * {@link android.R.attr#foregroundGravity}, {@link android.R.attr#foregroundTint}, and
+         * {@link android.R.attr#foregroundTintMode}.</li>
+         * <li> {@link android.view.MotionEvent#getButtonState MotionEvent.getButtonState}
+         * will no longer report {@link android.view.MotionEvent#BUTTON_PRIMARY}
+         * and {@link android.view.MotionEvent#BUTTON_SECONDARY} as synonyms for
+         * {@link android.view.MotionEvent#BUTTON_STYLUS_PRIMARY} and
+         * {@link android.view.MotionEvent#BUTTON_STYLUS_SECONDARY}.</li>
+         * <li> {@link android.widget.ScrollView} now respects the layout param margins
+         * when measuring.</li>
+         * </ul>
          */
         public static final int M = 23;
+
+        /**
+         * N is for ¯\_(ツ)_/¯.
+         */
+        public static final int N = CUR_DEVELOPMENT;
     }
 
     /** The type of build, like "user" or "eng". */
@@ -691,6 +722,9 @@
      * @hide
      */
     public static boolean isBuildConsistent() {
+        // Don't care on eng builds.  Incremental build may trigger false negative.
+        if ("eng".equals(TYPE)) return true;
+
         final String system = SystemProperties.get("ro.build.fingerprint");
         final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
         final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 97b85e2..fdd34f5 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -471,7 +471,7 @@
        *     </tbody>
        * </table>
        */
-        public String getMemoryStat(String statName) {
+       public String getMemoryStat(String statName) {
             switch(statName) {
                 case "summary.java-heap":
                     return Integer.toString(getSummaryJavaHeap());
@@ -1538,7 +1538,13 @@
 
     /**
      * Retrieves information about this processes memory usages. This information is broken down by
-     * how much is in use by dalivk, the native heap, and everything else.
+     * how much is in use by dalvik, the native heap, and everything else.
+     *
+     * <p><b>Note:</b> this method directly retrieves memory information for the give process
+     * from low-level data available to it.  It may not be able to retrieve information about
+     * some protected allocations, such as graphics.  If you want to be sure you can see
+     * all information about allocations by the process, use instead
+     * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p>
      */
     public static native void getMemoryInfo(MemoryInfo memoryInfo);
 
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/IBinder.java b/core/java/android/os/IBinder.java
index 2c21d13..0fa8750 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -103,6 +103,12 @@
     int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
     
     /**
+     * IBinder protocol transaction code: execute a shell command.
+     * @hide
+     */
+    int SHELL_COMMAND_TRANSACTION = ('_'<<24)|('C'<<16)|('M'<<8)|'D';
+
+    /**
      * IBinder protocol transaction code: interrogate the recipient side
      * of the transaction for its canonical interface descriptor.
      */
@@ -187,7 +193,7 @@
      * the transact() method.
      */
     public IInterface queryLocalInterface(String descriptor);
-    
+
     /**
      * Print the object's state into the given stream.
      * 
@@ -195,7 +201,7 @@
      * @param args additional arguments to the dump request.
      */
     public void dump(FileDescriptor fd, String[] args) throws RemoteException;
-    
+
     /**
      * Like {@link #dump(FileDescriptor, String[])} but always executes
      * asynchronously.  If the object is local, a new thread is created
@@ -207,6 +213,20 @@
     public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
 
     /**
+     * Execute a shell command on this object.  This may be performed asynchrously from the caller;
+     * the implementation must always call resultReceiver when finished.
+     *
+     * @param in The raw file descriptor that an input data stream can be read from.
+     * @param out The raw file descriptor that normal command messages should be written to.
+     * @param err The raw file descriptor that command error messages should be written to.
+     * @param args Command-line arguments.
+     * @param resultReceiver Called when the command has finished executing, with the result code.
+     * @hide
+     */
+    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException;
+
+    /**
      * Perform a generic operation with the object.
      * 
      * @param code The action to perform.  This should
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/ShellCommand.java b/core/java/android/os/ShellCommand.java
new file mode 100644
index 0000000..d64273a
--- /dev/null
+++ b/core/java/android/os/ShellCommand.java
@@ -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.
+ */
+
+package android.os;
+
+import android.util.Slog;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+
+/**
+ * @hide
+ */
+public abstract class ShellCommand {
+    static final String TAG = "ShellCommand";
+    static final boolean DEBUG = false;
+
+    private Binder mTarget;
+    private FileDescriptor mIn;
+    private FileDescriptor mOut;
+    private FileDescriptor mErr;
+    private String[] mArgs;
+    private ResultReceiver mResultReceiver;
+
+    private String mCmd;
+    private int mArgPos;
+    private String mCurArgData;
+
+    private FastPrintWriter mOutPrintWriter;
+    private FastPrintWriter mErrPrintWriter;
+
+    public void exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) {
+        mTarget = target;
+        mIn = in;
+        mOut = out;
+        mErr = err;
+        mArgs = args;
+        mResultReceiver = resultReceiver;
+        mCmd = args != null && args.length > 0 ? args[0] : null;
+        mArgPos = 1;
+        mCurArgData = null;
+        mOutPrintWriter = null;
+        mErrPrintWriter = null;
+
+        if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+        int res = -1;
+        try {
+            res = onCommand(mCmd);
+            if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
+        } catch (SecurityException e) {
+            PrintWriter eout = getErrPrintWriter();
+            eout.println("Security exception: " + e.getMessage());
+            eout.println();
+            e.printStackTrace(eout);
+        } catch (Throwable e) {
+            // Unlike usual calls, in this case if an exception gets thrown
+            // back to us we want to print it back in to the dump data, since
+            // that is where the caller expects all interesting information to
+            // go.
+            PrintWriter eout = getErrPrintWriter();
+            eout.println();
+            eout.println("Exception occurred while dumping:");
+            e.printStackTrace(eout);
+        } finally {
+            if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
+            if (mOutPrintWriter != null) {
+                mOutPrintWriter.flush();
+            }
+            if (mErrPrintWriter != null) {
+                mErrPrintWriter.flush();
+            }
+            if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
+            mResultReceiver.send(res, null);
+        }
+        if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
+    }
+
+    public PrintWriter getOutPrintWriter() {
+        if (mOutPrintWriter == null) {
+            FileOutputStream fout = new FileOutputStream(mOut);
+            mOutPrintWriter = new FastPrintWriter(fout);
+        }
+        return mOutPrintWriter;
+    }
+
+    public PrintWriter getErrPrintWriter() {
+        if (mErr == null) {
+            return getOutPrintWriter();
+        }
+        if (mErrPrintWriter == null) {
+            FileOutputStream fout = new FileOutputStream(mErr);
+            mErrPrintWriter = new FastPrintWriter(fout);
+        }
+        return mErrPrintWriter;
+    }
+
+    /**
+     * Return the next option on the command line -- that is an argument that
+     * starts with '-'.  If the next argument is not an option, null is returned.
+     */
+    public String getNextOption() {
+        if (mCurArgData != null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
+        }
+        if (mArgPos >= mArgs.length) {
+            return null;
+        }
+        String arg = mArgs[mArgPos];
+        if (!arg.startsWith("-")) {
+            return null;
+        }
+        mArgPos++;
+        if (arg.equals("--")) {
+            return null;
+        }
+        if (arg.length() > 1 && arg.charAt(1) != '-') {
+            if (arg.length() > 2) {
+                mCurArgData = arg.substring(2);
+                return arg.substring(0, 2);
+            } else {
+                mCurArgData = null;
+                return arg;
+            }
+        }
+        mCurArgData = null;
+        return arg;
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, return null.
+     */
+    public String getNextArg() {
+        if (mCurArgData != null) {
+            String arg = mCurArgData;
+            mCurArgData = null;
+            return arg;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos++];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, throws an IllegalArgumentException to report this to the user.
+     */
+    public String getNextArgRequired() {
+        String arg = getNextArg();
+        if (arg == null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
+        }
+        return arg;
+    }
+
+    public int handleDefaultCommands(String cmd) {
+        if ("dump".equals(cmd)) {
+            String[] newArgs = new String[mArgs.length-1];
+            System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1);
+            mTarget.doDump(mOut, getOutPrintWriter(), newArgs);
+            return 0;
+        } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
+            onHelp();
+        } else {
+            getOutPrintWriter().println("Unknown command: " + cmd);
+        }
+        return -1;
+    }
+
+    public abstract int onCommand(String cmd);
+
+    public abstract void onHelp();
+}
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/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 66642de..db04c71 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -214,6 +214,9 @@
 
     @Override
     public void onDestroyView() {
+        if (mList != null) {
+            mList.setOnKeyListener(null);
+        }
         mList = null;
         mHandler.removeCallbacks(mRequestFocus);
         mHandler.removeMessages(MSG_BIND_PREFERENCES);
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index d6e9e61f..13c3661 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -148,16 +148,15 @@
             }
         }
 
-        int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
-        if (insertionIndex < 0) {
-            insertionIndex = insertionIndex * -1 - 1;
-        }
-
         if (!onPrepareAddPreference(preference)) {
             return false;
         }
 
         synchronized(this) {
+            int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
+            if (insertionIndex < 0) {
+                insertionIndex = insertionIndex * -1 - 1;
+            }
             mPreferenceList.add(insertionIndex, preference);
         }
 
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/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 2a8fb2c..c679eda 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -35,8 +35,7 @@
  * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
  * able to create and update conditions for this service to monitor, include the
- * {@link #META_DATA_RULE_TYPE}, {@link #META_DATA_DEFAULT_CONDITION_ID}, and
- * {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
+ * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
  * <pre>
  * &lt;service android:name=".MyConditionProvider"
  *          android:label="&#64;string/service_name"
@@ -49,10 +48,6 @@
  *               android:value="@string/my_condition_rule">
  *           &lt;/meta-data>
  *           &lt;meta-data
- *               android:name="android.service.zen.automatic.defaultConditionId"
- *               android:value="condition://com.my.package/mycondition">
- *           &lt;/meta-data>
- *           &lt;meta-data
  *               android:name="android.service.zen.automatic.configurationActivity"
  *               android:value="com.my.package/.MyConditionConfigurationActivity">
  *           &lt;/meta-data>
@@ -82,13 +77,6 @@
     public static final String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
 
     /**
-     * The name of the {@code meta-data} tag containing a default Condition {@link Uri} that can
-     * be parsed by this service.
-     */
-    public static final String META_DATA_DEFAULT_CONDITION_ID =
-            "android.service.zen.automatic.defaultConditionId";
-
-    /**
      * The name of the {@code meta-data} tag containing the {@link ComponentName} of an activity
      * that allows users to configure the conditions provided by this service.
      */
@@ -96,17 +84,9 @@
             "android.service.zen.automatic.configurationActivity";
 
     /**
-     * A condition {@link Uri} extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}. If the
-     * condition Uri is modified by that activity, it must be included in the result Intent extras
-     * in order to be persisted.
+     * A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
      */
-    public static final String EXTRA_CONDITION_ID = "android.content.automatic.conditionId";
-
-    /**
-     * A String rule name extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}. This extra is
-     * informative only, and will be ignored if included in the result Intent extras.
-     */
-    public static final String EXTRA_RULE_NAME = "android.content.automatic.ruleName";
+    public static final String EXTRA_RULE_ID = "android.content.automatic.ruleId";
 
     /**
      * Called when this service is connected.
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 4de903e..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;
@@ -68,6 +69,7 @@
     public static final int[] MINUTE_BUCKETS = generateMinuteBuckets();
     private static final int SECONDS_MS = 1000;
     private static final int MINUTES_MS = 60 * SECONDS_MS;
+    private static final int DAY_MINUTES = 24 * 60;
     private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
 
     private static final boolean DEFAULT_ALLOW_CALLS = true;
@@ -110,6 +112,7 @@
     private static final String RULE_ATT_COMPONENT = "component";
     private static final String RULE_ATT_ZEN = "zen";
     private static final String RULE_ATT_CONDITION_ID = "conditionId";
+    private static final String RULE_ATT_CREATION_TIME = "creationTime";
 
     public boolean allowCalls = DEFAULT_ALLOW_CALLS;
     public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
@@ -415,6 +418,7 @@
                     final String id = parser.getAttributeValue(null, RULE_ATT_ID);
                     final ZenRule automaticRule = readRuleXml(parser);
                     if (id != null && automaticRule != null) {
+                        automaticRule.id = id;
                         rt.automaticRules.put(id, automaticRule);
                     }
                 }
@@ -468,6 +472,7 @@
         }
         rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
         rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+        rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
         rt.condition = readConditionXml(parser);
         return rt;
     }
@@ -485,6 +490,7 @@
         if (rule.conditionId != null) {
             out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
         }
+        out.attribute(null, RULE_ATT_CREATION_TIME, Long.toString(rule.creationTime));
         if (rule.condition != null) {
             writeConditionXml(rule.condition, out);
         }
@@ -552,12 +558,9 @@
         return Uri.parse(val);
     }
 
-    public ArraySet<String> getAutomaticRuleNames() {
-        final ArraySet<String> rt = new ArraySet<String>();
-        for (int i = 0; i < automaticRules.size(); i++) {
-            rt.add(automaticRules.valueAt(i).name);
-        }
-        return rt;
+    private static long safeLong(XmlPullParser parser, String att, long defValue) {
+        final String val = parser.getAttributeValue(null, att);
+        return tryParseLong(val, defValue);
     }
 
     @Override
@@ -652,40 +655,68 @@
             boolean shortVersion) {
         final long now = System.currentTimeMillis();
         final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
-        return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle,
-                shortVersion);
+        return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion);
     }
 
-    public static Condition toTimeCondition(Context context, long time, int minutes, long now,
+    public static Condition toTimeCondition(Context context, long time, int minutes,
             int userHandle, boolean shortVersion) {
-        final int num, summaryResId, line1ResId;
+        final int num;
+        String summary, line1, line2;
+        final CharSequence formattedTime = getFormattedTime(context, time, userHandle);
+        final Resources res = context.getResources();
         if (minutes < 60) {
             // display as minutes
             num = minutes;
-            summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
+            int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
                     : R.plurals.zen_mode_duration_minutes_summary;
-            line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
+            summary = res.getQuantityString(summaryResId, num, num, formattedTime);
+            int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
                     : R.plurals.zen_mode_duration_minutes;
-        } else {
+            line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+            line2 = res.getString(R.string.zen_mode_until, formattedTime);
+        } else if (minutes < DAY_MINUTES) {
             // display as hours
             num =  Math.round(minutes / 60f);
-            summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
+            int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
                     : R.plurals.zen_mode_duration_hours_summary;
-            line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
+            summary = res.getQuantityString(summaryResId, num, num, formattedTime);
+            int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
                     : R.plurals.zen_mode_duration_hours;
+            line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+            line2 = res.getString(R.string.zen_mode_until, formattedTime);
+        } else {
+            // display as day/time
+            summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
         }
-        final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma";
-        final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
-        final CharSequence formattedTime = DateFormat.format(pattern, time);
-        final Resources res = context.getResources();
-        final String summary = res.getQuantityString(summaryResId, num, num, formattedTime);
-        final String line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
-        final String line2 = res.getString(R.string.zen_mode_until, formattedTime);
         final Uri id = toCountdownConditionId(time);
         return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
                 Condition.FLAG_RELEVANT_NOW);
     }
 
+    public static Condition toNextAlarmCondition(Context context, long now, long alarm,
+            int 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);
+        return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE,
+                Condition.FLAG_RELEVANT_NOW);
+    }
+
+    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);
+    }
+
     // ==== Built-in system conditions ====
 
     public static final String SYSTEM_AUTHORITY = "android";
@@ -887,15 +918,10 @@
         return Global.isValidZenMode(rt) ? rt : defValue;
     }
 
-    public String newRuleId() {
+    public static String newRuleId() {
         return UUID.randomUUID().toString().replace("-", "");
     }
 
-    public static String getConditionLine1(Context context, ZenModeConfig config,
-            int userHandle, boolean shortVersion) {
-        return getConditionLine(context, config, userHandle, true /*useLine1*/, shortVersion);
-    }
-
     public static String getConditionSummary(Context context, ZenModeConfig config,
             int userHandle, boolean shortVersion) {
         return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion);
@@ -914,8 +940,8 @@
             if (time > 0) {
                 final long now = System.currentTimeMillis();
                 final long span = time - now;
-                c = toTimeCondition(context,
-                        time, Math.round(span / (float) MINUTES_MS), now, userHandle, shortVersion);
+                c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS),
+                        userHandle, shortVersion);
             }
             final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
             return TextUtils.isEmpty(rt) ? "" : rt;
@@ -943,6 +969,8 @@
         public Uri conditionId;          // required for automatic
         public Condition condition;      // optional
         public ComponentName component;  // optional
+        public String id;                // required for automatic (unique)
+        public long creationTime;        // required for automatic
 
         public ZenRule() { }
 
@@ -956,6 +984,10 @@
             conditionId = source.readParcelable(null);
             condition = source.readParcelable(null);
             component = source.readParcelable(null);
+            if (source.readInt() == 1) {
+                id = source.readString();
+            }
+            creationTime = source.readLong();
         }
 
         @Override
@@ -977,6 +1009,13 @@
             dest.writeParcelable(conditionId, 0);
             dest.writeParcelable(condition, 0);
             dest.writeParcelable(component, 0);
+            if (id != null) {
+                dest.writeInt(1);
+                dest.writeString(id);
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeLong(creationTime);
         }
 
         @Override
@@ -989,6 +1028,8 @@
                     .append(",conditionId=").append(conditionId)
                     .append(",condition=").append(condition)
                     .append(",component=").append(component)
+                    .append(",id=").append(id)
+                    .append(",creationTime=").append(creationTime)
                     .append(']').toString();
         }
 
@@ -1029,6 +1070,12 @@
             if (!Objects.equals(component, to.component)) {
                 d.addLine(item, "component", component, to.component);
             }
+            if (!Objects.equals(id, to.id)) {
+                d.addLine(item, "id", id, to.id);
+            }
+            if (creationTime == to.creationTime) {
+                d.addLine(item, "creationTime", creationTime, to.creationTime);
+            }
         }
 
         @Override
@@ -1042,13 +1089,15 @@
                     && other.zenMode == zenMode
                     && Objects.equals(other.conditionId, conditionId)
                     && Objects.equals(other.condition, condition)
-                    && Objects.equals(other.component, component);
+                    && Objects.equals(other.component, component)
+                    && Objects.equals(other.id, id)
+                    && other.creationTime == creationTime;
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
-                    component);
+                    component, id, creationTime);
         }
 
         public boolean isAutomaticActive() {
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/BoringLayout.java b/core/java/android/text/BoringLayout.java
index aa8b71c..fd9188b 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -364,6 +364,11 @@
     }
 
     @Override
+    public float getLineWidth(int line) {
+        return (line == 0 ? mMax : 0);
+    }
+
+    @Override
     public final Directions getLineDirections(int line) {
         return Layout.DIRS_ALL_LEFT_TO_RIGHT;
     }
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/MathUtils.java b/core/java/android/util/MathUtils.java
index acca3ed..32aac13 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -156,6 +156,27 @@
         return start + (stop - start) * amount;
     }
 
+    /**
+     * Returns an interpolated angle in degrees between a set of start and end
+     * angles.
+     * <p>
+     * Unlike {@link #lerp(float, float, float)}, the direction and distance of
+     * travel is determined by the shortest angle between the start and end
+     * angles. For example, if the starting angle is 0 and the ending angle is
+     * 350, then the interpolated angle will be in the range [0,-10] rather
+     * than [0,350].
+     *
+     * @param start the starting angle in degrees
+     * @param end the ending angle in degrees
+     * @param amount the position between start and end in the range [0,1]
+     *               where 0 is the starting angle and 1 is the ending angle
+     * @return the interpolated angle in degrees
+     */
+    public static float lerpDeg(float start, float end, float amount) {
+        final float minAngle = (((end - start) + 180) % 360) - 180;
+        return minAngle * amount + start;
+    }
+
     public static float norm(float start, float stop, float value) {
         return (value - start) / (stop - start);
     }
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/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index c1eb80d..37d6757 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -343,15 +343,15 @@
         int seconds = (int) Math.floor(duration / 1000);
         int days = 0, hours = 0, minutes = 0;
 
-        if (seconds > SECONDS_PER_DAY) {
+        if (seconds >= SECONDS_PER_DAY) {
             days = seconds / SECONDS_PER_DAY;
             seconds -= days * SECONDS_PER_DAY;
         }
-        if (seconds > SECONDS_PER_HOUR) {
+        if (seconds >= SECONDS_PER_HOUR) {
             hours = seconds / SECONDS_PER_HOUR;
             seconds -= hours * SECONDS_PER_HOUR;
         }
-        if (seconds > SECONDS_PER_MINUTE) {
+        if (seconds >= SECONDS_PER_MINUTE) {
             minutes = seconds / SECONDS_PER_MINUTE;
             seconds -= minutes * SECONDS_PER_MINUTE;
         }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index acad496..cc4bcb6 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -80,20 +80,6 @@
             int localValue, int localChanges);
 
     /**
-     * The window is beginning to animate. The application should stop drawing frames until the
-     * window is not animating anymore, indicated by being called {@link #windowEndAnimating}.
-     *
-     * @param remainingFrameCount how many frames the app might still draw before stopping drawing;
-     *                            pass -1 to let it continue drawing
-     */
-    void onAnimationStarted(int remainingFrameCount);
-
-    /**
-     * The window has ended animating. See {@link #onAnimationStarted}.
-     */
-    void onAnimationStopped();
-
-    /**
      * Called for non-application windows when the enter animation has completed.
      */
     void dispatchWindowShown();
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/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 2a3756d..7747580 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -336,9 +336,6 @@
         return mUnscaledDuration;
     }
 
-    /**
-     * @hide
-     */
     @Override
     public long getTotalDuration() {
         return mUnscaledDuration + mUnscaledStartDelay;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 7d48a9a..db68c29 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -447,10 +447,11 @@
         final boolean formatChanged = mFormat != mRequestedFormat;
         final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
         final boolean visibleChanged = mVisible != mRequestedVisible;
+        final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
 
         if (force || creating || formatChanged || sizeChanged || visibleChanged
             || mLeft != mLocation[0] || mTop != mLocation[1]
-            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
+            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
 
             if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2c7a436..fa86c74 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2408,7 +2408,13 @@
      *                            1      PFLAG3_APPLYING_INSETS
      *                           1       PFLAG3_FITTING_SYSTEM_WINDOWS
      *                          1        PFLAG3_NESTED_SCROLLING_ENABLED
-     *                         1         PFLAG3_ASSIST_BLOCKED
+     *                         1         PFLAG3_SCROLL_INDICATOR_TOP
+     *                        1          PFLAG3_SCROLL_INDICATOR_BOTTOM
+     *                       1           PFLAG3_SCROLL_INDICATOR_LEFT
+     *                      1            PFLAG3_SCROLL_INDICATOR_RIGHT
+     *                     1             PFLAG3_SCROLL_INDICATOR_START
+     *                    1              PFLAG3_SCROLL_INDICATOR_END
+     *                   1               PFLAG3_ASSIST_BLOCKED
      * |-------|-------|-------|-------|
      */
 
@@ -2602,7 +2608,7 @@
      * <p>Indicates that we are allowing {@link ViewStructure} to traverse
      * into this view.<p>
      */
-    static final int PFLAG3_ASSIST_BLOCKED = 0x100;
+    static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
 
     /**
      * Always allow a user to over-scroll this view, provided it is a
@@ -15379,11 +15385,11 @@
 
     private void resetDisplayList() {
         if (mRenderNode.isValid()) {
-            mRenderNode.destroyDisplayListData();
+            mRenderNode.discardDisplayList();
         }
 
         if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
-            mBackgroundRenderNode.destroyDisplayListData();
+            mBackgroundRenderNode.discardDisplayList();
         }
     }
 
@@ -20989,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 7cf23e7..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;
@@ -88,6 +89,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
 import java.util.HashSet;
 
 /**
@@ -141,10 +143,10 @@
 
     static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
     static boolean sFirstDrawComplete = false;
-    static final ArrayList<WindowCallbacks> sWindowCallbacks = new ArrayList();
 
     static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
 
+    final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList();
     final Context mContext;
     final IWindowSession mWindowSession;
     final Display mDisplay;
@@ -238,11 +240,8 @@
     boolean mNewSurfaceNeeded;
     boolean mHasHadWindowFocus;
     boolean mLastWasImTarget;
-    boolean mWindowsAnimating;
-    boolean mDrawDuringWindowsAnimating;
+    CountDownLatch mWindowDrawCountDown;
 
-    /** How many frames the app is still allowed to draw when a window animation is happening. */
-    private int mRemainingFrameCount;
     boolean mIsDrawing;
     int mLastSystemUiVisibility;
     int mClientWindowLayoutFlags;
@@ -326,6 +325,8 @@
     private long mFpsPrevTime = -1;
     private int mFpsNumFrames;
 
+    private int mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -424,22 +425,28 @@
         }
     }
 
-    public static void addWindowCallbacks(WindowCallbacks callback) {
+    public void addWindowCallbacks(WindowCallbacks callback) {
         if (USE_MT_RENDERER) {
-            synchronized (sWindowCallbacks) {
-                sWindowCallbacks.add(callback);
+            synchronized (mWindowCallbacks) {
+                mWindowCallbacks.add(callback);
             }
         }
     }
 
-    public static void removeWindowCallbacks(WindowCallbacks callback) {
+    public void removeWindowCallbacks(WindowCallbacks callback) {
         if (USE_MT_RENDERER) {
-            synchronized (sWindowCallbacks) {
-                sWindowCallbacks.remove(callback);
+            synchronized (mWindowCallbacks) {
+                mWindowCallbacks.remove(callback);
             }
         }
     }
 
+    public void reportDrawFinish() {
+        if (mWindowDrawCountDown != null) {
+            mWindowDrawCountDown.countDown();
+        }
+    }
+
     // FIXME for perf testing only
     private boolean mProfile = false;
 
@@ -1978,8 +1985,6 @@
             }
         }
 
-        boolean skipDraw = false;
-
         if (mFirst) {
             // handle first focus request
             if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()="
@@ -1994,11 +1999,6 @@
                             + mView.findFocus());
                 }
             }
-        } else if (mWindowsAnimating) {
-            if (mRemainingFrameCount <= 0) {
-                skipDraw = true;
-            }
-            mRemainingFrameCount--;
         }
 
         final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
@@ -2043,16 +2043,14 @@
         boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
 
         if (!cancelDraw && !newSurface) {
-            if (!skipDraw || mReportNextDraw) {
-                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
-                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
-                        mPendingTransitions.get(i).startChangingAnimations();
-                    }
-                    mPendingTransitions.clear();
+            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                    mPendingTransitions.get(i).startChangingAnimations();
                 }
-
-                performDraw();
+                mPendingTransitions.clear();
             }
+
+            performDraw();
         } else {
             if (isViewVisible) {
                 // Try again
@@ -2432,6 +2430,17 @@
 
         if (mReportNextDraw) {
             mReportNextDraw = false;
+
+            // if we're using multi-thread renderer, wait for the window frame draws
+            if (mWindowDrawCountDown != null) {
+                try {
+                    mWindowDrawCountDown.await();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Window redraw count down interruped!");
+                }
+                mWindowDrawCountDown = null;
+            }
+
             if (mAttachInfo.mHardwareRenderer != null) {
                 mAttachInfo.mHardwareRenderer.fence();
             }
@@ -2579,7 +2588,15 @@
 
                 dirty.setEmpty();
 
+                // Stage the content drawn size now. It will be transferred to the renderer
+                // shortly before the draw commands get send to the renderer.
+                final boolean updated = updateContentDrawBounds();
+
                 mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
+
+                if (updated) {
+                    requestDrawWindow();
+                }
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
                 // wrong (an invalidate posted right before we destroyed the hardware surface
@@ -2787,16 +2804,6 @@
         return mAttachInfo.mAccessibilityFocusDrawable;
     }
 
-    /**
-     * @hide
-     */
-    public void setDrawDuringWindowsAnimating(boolean value) {
-        mDrawDuringWindowsAnimating = value;
-        if (value) {
-            handleDispatchWindowAnimationStopped();
-        }
-    }
-
     boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
         final Rect ci = mAttachInfo.mContentInsets;
         final Rect vi = mAttachInfo.mVisibleInsets;
@@ -3160,8 +3167,6 @@
     private final static int MSG_WINDOW_MOVED = 23;
     private final static int MSG_SYNTHESIZE_INPUT_EVENT = 24;
     private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
-    private final static int MSG_DISPATCH_WINDOW_ANIMATION_STOPPED = 26;
-    private final static int MSG_DISPATCH_WINDOW_ANIMATION_STARTED = 27;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -3205,10 +3210,6 @@
                     return "MSG_PROCESS_INPUT_EVENTS";
                 case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
                     return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
-                case MSG_DISPATCH_WINDOW_ANIMATION_STARTED:
-                    return "MSG_DISPATCH_WINDOW_ANIMATION_STARTED";
-                case MSG_DISPATCH_WINDOW_ANIMATION_STOPPED:
-                    return "MSG_DISPATCH_WINDOW_ANIMATION_STOPPED";
                 case MSG_WINDOW_MOVED:
                     return "MSG_WINDOW_MOVED";
                 case MSG_SYNTHESIZE_INPUT_EVENT:
@@ -3429,13 +3430,6 @@
             case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
                 setAccessibilityFocus(null, null);
             } break;
-            case MSG_DISPATCH_WINDOW_ANIMATION_STARTED: {
-                int remainingFrameCount = msg.arg1;
-                handleDispatchWindowAnimationStarted(remainingFrameCount);
-            } break;
-            case MSG_DISPATCH_WINDOW_ANIMATION_STOPPED: {
-                handleDispatchWindowAnimationStopped();
-            } break;
             case MSG_INVALIDATE_WORLD: {
                 if (mView != null) {
                     invalidateWorld(mView);
@@ -4048,9 +4042,6 @@
             if (q.mEvent instanceof KeyEvent) {
                 return processKeyEvent(q);
             } else {
-                // If delivering a new non-key event, make sure the window is
-                // now allowed to start updating.
-                handleDispatchWindowAnimationStopped();
                 final int source = q.mEvent.getSource();
                 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                     return processPointerEvent(q);
@@ -4077,12 +4068,6 @@
         private int processKeyEvent(QueuedInputEvent q) {
             final KeyEvent event = (KeyEvent)q.mEvent;
 
-            if (event.getAction() != KeyEvent.ACTION_UP) {
-                // If delivering a new key event, make sure the window is
-                // now allowed to start updating.
-                handleDispatchWindowAnimationStopped();
-            }
-
             // Deliver the key to the view hierarchy.
             if (mView.dispatchKeyEvent(event)) {
                 return FINISH_HANDLED;
@@ -4186,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) {
@@ -5297,22 +5309,6 @@
         }
     }
 
-    public void handleDispatchWindowAnimationStarted(int remainingFrameCount) {
-        if (!mDrawDuringWindowsAnimating && remainingFrameCount != -1) {
-            mRemainingFrameCount = remainingFrameCount;
-            mWindowsAnimating = true;
-        }
-    }
-
-    public void handleDispatchWindowAnimationStopped() {
-        if (mWindowsAnimating) {
-            mWindowsAnimating = false;
-            if (!mDirty.isEmpty() || mIsAnimating || mFullRedrawNeeded)  {
-                scheduleTraversals();
-            }
-        }
-    }
-
     public void handleDispatchWindowShown() {
         mAttachInfo.mTreeObserver.dispatchOnWindowShown();
     }
@@ -5653,6 +5649,17 @@
                 + " contentInsets=" + contentInsets.toShortString()
                 + " visibleInsets=" + visibleInsets.toShortString()
                 + " reportDraw=" + reportDraw);
+
+        // Tell all listeners that we are resizing the window so that the chrome can get
+        // updated as fast as possible on a separate thread,
+        if (mDragResizing) {
+            synchronized (mWindowCallbacks) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowSizeIsChanging(frame);
+                }
+            }
+        }
+
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         if (mTranslator != null) {
             mTranslator.translateRectInScreenToAppWindow(frame);
@@ -6224,15 +6231,6 @@
         mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
     }
 
-    public void dispatchWindowAnimationStarted(int remainingFrameCount) {
-        mHandler.obtainMessage(MSG_DISPATCH_WINDOW_ANIMATION_STARTED,
-                remainingFrameCount, 0 /* unused */).sendToTarget();
-    }
-
-    public void dispatchWindowAnimationStopped() {
-        mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_ANIMATION_STOPPED);
-    }
-
     public void dispatchCheckFocus() {
         if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
             // This will result in a call to checkFocus() below.
@@ -6665,15 +6663,6 @@
                 Configuration newConfig) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
-                // Tell all listeners that we are resizing the window so that the chrome can get
-                // updated as fast as possible on a separate thread,
-                if (mViewAncestor.get().mDragResizing) {
-                    synchronized (sWindowCallbacks) {
-                        for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
-                            sWindowCallbacks.get(i).onWindowSizeIsChanging(frame);
-                        }
-                    }
-                }
                 viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
                         visibleInsets, stableInsets, outsets, reportDraw, newConfig);
             }
@@ -6802,22 +6791,6 @@
         }
 
         @Override
-        public void onAnimationStarted(int remainingFrameCount) {
-            final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.dispatchWindowAnimationStarted(remainingFrameCount);
-            }
-        }
-
-        @Override
-        public void onAnimationStopped() {
-            final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.dispatchWindowAnimationStopped();
-            }
-        }
-
-        @Override
         public void dispatchWindowShown() {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
@@ -6848,9 +6821,9 @@
     private void startDragResizing(Rect initialBounds) {
         if (!mDragResizing) {
             mDragResizing = true;
-            synchronized (sWindowCallbacks) {
-                for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
-                    sWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds);
+            synchronized (mWindowCallbacks) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds);
                 }
             }
             mFullRedrawNeeded = true;
@@ -6863,15 +6836,39 @@
     private void endDragResizing() {
         if (mDragResizing) {
             mDragResizing = false;
-            synchronized (sWindowCallbacks) {
-                for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) {
-                    sWindowCallbacks.get(i).onWindowDragResizeEnd();
+            synchronized (mWindowCallbacks) {
+                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                    mWindowCallbacks.get(i).onWindowDragResizeEnd();
                 }
             }
             mFullRedrawNeeded = true;
         }
     }
 
+    private boolean updateContentDrawBounds() {
+        boolean updated = false;
+        synchronized (mWindowCallbacks) {
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                updated |= mWindowCallbacks.get(i).onContentDrawn(
+                        mWindowAttributes.surfaceInsets.left,
+                        mWindowAttributes.surfaceInsets.top,
+                        mWidth, mHeight);
+            }
+        }
+        return updated | (mDragResizing && mReportNextDraw);
+    }
+
+    private void requestDrawWindow() {
+        if (mReportNextDraw) {
+            mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
+        }
+        synchronized (mWindowCallbacks) {
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
+            }
+        }
+    }
+
     /**
      * Class for managing the accessibility interaction connection
      * based on the global accessibility state.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5f4e7af..d9ec866 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -28,9 +28,8 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.media.session.MediaController;
 import android.net.Uri;
 import android.os.Bundle;
@@ -667,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) {
@@ -675,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);
             }
@@ -2007,16 +2020,6 @@
      */
     public abstract void setNavigationBarColor(@ColorInt int color);
 
-    /**
-     * Get information whether the activity has non client decoration view. These views are used in
-     * the multi window environment, to provide dragging handle and maximize/close buttons.
-     *
-     * @hide
-     */
-    public boolean hasNonClientDecorView() {
-        return false;
-    }
-
     /** @hide */
     public void setTheme(int resId) {
     }
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
index cb6e983..def0236 100644
--- a/core/java/android/view/WindowCallbacks.java
+++ b/core/java/android/view/WindowCallbacks.java
@@ -21,6 +21,8 @@
 /**
  * These callbacks are used to communicate window configuration changes while the user is performing
  * window changes.
+ * Note: Note that at the time of onWindowDragResizeStart the content size isn't known. A consumer
+ * should therfore not draw anything before the additional onContentDraw call has arrived.
  * @hide
  */
 public interface WindowCallbacks {
@@ -45,4 +47,16 @@
      * Called when a drag resize ends.
      */
     void onWindowDragResizeEnd();
+
+    /**
+     * The content will now be drawn to these bounds. Returns true if
+     * a draw should be requested after the next content draw.
+     */
+    boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY);
+
+    /**
+     * Called to request the window to draw one frame.
+     * @param reportNextDraw Whether it should report when the requested draw finishes.
+     */
+    void onRequestDraw(boolean reportNextDraw);
 }
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index a6d9932..3882bca 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -263,4 +263,7 @@
       */
     public abstract void setOnHardKeyboardStatusChangeListener(
         OnHardKeyboardStatusChangeListener listener);
+
+    /** Returns true if the stack with the input Id is currently visible. */
+    public abstract boolean isStackVisible(int stackId);
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index aaf6052..d20c729 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -22,6 +22,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Bundle;
@@ -38,7 +39,7 @@
  * instance of it is created by the window manager when it starts up, and allows
  * customization of window layering, special window types, key dispatching, and
  * layout.
- * 
+ *
  * <p>Because this provides deep interaction with the system window manager,
  * specific methods on this interface can be called from a variety of contexts
  * with various restrictions on what they can do.  These are encoded through
@@ -47,9 +48,9 @@
  * is attached to a method, then it is not called with any locks and may be
  * called from the main window manager thread or another thread calling into
  * the window manager.
- * 
+ *
  * <p>The current suffixes are:
- * 
+ *
  * <dl>
  * <dt> Ti <dd> Called from the input thread.  This is the thread that
  * collects pending input events and dispatches them to the appropriate window.
@@ -71,7 +72,7 @@
  * acquired by the window manager while it holds the window lock, so this is
  * even more restrictive than <var>Lw</var>.
  * </dl>
- * 
+ *
  * @hide
  */
 public interface WindowManagerPolicy {
@@ -138,7 +139,7 @@
          * Perform standard frame computation.  The result can be obtained with
          * getFrame() if so desired.  Must be called with the window manager
          * lock held.
-         * 
+         *
          * @param parentFrame The frame of the parent container this window
          * is in, used for computing its basic position.
          * @param displayFrame The frame of the overall display in which this
@@ -168,18 +169,18 @@
         /**
          * Retrieve the current frame of the window that has been assigned by
          * the window manager.  Must be called with the window manager lock held.
-         * 
+         *
          * @return Rect The rectangle holding the window frame.
          */
         public Rect getFrameLw();
 
         /**
-         * Retrieve the current frame of the window that is actually shown.
+         * Retrieve the current position of the window that is actually shown.
          * Must be called with the window manager lock held.
-         * 
-         * @return Rect The rectangle holding the shown window frame.
+         *
+         * @return Point The point holding the shown window position.
          */
-        public RectF getShownFrameLw();
+        public Point getShownPositionLw();
 
         /**
          * Retrieve the frame of the display that this window was last
@@ -206,7 +207,7 @@
          * account for screen decorations such as a status bar or soft
          * keyboard.  Must be called with the
          * window manager lock held.
-         * 
+         *
          * @return Rect The rectangle holding the content frame.
          */
         public Rect getContentFrameLw();
@@ -218,7 +219,7 @@
          * content frame to account for transient UI elements blocking it
          * such as an input method's candidates UI.  Must be called with the
          * window manager lock held.
-         * 
+         *
          * @return Rect The rectangle holding the visible frame.
          */
         public Rect getVisibleFrameLw();
@@ -229,12 +230,12 @@
          * layout of other windows.
          */
         public boolean getGivenInsetsPendingLw();
-        
+
         /**
          * Retrieve the insets given by this window's client for the content
          * area of windows behind it.  Must be called with the
          * window manager lock held.
-         * 
+         *
          * @return Rect The left, top, right, and bottom insets, relative
          * to the window's frame, of the actual contents.
          */
@@ -244,7 +245,7 @@
          * Retrieve the insets given by this window's client for the visible
          * area of windows behind it.  Must be called with the
          * window manager lock held.
-         * 
+         *
          * @return Rect The left, top, right, and bottom insets, relative
          * to the window's frame, of the actual visible area.
          */
@@ -252,7 +253,7 @@
 
         /**
          * Retrieve the current LayoutParams of the window.
-         * 
+         *
          * @return WindowManager.LayoutParams The window's internal LayoutParams
          *         instance.
          */
@@ -298,12 +299,12 @@
         public boolean isVoiceInteraction();
 
         /**
-         * Return true if, at any point, the application token associated with 
-         * this window has actually displayed any windows.  This is most useful 
-         * with the "starting up" window to determine if any windows were 
-         * displayed when it is closed. 
-         * 
-         * @return Returns true if one or more windows have been displayed, 
+         * Return true if, at any point, the application token associated with
+         * this window has actually displayed any windows.  This is most useful
+         * with the "starting up" window to determine if any windows were
+         * displayed when it is closed.
+         *
+         * @return Returns true if one or more windows have been displayed,
          *         else false.
          */
         public boolean hasAppShownWindows();
@@ -314,17 +315,17 @@
          * that will remove the surface.
          */
         boolean isVisibleLw();
-        
+
         /**
          * Like {@link #isVisibleLw}, but also counts a window that is currently
          * "hidden" behind the keyguard as visible.  This allows us to apply
          * things like window flags that impact the keyguard.
          */
         boolean isVisibleOrBehindKeyguardLw();
-        
+
         /**
-         * Is this window currently visible to the user on-screen?  It is 
-         * displayed either if it is visible or it is currently running an 
+         * Is this window currently visible to the user on-screen?  It is
+         * displayed either if it is visible or it is currently running an
          * animation before no longer being visible.  Must be called with the
          * window manager lock held.
          */
@@ -350,7 +351,7 @@
         boolean isDrawnLw();
 
         /**
-         * Returns true if this window has been shown on screen at some time in 
+         * Returns true if this window has been shown on screen at some time in
          * the past.  Must be called with the window manager lock held.
          */
         public boolean hasDrawnLw();
@@ -362,7 +363,7 @@
          * Returns true if {@link #showLw} was last called for the window.
          */
         public boolean hideLw(boolean doAnimation);
-        
+
         /**
          * Can be called to undo the effect of {@link #hideLw}, allowing a
          * window to be shown as long as the window manager and client would
@@ -479,7 +480,7 @@
 
     // NOTE: screen off reasons are in order of significance, with more
     // important ones lower than less important ones.
-    
+
     /** Screen turned off because of a device admin */
     public final int OFF_BECAUSE_OF_ADMIN = 1;
     /** Screen turned off because of power button */
@@ -498,10 +499,10 @@
     /** When not otherwise specified by the activity's screenOrientation, rotation is set by
      * the user. */
     public final int USER_ROTATION_LOCKED = 1;
-    
+
     /**
      * Perform initialization of the policy.
-     * 
+     *
      * @param context The system context we are running in.
      */
     public void init(Context context, IWindowManager windowManager,
@@ -526,11 +527,11 @@
 
     /**
      * Check permissions when adding a window.
-     * 
+     *
      * @param attrs The window's LayoutParams.
      * @param outAppOp First element will be filled with the app op corresponding to
      *                 this window, or OP_NONE.
-     *  
+     *
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
      *      else an error code, usually
      *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
@@ -551,21 +552,21 @@
      * Sanitize the layout parameters coming from a client.  Allows the policy
      * to do things like ensure that windows of a specific type can't take
      * input focus.
-     * 
+     *
      * @param attrs The window layout parameters to be modified.  These values
      * are modified in-place.
      */
     public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
-    
+
     /**
      * After the window manager has computed the current configuration based
      * on its knowledge of the display and input devices, it gives the policy
      * a chance to adjust the information contained in it.  If you want to
      * leave it as-is, simply do nothing.
-     * 
+     *
      * <p>This method may be called by any thread in the window manager, but
      * no internal locks in the window manager will be held.
-     * 
+     *
      * @param config The Configuration being computed, for you to change as
      * desired.
      * @param keyboardPresence Flags that indicate whether internal or external
@@ -575,13 +576,13 @@
      */
     public void adjustConfigurationLw(Configuration config, int keyboardPresence,
             int navigationPresence);
-    
+
     /**
      * Assign a window type to a layer.  Allows you to control how different
      * kinds of windows are ordered on-screen.
-     * 
+     *
      * @param type The type of window being assigned.
-     * 
+     *
      * @return int An arbitrary integer used to order windows, with lower
      *         numbers below higher ones.
      */
@@ -591,9 +592,9 @@
      * Return how to Z-order sub-windows in relation to the window they are
      * attached to.  Return positive to have them ordered in front, negative for
      * behind.
-     * 
+     *
      * @param type The sub-window type code.
-     * 
+     *
      * @return int Layer in relation to the attached window, where positive is
      *         above and negative is below.
      */
@@ -669,19 +670,19 @@
      * manager (using the normal window manager APIs) that will be shown until
      * the application displays its own window.  This is called without the
      * window manager locked so that you can call back into it.
-     * 
+     *
      * @param appToken Token of the application being started.
-     * @param packageName The name of the application package being started. 
+     * @param packageName The name of the application package being started.
      * @param theme Resource defining the application's overall visual theme.
      * @param nonLocalizedLabel The default title label of the application if
      *        no data is found in the resource.
      * @param labelRes The resource ID the application would like to use as its name.
      * @param icon The resource ID the application would like to use as its icon.
      * @param windowFlags Window layout flags.
-     * 
+     *
      * @return Optionally you can return the View that was used to create the
      *         window, for easy removal in removeStartingWindow.
-     * 
+     *
      * @see #removeStartingWindow
      */
     public View addStartingWindow(IBinder appToken, String packageName,
@@ -694,15 +695,15 @@
      * that application.  You should at this point remove the window from the
      * window manager.  This is called without the window manager locked so
      * that you can call back into it.
-     * 
+     *
      * <p>Note: due to the nature of these functions not being called with the
      * window manager locked, you must be prepared for this function to be
      * called multiple times and/or an initial time with a null View window
      * even if you previously returned one.
-     * 
+     *
      * @param appToken Token of the application that has started.
      * @param window Window View that was returned by createStartingWindow.
-     * 
+     *
      * @see #addStartingWindow
      */
     public void removeStartingWindow(IBinder appToken, View window);
@@ -711,10 +712,10 @@
      * Prepare for a window being added to the window manager.  You can throw an
      * exception here to prevent the window being added, or do whatever setup
      * you need to keep track of the window.
-     * 
+     *
      * @param win The window being added.
-     * @param attrs The window's LayoutParams. 
-     *  
+     * @param attrs The window's LayoutParams.
+     *
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an
      *         error code to abort the add.
      */
@@ -724,7 +725,7 @@
     /**
      * Called when a window is being removed from a window manager.  Must not
      * throw an exception -- clean up as much as possible.
-     * 
+     *
      * @param win The window being removed.
      */
     public void removeWindowLw(WindowState win);
@@ -733,12 +734,12 @@
      * Control the animation to run when a window's state changes.  Return a
      * non-0 number to force the animation to a specific resource ID, or 0
      * to use the default animation.
-     * 
+     *
      * @param win The window that is changing.
      * @param transit What is happening to the window: {@link #TRANSIT_ENTER},
      *                {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or
      *                {@link #TRANSIT_HIDE}.
-     * 
+     *
      * @return Resource ID of the actual animation to use, or 0 for none.
      */
     public int selectAnimationLw(WindowState win, int transit);
@@ -747,8 +748,8 @@
      * Determine the animation to run for a rotation transition based on the
      * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
      * and whether it is currently fullscreen and frontmost.
-     * 
-     * @param anim The exiting animation resource id is stored in anim[0], the 
+     *
+     * @param anim The exiting animation resource id is stored in anim[0], the
      * entering animation resource id is stored in anim[1].
      */
     public void selectRotationAnimationLw(int anim[]);
@@ -814,7 +815,7 @@
      * <p>Allows you to define
      * behavior for keys that can not be overridden by applications.
      * This method is called from the input thread, with no locks held.
-     * 
+     *
      * @param win The window that currently has focus.  This is where the key
      *            event will normally go.
      * @param event The key event.
@@ -832,7 +833,7 @@
      *
      * <p>Allows you to define default global behavior for keys that were not handled
      * by applications.  This method is called from the input thread, with no locks held.
-     * 
+     *
      * @param win The window that currently has focus.  This is where the key
      *            event will normally go.
      * @param event The key event.
@@ -844,7 +845,7 @@
 
     /**
      * Called when layout of the windows is about to start.
-     * 
+     *
      * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
      * @param displayWidth The current full width of the screen.
      * @param displayHeight The current full height of the screen.
@@ -872,7 +873,7 @@
      * Called for each window attached to the window manager as layout is
      * proceeding.  The implementation of this function must take care of
      * setting the window's frame, either here or in finishLayout().
-     * 
+     *
      * @param win The window being positioned.
      * @param attached For sub-windows, the window it is attached to; this
      *                 window will already have had layoutWindow() called on it
@@ -880,7 +881,7 @@
      */
     public void layoutWindowLw(WindowState win, WindowState attached);
 
-    
+
     /**
      * Return the insets for the areas covered by system windows. These values
      * are computed on the most recent layout, so they are not guaranteed to
@@ -912,10 +913,10 @@
     static final int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
     /** Need to recompute animations */
     static final int FINISH_LAYOUT_REDO_ANIM = 0x0008;
-    
+
     /**
      * Called following layout of all windows before each window has policy applied.
-     * 
+     *
      * @param displayWidth The current full width of the screen.
      * @param displayHeight The current full height of the screen.
      */
@@ -936,7 +937,7 @@
      * to each window. If in this function you do
      * something that may have modified the animation state of another window,
      * be sure to return non-zero in order to perform another pass through layout.
-     *  
+     *
      * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
      * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
      * or {@link #FINISH_LAYOUT_REDO_ANIM}.
@@ -1150,7 +1151,7 @@
      * the system should go into safe mode.
      */
     public void setSafeMode(boolean safeMode);
-    
+
     /**
      * Called when the system is mostly done booting.
      */
@@ -1184,14 +1185,14 @@
      * this point the display is active.
      */
     public void enableScreenAfterBoot();
-    
+
     public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation);
-    
+
     /**
      * Call from application to perform haptic feedback on its window.
      */
     public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
-    
+
     /**
      * Called when we have started keeping the screen on because a window
      * requesting this has become visible.
@@ -1205,21 +1206,21 @@
     public void keepScreenOnStoppedLw();
 
     /**
-     * Gets the current user rotation mode. 
+     * Gets the current user rotation mode.
      *
      * @return The rotation mode.
      *
      * @see WindowManagerPolicy#USER_ROTATION_LOCKED
-     * @see WindowManagerPolicy#USER_ROTATION_FREE 
+     * @see WindowManagerPolicy#USER_ROTATION_FREE
      */
     @UserRotationMode
     public int getUserRotationMode();
 
     /**
-     * Inform the policy that the user has chosen a preferred orientation ("rotation lock"). 
+     * Inform the policy that the user has chosen a preferred orientation ("rotation lock").
      *
      * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
-     *             {@link WindowManagerPolicy#USER_ROTATION_FREE}. 
+     *             {@link WindowManagerPolicy#USER_ROTATION_FREE}.
      * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
      *                 {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
      */
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/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 11d0f4a..4ac547a 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -429,7 +429,8 @@
 
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
-                + " mSettingsActivityName=" + mSettingsActivityName);
+                + " mSettingsActivityName=" + mSettingsActivityName
+                + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod);
         pw.println(prefix + "mIsDefaultResId=0x"
                 + Integer.toHexString(mIsDefaultResId));
         pw.println(prefix + "Service:");
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/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d0c50c9..358f939 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@
 import android.annotation.SystemApi;
 import android.annotation.Widget;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -1850,7 +1851,7 @@
      * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
      * </a>
      *
-     * The returned message channels are entangled and already in started state.
+     * <p>The returned message channels are entangled and already in started state.</p>
      *
      * @return the two message ports that form the message channel.
      */
@@ -2157,6 +2158,10 @@
             WebView.super.setLayoutParams(params);
         }
 
+        public void super_startActivityForResult(Intent intent, int requestCode) {
+            WebView.super.startActivityForResult(intent, requestCode);
+        }
+
         // ---- Access to non-public methods ----
         public void overScrollBy(int deltaX, int deltaY,
                 int scrollX, int scrollY,
@@ -2594,6 +2599,23 @@
         mProvider.getViewDelegate().onFinishTemporaryDetach();
     }
 
+    /**
+     * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     * @hide
+     */
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
+    }
+
     /** @hide */
     @Override
     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 27033ad..3ce034c 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.content.res.Configuration;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -380,6 +381,8 @@
         public void onStartTemporaryDetach();
 
         public void onFinishTemporaryDetach();
+
+        public void onActivityResult(int requestCode, int resultCode, Intent data);
     }
 
     interface ScrollDelegate {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 932b354..0e3a69f 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -401,12 +401,11 @@
      }
 
     LayoutParams createOrReuseLayoutParams(View v) {
-        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
-        if (currentLp instanceof ViewGroup.LayoutParams) {
-            LayoutParams lp = (LayoutParams) currentLp;
-            return lp;
+        final LayoutParams currentLp = v.getLayoutParams();
+        if (currentLp != null) {
+            return currentLp;
         }
-        return new ViewGroup.LayoutParams(0, 0);
+        return new LayoutParams(0, 0);
     }
 
     void refreshChildren() {
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/FastScroller.java b/core/java/android/widget/FastScroller.java
index c40289e..559181b 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -383,6 +383,7 @@
                     break;
             }
         }
+        ta.recycle();
 
         updateAppearance();
     }
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/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 9571109..7220256 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -16,12 +16,7 @@
 
 package android.widget;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.Keyframe;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -35,6 +30,7 @@
 import android.graphics.Typeface;
 import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
@@ -50,7 +46,6 @@
 import com.android.internal.R;
 import com.android.internal.widget.ExploreByTouchHelper;
 
-import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Locale;
 
@@ -60,6 +55,7 @@
  * @hide
  */
 public class RadialTimePickerView extends View {
+
     private static final String TAG = "RadialTimePickerView";
 
     private static final int HOURS = 0;
@@ -73,12 +69,6 @@
     private static final int AM = 0;
     private static final int PM = 1;
 
-    // Opaque alpha level
-    private static final int ALPHA_OPAQUE = 255;
-
-    // Transparent alpha level
-    private static final int ALPHA_TRANSPARENT = 0;
-
     private static final int HOURS_IN_CIRCLE = 12;
     private static final int MINUTES_IN_CIRCLE = 60;
     private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
@@ -88,8 +78,8 @@
     private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
     private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
 
-    private static final int FADE_OUT_DURATION = 500;
-    private static final int FADE_IN_DURATION = 500;
+    private static final int ANIM_DURATION_NORMAL = 500;
+    private static final int ANIM_DURATION_TOUCH = 60;
 
     private static final int[] SNAP_PREFER_30S_MAP = new int[361];
 
@@ -97,6 +87,9 @@
     private static final float[] COS_30 = new float[NUM_POSITIONS];
     private static final float[] SIN_30 = new float[NUM_POSITIONS];
 
+    /** "Something is wrong" color used when a color attribute is missing. */
+    private static final int MISSING_COLOR = Color.MAGENTA;
+
     static {
         // Prepare mapping to snap touchable degrees to selectable degrees.
         preparePrefer30sMap();
@@ -110,8 +103,19 @@
         }
     }
 
-    private final InvalidateUpdateListener mInvalidateUpdateListener =
-            new InvalidateUpdateListener();
+    private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES =
+            new FloatProperty<RadialTimePickerView>("hoursToMinutes") {
+                @Override
+                public Float get(RadialTimePickerView radialTimePickerView) {
+                    return radialTimePickerView.mHoursToMinutes;
+                }
+
+                @Override
+                public void setValue(RadialTimePickerView object, float value) {
+                    object.mHoursToMinutes = value;
+                    object.invalidate();
+                }
+            };
 
     private final String[] mHours12Texts = new String[12];
     private final String[] mOuterHours24Texts = new String[12];
@@ -119,15 +123,8 @@
     private final String[] mMinutesTexts = new String[12];
 
     private final Paint[] mPaint = new Paint[2];
-    private final IntHolder[] mAlpha = new IntHolder[2];
-
     private final Paint mPaintCenter = new Paint();
-
-    private final Paint[][] mPaintSelector = new Paint[2][3];
-
-    private final int mSelectorColor;
-    private final int mSelectorDotColor;
-
+    private final Paint[] mPaintSelector = new Paint[3];
     private final Paint mPaintBackground = new Paint();
 
     private final Typeface mTypeface;
@@ -144,9 +141,6 @@
 
     private final int[] mSelectionDegrees = new int[2];
 
-    private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>();
-    private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>();
-
     private final RadialPickerTouchHelper mTouchHelper;
 
     private final Path mSelectorPath = new Path();
@@ -154,6 +148,9 @@
     private boolean mIs24HourMode;
     private boolean mShowHours;
 
+    private ObjectAnimator mHoursToMinutesAnimator;
+    private float mHoursToMinutes;
+
     /**
      * When in 24-hour mode, indicates that the current hour is between
      * 1 and 12 (inclusive).
@@ -165,6 +162,9 @@
     private int mSelectorDotRadius;
     private int mCenterDotRadius;
 
+    private int mSelectorColor;
+    private int mSelectorDotColor;
+
     private int mXCenter;
     private int mYCenter;
     private int mCircleRadius;
@@ -176,7 +176,6 @@
     private String[] mOuterTextHours;
     private String[] mInnerTextHours;
     private String[] mMinutesText;
-    private AnimatorSet mTransition;
 
     private int mAmOrPm;
 
@@ -304,27 +303,15 @@
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
         super(context, attrs);
 
+        applyAttributes(attrs, defStyleAttr, defStyleRes);
+
         // Pull disabled alpha from theme.
         final TypedValue outValue = new TypedValue();
         context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
         mDisabledAlpha = outValue.getFloat();
 
-        // process style attributes
-        final Resources res = getResources();
-        final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
-                defStyleAttr, defStyleRes);
-
         mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
 
-        // Initialize all alpha values to opaque.
-        for (int i = 0; i < mAlpha.length; i++) {
-            mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
-        }
-
-        mTextColor[HOURS] = a.getColorStateList(R.styleable.TimePicker_numbersTextColor);
-        mTextColor[HOURS_INNER] = a.getColorStateList(R.styleable.TimePicker_numbersInnerTextColor);
-        mTextColor[MINUTES] = mTextColor[HOURS];
-
         mPaint[HOURS] = new Paint();
         mPaint[HOURS].setAntiAlias(true);
         mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
@@ -333,44 +320,21 @@
         mPaint[MINUTES].setAntiAlias(true);
         mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
 
-        final ColorStateList selectorColors = a.getColorStateList(
-                R.styleable.TimePicker_numbersSelectorColor);
-        final int selectorActivatedColor = selectorColors.getColorForState(
-                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
-
-        mPaintCenter.setColor(selectorActivatedColor);
         mPaintCenter.setAntiAlias(true);
 
-        final int[] activatedStateSet = StateSet.get(
-                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
+        mPaintSelector[SELECTOR_CIRCLE] = new Paint();
+        mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true);
 
-        mSelectorColor = selectorActivatedColor;
-        mSelectorDotColor = mTextColor[HOURS].getColorForState(activatedStateSet, 0);
+        mPaintSelector[SELECTOR_DOT] = new Paint();
+        mPaintSelector[SELECTOR_DOT].setAntiAlias(true);
 
-        mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
+        mPaintSelector[SELECTOR_LINE] = new Paint();
+        mPaintSelector[SELECTOR_LINE].setAntiAlias(true);
+        mPaintSelector[SELECTOR_LINE].setStrokeWidth(2);
 
-        mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
-
-        mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
-        mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
-        mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
-
-        mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
-
-        mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
-
-        mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
-        mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
-        mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
-
-        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
-                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
         mPaintBackground.setAntiAlias(true);
 
+        final Resources res = getResources();
         mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
         mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
         mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
@@ -385,6 +349,7 @@
         mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);
 
         mShowHours = true;
+        mHoursToMinutes = HOURS;
         mIs24HourMode = false;
         mAmOrPm = AM;
 
@@ -399,8 +364,6 @@
         initHoursAndMinutesText();
         initData();
 
-        a.recycle();
-
         // Initial values
         final Calendar calendar = Calendar.getInstance(Locale.getDefault());
         final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
@@ -412,6 +375,48 @@
         setHapticFeedbackEnabled(true);
     }
 
+    void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        final Context context = getContext();
+        final TypedArray a = getContext().obtainStyledAttributes(attrs,
+                R.styleable.TimePicker, defStyleAttr, defStyleRes);
+
+        final ColorStateList numbersTextColor = a.getColorStateList(
+                R.styleable.TimePicker_numbersTextColor);
+        final ColorStateList numbersInnerTextColor = a.getColorStateList(
+                R.styleable.TimePicker_numbersInnerTextColor);
+        mTextColor[HOURS] = numbersTextColor == null ?
+                ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor;
+        mTextColor[HOURS_INNER] = numbersInnerTextColor == null ?
+                ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor;
+        mTextColor[MINUTES] = mTextColor[HOURS];
+
+        // Set up various colors derived from the selector "activated" state.
+        final ColorStateList selectorColors = a.getColorStateList(
+                R.styleable.TimePicker_numbersSelectorColor);
+        final int selectorActivatedColor;
+        if (selectorColors != null) {
+            final int[] stateSetEnabledActivated = StateSet.get(
+                    StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
+            selectorActivatedColor = selectorColors.getColorForState(
+                    stateSetEnabledActivated, 0);
+        }  else {
+            selectorActivatedColor = MISSING_COLOR;
+        }
+
+        mPaintCenter.setColor(selectorActivatedColor);
+
+        final int[] stateSetActivated = StateSet.get(
+                StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
+
+        mSelectorColor = selectorActivatedColor;
+        mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0);
+
+        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
+                context.getColor(R.color.timepicker_default_numbers_background_color_material)));
+
+        a.recycle();
+    }
+
     public void initialize(int hour, int minute, boolean is24HourMode) {
         if (mIs24HourMode != is24HourMode) {
             mIs24HourMode = is24HourMode;
@@ -569,35 +574,11 @@
     }
 
     public void showHours(boolean animate) {
-        if (mShowHours) {
-            return;
-        }
-
-        mShowHours = true;
-
-        if (animate) {
-            startMinutesToHoursAnimation();
-        }
-
-        initData();
-        invalidate();
-        mTouchHelper.invalidateRoot();
+        showPicker(true, animate);
     }
 
     public void showMinutes(boolean animate) {
-        if (!mShowHours) {
-            return;
-        }
-
-        mShowHours = false;
-
-        if (animate) {
-            startHoursToMinutesAnimation();
-        }
-
-        initData();
-        invalidate();
-        mTouchHelper.invalidateRoot();
+        showPicker(false, animate);
     }
 
     private void initHoursAndMinutesText() {
@@ -620,12 +601,6 @@
         }
 
         mMinutesText = mMinutesTexts;
-
-        final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT;
-        mAlpha[HOURS].setValue(hoursAlpha);
-
-        final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE;
-        mAlpha[MINUTES].setValue(minutesAlpha);
     }
 
     @Override
@@ -653,90 +628,144 @@
         final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha;
 
         drawCircleBackground(canvas);
-        drawHours(canvas, alphaMod);
-        drawMinutes(canvas, alphaMod);
+
+        final Path selectorPath = mSelectorPath;
+        drawSelector(canvas, selectorPath);
+        drawHours(canvas, selectorPath, alphaMod);
+        drawMinutes(canvas, selectorPath, alphaMod);
         drawCenter(canvas, alphaMod);
     }
 
+    private void showPicker(boolean hours, boolean animate) {
+        if (mShowHours == hours) {
+            return;
+        }
+
+        mShowHours = hours;
+
+        if (animate) {
+            animatePicker(hours, ANIM_DURATION_NORMAL);
+        }
+
+        initData();
+        invalidate();
+        mTouchHelper.invalidateRoot();
+    }
+
+    private void animatePicker(boolean hoursToMinutes, long duration) {
+        final float target = hoursToMinutes ? HOURS : MINUTES;
+        if (mHoursToMinutes == target) {
+            // If we have a pending or running animator, cancel it.
+            if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) {
+                mHoursToMinutesAnimator.cancel();
+                mHoursToMinutesAnimator = null;
+            }
+
+            // We're already showing the correct picker.
+            return;
+        }
+
+        mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target);
+        mHoursToMinutesAnimator.setAutoCancel(true);
+        mHoursToMinutesAnimator.setDuration(duration);
+        mHoursToMinutesAnimator.start();
+    }
+
     private void drawCircleBackground(Canvas canvas) {
         canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
     }
 
-    private void drawHours(Canvas canvas, float alphaMod) {
-        final int hoursAlpha = (int) (mAlpha[HOURS].getValue() * alphaMod + 0.5f);
+    private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) {
+        final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f);
         if (hoursAlpha > 0) {
-            // Draw the hour selector under the elements.
-            drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS, null, alphaMod);
-
-            // Draw outer hours.
-            drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS],
-                    mOuterTextHours, mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS],
-                    hoursAlpha, !mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
-
-            // Draw inner hours (13-00) for 24-hour time.
-            if (mIs24HourMode && mInnerTextHours != null) {
-                drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
-                        mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
-                        mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
-            }
-        }
-    }
-
-    private void drawMinutes(Canvas canvas, float alphaMod) {
-        final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f);
-        if (minutesAlpha > 0) {
-            // Draw the minute selector under the elements.
-            drawSelector(canvas, MINUTES, mSelectorPath, alphaMod);
-
-            // Exclude the selector region, then draw minutes with no
+            // Exclude the selector region, then draw inner/outer hours with no
             // activated states.
             canvas.save(Canvas.CLIP_SAVE_FLAG);
-            canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE);
-            drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
-                    mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
-                    minutesAlpha, false, 0, false);
+            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
+            drawHoursClipped(canvas, hoursAlpha, false);
             canvas.restore();
 
             // Intersect the selector region, then draw minutes with only
             // activated states.
             canvas.save(Canvas.CLIP_SAVE_FLAG);
-            canvas.clipPath(mSelectorPath, Region.Op.INTERSECT);
-            drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
-                    mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
-                    minutesAlpha, true, mSelectionDegrees[MINUTES], true);
+            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
+            drawHoursClipped(canvas, hoursAlpha, true);
             canvas.restore();
         }
     }
 
+    private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) {
+        // Draw outer hours.
+        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours,
+                mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
+                showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
+
+        // Draw inner hours (13-00) for 24-hour time.
+        if (mIs24HourMode && mInnerTextHours != null) {
+            drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
+                    mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
+                    showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated);
+        }
+    }
+
+    private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) {
+        final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f);
+        if (minutesAlpha > 0) {
+            // Exclude the selector region, then draw minutes with no
+            // activated states.
+            canvas.save(Canvas.CLIP_SAVE_FLAG);
+            canvas.clipPath(selectorPath, Region.Op.DIFFERENCE);
+            drawMinutesClipped(canvas, minutesAlpha, false);
+            canvas.restore();
+
+            // Intersect the selector region, then draw minutes with only
+            // activated states.
+            canvas.save(Canvas.CLIP_SAVE_FLAG);
+            canvas.clipPath(selectorPath, Region.Op.INTERSECT);
+            drawMinutesClipped(canvas, minutesAlpha, true);
+            canvas.restore();
+        }
+    }
+
+    private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) {
+        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText,
+                mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
+                showActivated, mSelectionDegrees[MINUTES], showActivated);
+    }
+
     private void drawCenter(Canvas canvas, float alphaMod) {
         mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f));
         canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
     }
 
-    private int applyAlpha(int argb, int alpha) {
-        final int srcAlpha = (argb >> 24) & 0xFF;
-        final int dstAlpha = (int) (srcAlpha * (alpha / 255.0) + 0.5f);
-        return (0xFFFFFF & argb) | (dstAlpha << 24);
-    }
-
     private int getMultipliedAlpha(int argb, int alpha) {
         return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
     }
 
-    private void drawSelector(Canvas canvas, int index, Path selectorPath, float alphaMod) {
-        final int alpha = (int) (mAlpha[index % 2].getValue() * alphaMod + 0.5f);
-        final int color = applyAlpha(mSelectorColor, alpha);
+    private void drawSelector(Canvas canvas, Path selectorPath) {
+        // Determine the current length, angle, and dot scaling factor.
+        final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS;
+        final int hoursInset = mTextInset[hoursIndex];
+        final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2];
+        final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0;
+
+        final int minutesIndex = MINUTES;
+        final int minutesInset = mTextInset[minutesIndex];
+        final int minutesAngleDeg = mSelectionDegrees[minutesIndex];
+        final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0;
 
         // Calculate the current radius at which to place the selection circle.
         final int selRadius = mSelectorRadius;
-        final int selLength = mCircleRadius - mTextInset[index];
-        final double selAngleRad = Math.toRadians(mSelectionDegrees[index % 2]);
+        final float selLength =
+                mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes);
+        final double selAngleRad =
+                Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes));
         final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad);
         final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad);
 
         // Draw the selection circle.
-        final Paint paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
-        paint.setColor(color);
+        final Paint paint = mPaintSelector[SELECTOR_CIRCLE];
+        paint.setColor(mSelectorColor);
         canvas.drawCircle(selCenterX, selCenterY, selRadius, paint);
 
         // If needed, set up the clip path for later.
@@ -746,26 +775,26 @@
         }
 
         // Draw the dot if we're between two items.
-        final boolean shouldDrawDot = mSelectionDegrees[index % 2] % 30 != 0;
-        if (shouldDrawDot) {
-            final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT];
+        final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes);
+        if (dotScale > 0) {
+            final Paint dotPaint = mPaintSelector[SELECTOR_DOT];
             dotPaint.setColor(mSelectorDotColor);
-            canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint);
+            canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint);
         }
 
         // Shorten the line to only go from the edge of the center dot to the
         // edge of the selection circle.
         final double sin = Math.sin(selAngleRad);
         final double cos = Math.cos(selAngleRad);
-        final int lineLength = selLength - selRadius;
+        final float lineLength = selLength - selRadius;
         final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
         final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
         final float linePointX = centerX + (int) (lineLength * sin);
         final float linePointY = centerY - (int) (lineLength * cos);
 
         // Draw the line.
-        final Paint linePaint = mPaintSelector[index % 2][SELECTOR_LINE];
-        linePaint.setColor(color);
+        final Paint linePaint = mPaintSelector[SELECTOR_LINE];
+        linePaint.setColor(mSelectorColor);
         linePaint.setStrokeWidth(mSelectorStroke);
         canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint);
     }
@@ -842,73 +871,6 @@
         }
     }
 
-    private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
-                InvalidateUpdateListener updateListener) {
-        final ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
-        animator.setDuration(FADE_OUT_DURATION);
-        animator.addUpdateListener(updateListener);
-        return animator;
-    }
-
-    private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
-                InvalidateUpdateListener updateListener) {
-        final float delayMultiplier = 0.25f;
-        final float transitionDurationMultiplier = 1f;
-        final float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
-        final int totalDuration = (int) (FADE_IN_DURATION * totalDurationMultiplier);
-        final float delayPoint = (delayMultiplier * FADE_IN_DURATION) / totalDuration;
-
-        final Keyframe kf0, kf1, kf2;
-        kf0 = Keyframe.ofInt(0f, startAlpha);
-        kf1 = Keyframe.ofInt(delayPoint, startAlpha);
-        kf2 = Keyframe.ofInt(1f, endAlpha);
-        final PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
-
-        final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, fadeIn);
-        animator.setDuration(totalDuration);
-        animator.addUpdateListener(updateListener);
-        return animator;
-    }
-
-    private class InvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener {
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            RadialTimePickerView.this.invalidate();
-        }
-    }
-
-    private void startHoursToMinutesAnimation() {
-        if (mHoursToMinutesAnims.size() == 0) {
-            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
-                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
-                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-        }
-
-        if (mTransition != null && mTransition.isRunning()) {
-            mTransition.end();
-        }
-        mTransition = new AnimatorSet();
-        mTransition.playTogether(mHoursToMinutesAnims);
-        mTransition.start();
-    }
-
-    private void startMinutesToHoursAnimation() {
-        if (mMinuteToHoursAnims.size() == 0) {
-            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
-                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-            mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
-                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
-        }
-
-        if (mTransition != null && mTransition.isRunning()) {
-            mTransition.end();
-        }
-        mTransition = new AnimatorSet();
-        mTransition.playTogether(mMinuteToHoursAnims);
-        mTransition.start();
-    }
-
     private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
         // Ensure the point is inside the touchable area.
         final int innerBound;
@@ -992,6 +954,9 @@
             return false;
         }
 
+        // Ensure we're showing the correct picker.
+        animatePicker(mShowHours, ANIM_DURATION_TOUCH);
+
         final int type;
         final int newValue;
         final boolean valueChanged;
@@ -1356,20 +1321,4 @@
             return id >>> SHIFT_VALUE & MASK_VALUE;
         }
     }
-
-    private static class IntHolder {
-        private int mValue;
-
-        public IntHolder(int value) {
-            mValue = value;
-        }
-
-        public void setValue(int value) {
-            mValue = value;
-        }
-
-        public int getValue() {
-            return mValue;
-        }
-    }
 }
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 61ef6dc..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,30 +187,78 @@
 
         // 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();
 
-        mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
-                R.id.radial_picker);
+        mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(R.id.radial_picker);
+        mRadialTimePickerView.applyAttributes(attrs, defStyleAttr, defStyleRes);
 
         setupListeners();
 
         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 f0e216f..b7b7400 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,6 +31,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.database.DataSetObserver;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
@@ -46,13 +49,18 @@
 import android.service.chooser.IChooserTargetResult;
 import android.service.chooser.IChooserTargetService;
 import android.text.TextUtils;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.util.Slog;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.widget.AbsListView;
 import android.widget.BaseAdapter;
 import android.widget.ListView;
@@ -80,6 +88,7 @@
     private Intent mReferrerFillInIntent;
 
     private ChooserListAdapter mChooserListAdapter;
+    private ChooserRowAdapter mChooserRowAdapter;
 
     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
 
@@ -253,7 +262,9 @@
             boolean alwaysUseOption) {
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
         mChooserListAdapter = (ChooserListAdapter) adapter;
-        adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
+        mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
+        mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
+        adapterView.setAdapter(mChooserRowAdapter);
         if (listView != null) {
             listView.setItemsCanFocus(true);
         }
@@ -362,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,
@@ -909,7 +925,63 @@
         @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());
+        }
+    }
+
+    static class RowScale {
+        private static final int DURATION = 400;
+
+        float mScale;
+        ChooserRowAdapter mAdapter;
+        private final ObjectAnimator mAnimator;
+
+        public static final FloatProperty<RowScale> PROPERTY =
+                new FloatProperty<RowScale>("scale") {
+            @Override
+            public void setValue(RowScale object, float value) {
+                object.mScale = value;
+                object.mAdapter.notifyDataSetChanged();
+            }
+
+            @Override
+            public Float get(RowScale object) {
+                return object.mScale;
+            }
+        };
+
+        public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
+            mAdapter = adapter;
+            mScale = from;
+            if (from == to) {
+                mAnimator = null;
+                return;
+            }
+
+            mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
+        }
+
+        public RowScale setInterpolator(Interpolator interpolator) {
+            if (mAnimator != null) {
+                mAnimator.setInterpolator(interpolator);
+            }
+            return this;
+        }
+
+        public float get() {
+            return mScale;
+        }
+
+        public void startAnimation() {
+            if (mAnimator != null) {
+                mAnimator.start();
+            }
+        }
+
+        public void cancelAnimation() {
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
         }
     }
 
@@ -917,15 +989,51 @@
         private ChooserListAdapter mChooserListAdapter;
         private final LayoutInflater mLayoutInflater;
         private final int mColumnCount = 4;
+        private RowScale[] mServiceTargetScale;
+        private final Interpolator mInterpolator;
 
         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
             mChooserListAdapter = wrappedAdapter;
             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
 
+            mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
+                    android.R.interpolator.decelerate_quint);
+
             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
                 @Override
                 public void onChanged() {
                     super.onChanged();
+                    final int rcount = getServiceTargetRowCount();
+                    if (mServiceTargetScale == null
+                            || mServiceTargetScale.length != rcount) {
+                        RowScale[] old = mServiceTargetScale;
+                        int oldRCount = old != null ? old.length : 0;
+                        mServiceTargetScale = new RowScale[rcount];
+                        if (old != null && rcount > 0) {
+                            System.arraycopy(old, 0, mServiceTargetScale, 0,
+                                    Math.min(old.length, rcount));
+                        }
+
+                        for (int i = rcount; i < oldRCount; i++) {
+                            old[i].cancelAnimation();
+                        }
+
+                        for (int i = oldRCount; i < rcount; i++) {
+                            final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
+                                    .setInterpolator(mInterpolator);
+                            mServiceTargetScale[i] = rs;
+                        }
+
+                        // Start the animations in a separate loop.
+                        // The process of starting animations will result in
+                        // binding views to set up initial values, and we must
+                        // have ALL of the new RowScale objects created above before
+                        // we get started.
+                        for (int i = oldRCount; i < rcount; i++) {
+                            mServiceTargetScale[i].startAnimation();
+                        }
+                    }
+
                     notifyDataSetChanged();
                 }
 
@@ -933,19 +1041,43 @@
                 public void onInvalidated() {
                     super.onInvalidated();
                     notifyDataSetInvalidated();
+                    if (mServiceTargetScale != null) {
+                        for (RowScale rs : mServiceTargetScale) {
+                            rs.cancelAnimation();
+                        }
+                    }
                 }
             });
         }
 
+        private float getRowScale(int rowPosition) {
+            final int start = getCallerTargetRowCount();
+            final int end = start + getServiceTargetRowCount();
+            if (rowPosition >= start && rowPosition < end) {
+                return mServiceTargetScale[rowPosition - start].get();
+            }
+            return 1.f;
+        }
+
         @Override
         public int getCount() {
             return (int) (
-                    Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
-                    + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
+                    getCallerTargetRowCount()
+                    + getServiceTargetRowCount()
                     + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
             );
         }
 
+        public int getCallerTargetRowCount() {
+            return (int) Math.ceil(
+                    (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
+        }
+
+        public int getServiceTargetRowCount() {
+            return (int) Math.ceil(
+                    (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
+        }
+
         @Override
         public Object getItem(int position) {
             // We have nothing useful to return here.
@@ -959,33 +1091,69 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            final View[] holder;
+            final RowViewHolder holder;
             if (convertView == null) {
                 holder = createViewHolder(parent);
             } else {
-                holder = (View[]) convertView.getTag();
+                holder = (RowViewHolder) convertView.getTag();
             }
             bindViewHolder(position, holder);
 
-            // We keep the actual list item view as the last item in the holder array
-            return holder[mColumnCount];
+            return holder.row;
         }
 
-        View[] createViewHolder(ViewGroup parent) {
-            final View[] holder = new View[mColumnCount + 1];
-
+        RowViewHolder createViewHolder(ViewGroup parent) {
             final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
                     parent, false);
+            final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
+            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
             for (int i = 0; i < mColumnCount; i++) {
-                holder[i] = mChooserListAdapter.createView(row);
-                row.addView(holder[i]);
+                final View v = mChooserListAdapter.createView(row);
+                final int column = i;
+                v.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        startSelected(holder.itemIndices[column], false, true);
+                    }
+                });
+                v.setOnLongClickListener(new OnLongClickListener() {
+                    @Override
+                    public boolean onLongClick(View v) {
+                        showAppDetails(
+                                mChooserListAdapter.resolveInfoForPosition(
+                                        holder.itemIndices[column], true));
+                        return true;
+                    }
+                });
+                row.addView(v);
+                holder.cells[i] = v;
+
+                // Force height to be a given so we don't have visual disruption during scaling.
+                LayoutParams lp = v.getLayoutParams();
+                v.measure(spec, spec);
+                if (lp == null) {
+                    lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
+                    row.setLayoutParams(lp);
+                } else {
+                    lp.height = v.getMeasuredHeight();
+                }
+            }
+
+            // Pre-measure so we can scale later.
+            holder.measure();
+            LayoutParams lp = row.getLayoutParams();
+            if (lp == null) {
+                lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
+                row.setLayoutParams(lp);
+            } else {
+                lp.height = holder.measuredRowHeight;
             }
             row.setTag(holder);
-            holder[mColumnCount] = row;
             return holder;
         }
 
-        void bindViewHolder(int rowPosition, View[] holder) {
+        void bindViewHolder(int rowPosition, RowViewHolder holder) {
             final int start = getFirstRowPosition(rowPosition);
             final int startType = mChooserListAdapter.getPositionTargetType(start);
 
@@ -994,34 +1162,26 @@
                 end--;
             }
 
-            final ViewGroup row = (ViewGroup) holder[mColumnCount];
-
             if (startType == ChooserListAdapter.TARGET_SERVICE) {
-                row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
+                holder.row.setBackgroundColor(
+                        getColor(R.color.chooser_service_row_background_color));
             } else {
-                row.setBackground(null);
+                holder.row.setBackgroundColor(Color.TRANSPARENT);
+            }
+
+            final int oldHeight = holder.row.getLayoutParams().height;
+            holder.row.getLayoutParams().height = Math.max(1,
+                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+            if (holder.row.getLayoutParams().height != oldHeight) {
+                holder.row.requestLayout();
             }
 
             for (int i = 0; i < mColumnCount; i++) {
-                final View v = holder[i];
+                final View v = holder.cells[i];
                 if (start + i <= end) {
                     v.setVisibility(View.VISIBLE);
-                    final int itemIndex = start + i;
-                    mChooserListAdapter.bindView(itemIndex, v);
-                    v.setOnClickListener(new OnClickListener() {
-                        @Override
-                        public void onClick(View v) {
-                            startSelected(itemIndex, false, true);
-                        }
-                    });
-                    v.setOnLongClickListener(new OnLongClickListener() {
-                        @Override
-                        public boolean onLongClick(View v) {
-                            showAppDetails(
-                                    mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
-                            return true;
-                        }
-                    });
+                    holder.itemIndices[i] = start + i;
+                    mChooserListAdapter.bindView(holder.itemIndices[i], v);
                 } else {
                     v.setVisibility(View.GONE);
                 }
@@ -1048,6 +1208,25 @@
         }
     }
 
+    static class RowViewHolder {
+        final View[] cells;
+        final ViewGroup row;
+        int measuredRowHeight;
+        int[] itemIndices;
+
+        public RowViewHolder(ViewGroup row, int cellCount) {
+            this.row = row;
+            this.cells = new View[cellCount];
+            this.itemIndices = new int[cellCount];
+        }
+
+        public void measure() {
+            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            row.measure(spec, spec);
+            measuredRowHeight = row.getMeasuredHeight();
+        }
+    }
+
     static class ChooserTargetServiceConnection implements ServiceConnection {
         private final DisplayResolveInfo mOriginalTarget;
         private ComponentName mConnectedComponent;
@@ -1199,4 +1378,44 @@
             mSelectedTarget = null;
         }
     }
+
+    class OffsetDataSetObserver extends DataSetObserver {
+        private final AbsListView mListView;
+        private int mCachedViewType = -1;
+        private View mCachedView;
+
+        public OffsetDataSetObserver(AbsListView listView) {
+            mListView = listView;
+        }
+
+        @Override
+        public void onChanged() {
+            if (mResolverDrawerLayout == null) {
+                return;
+            }
+
+            final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
+            int offset = 0;
+            for (int i = 0; i < chooserTargetRows; i++)  {
+                final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
+                final int vt = mChooserRowAdapter.getItemViewType(pos);
+                if (vt != mCachedViewType) {
+                    mCachedView = null;
+                }
+                final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
+                int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
+
+                offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
+
+                if (vt >= 0) {
+                    mCachedViewType = vt;
+                    mCachedView = v;
+                } else {
+                    mCachedViewType = -1;
+                }
+            }
+
+            mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+        }
+    }
 }
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/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 1710489..ba0912a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -103,6 +103,8 @@
     private ResolverComparator mResolverComparator;
     private PickTargetOptionRequest mPickOptionRequest;
 
+    protected ResolverDrawerLayout mResolverDrawerLayout;
+
     private boolean mRegistered;
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override public void onSomePackagesChanged() {
@@ -253,6 +255,7 @@
             if (isVoiceInteraction()) {
                 rdl.setCollapsed(false);
             }
+            mResolverDrawerLayout = rdl;
         }
 
         if (title == null) {
@@ -1570,7 +1573,10 @@
 
         private void onBindView(View view, TargetInfo info) {
             final ViewHolder holder = (ViewHolder) view.getTag();
-            holder.text.setText(info.getDisplayLabel());
+            final CharSequence label = info.getDisplayLabel();
+            if (!TextUtils.equals(holder.text.getText(), label)) {
+                holder.text.setText(info.getDisplayLabel());
+            }
             if (showsExtendedInfo(info)) {
                 holder.text2.setVisibility(View.VISIBLE);
                 holder.text2.setText(info.getExtendedInfo());
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 c9b8119..afef763 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -19,8 +19,8 @@
 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.app.ActivityManager.PINNED_STACK_ID;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.getMode;
@@ -32,8 +32,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;
 
@@ -488,8 +486,14 @@
     }
 
     public void clearContentView() {
-        if (mNonClientDecorView.getChildCount() > 1) {
-            mNonClientDecorView.removeViewAt(1);
+        if (mNonClientDecorView != null) {
+            if (mNonClientDecorView.getChildCount() > 1) {
+                mNonClientDecorView.removeViewAt(1);
+            }
+        } else {
+            // This window doesn't have non client decor, so we need to just remove the children
+            // of the decor view.
+            mDecor.removeAllViews();
         }
     }
 
@@ -3171,16 +3175,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);
                 }
             }
@@ -3699,13 +3703,14 @@
         }
 
         /**
-         * Returns true if the Window is free floating and has a shadow. Note that non overlapping
-         * windows do not have a shadow since it could not be seen anyways (a small screen / tablet
+         * Returns true if the Window is free floating and has a shadow (although at some times
+         * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
+         * do not have a shadow since it could not be seen anyways (a small screen / tablet
          * "tiles" the windows side by side but does not overlap them).
          * @return Returns true when the window has a shadow created by the non client decor.
          **/
         private boolean windowHasShadow() {
-            return windowHasNonClientDecor() && getElevation() > 0;
+            return windowHasNonClientDecor() && nonClientDecorHasShadow(mWindow.mWorkspaceId);
         }
 
         void setWindow(PhoneWindow phoneWindow) {
@@ -4202,10 +4207,9 @@
         boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
                 attrs.type == TYPE_APPLICATION;
         mWorkspaceId = getWorkspaceId();
-        // Only a non floating application window on one of the allowed worksapces can get a non
+        // Only a non floating application window on one of the allowed workspaces can get a non
         // client decor.
-        if (!isFloating() && isApplication && mWorkspaceId != HOME_STACK_ID &&
-                mWorkspaceId < FIRST_DYNAMIC_STACK_ID) {
+        if (!isFloating() && isApplication && mWorkspaceId < FIRST_DYNAMIC_STACK_ID) {
             // Dependent on the brightness of the used title we either use the
             // dark or the light button frame.
             if (nonClientDecorView == null) {
@@ -4222,7 +4226,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));
@@ -5403,8 +5408,8 @@
      * @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) {
-        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
+    private static boolean hasNonClientDecor(int workspaceId) {
+        return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
     }
 
     /**
@@ -5412,14 +5417,8 @@
      * @param workspaceId The Id of the workspace which contains this window.
      * @Return Returns true if the window should show a shadow.
      **/
-    private boolean nonClientDecorHasShadow(int workspaceId) {
-        // TODO(skuhne): Add side by side mode here to add a decor.
-        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    @Override
-    public boolean hasNonClientDecorView() {
-        return mNonClientDecorView != null;
+    private static boolean nonClientDecorHasShadow(int workspaceId) {
+        return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
     }
 
     @Override
@@ -5432,4 +5431,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/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 3eeabcd..07bfce7 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -100,14 +100,6 @@
     }
 
     @Override
-    public void onAnimationStarted(int remainingFrameCount) {
-    }
-
-    @Override
-    public void onAnimationStopped() {
-    }
-
-    @Override
     public void dispatchWindowShown() {
     }
 }
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index 7b80ac2..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;
@@ -28,7 +29,6 @@
 import android.view.RenderNode;
 import android.view.ThreadedRenderer;
 import android.view.View;
-import android.view.ViewRootImpl;
 import android.widget.LinearLayout;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
@@ -53,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):
@@ -98,6 +98,9 @@
     // The resize frame renderer.
     private ResizeFrameThread mFrameRendererThread = null;
 
+    private Drawable mResizingBackgroundDrawable;
+    private Drawable mCaptionBackgroundDrawable;
+
     public NonClientDecorView(Context context) {
         super(context);
     }
@@ -110,18 +113,9 @@
         super(context, attrs, defStyle);
     }
 
-    public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
-        mOwner = owner;
-        mWindowHasShadow = windowHasShadow;
-        mShowDecor = showDecor;
-        updateCaptionVisibility();
-        if (mWindowHasShadow) {
-            initializeElevation();
-        }
-        // By changing the outline provider to BOUNDS, the window can remove its
-        // background without removing the shadow.
-        mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
-
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
         if (!mAttachedCallbacksToRootViewImpl) {
             // If there is no window callback installed there was no window set before. Set it now.
             // Note that our ViewRootImpl object will not change.
@@ -132,6 +126,31 @@
             // renderer about it.
             mFrameRendererThread.onConfigurationChange();
         }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mAttachedCallbacksToRootViewImpl) {
+            getViewRootImpl().removeWindowCallbacks(this);
+            mAttachedCallbacksToRootViewImpl = false;
+        }
+    }
+
+    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();
+        }
+        // By changing the outline provider to BOUNDS, the window can remove its
+        // background without removing the shadow.
+        mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
 
         findViewById(R.id.maximize_window).setOnClickListener(this);
         findViewById(R.id.close_window).setOnClickListener(this);
@@ -343,6 +362,26 @@
     }
 
     @Override
+    public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
+        if (mFrameRendererThread == null) {
+            return false;
+        }
+        return mFrameRendererThread.onContentDrawn(xOffset, yOffset, xSize, ySize);
+    }
+
+    @Override
+    public void onRequestDraw(boolean reportNextDraw) {
+        if (mFrameRendererThread != null) {
+            mFrameRendererThread.onRequsetDraw(reportNextDraw);
+        } else if (reportNextDraw) {
+            // If render thread is gone, just report immediately.
+            if (isAttachedToWindow()) {
+                getViewRootImpl().reportDrawFinish();
+            }
+        }
+    }
+
+    @Override
     public void onWindowDragResizeEnd() {
         releaseThreadedRenderer();
     }
@@ -372,10 +411,6 @@
      */
     private void releaseResources() {
         releaseThreadedRenderer();
-        if (mAttachedCallbacksToRootViewImpl) {
-            ViewRootImpl.removeWindowCallbacks(this);
-            mAttachedCallbacksToRootViewImpl = false;
-        }
     }
 
     /**
@@ -383,6 +418,8 @@
      * It starts with the creation and it ends once someone calls destroy().
      * Any size changes can be passed by a call to setTargetRect will passed to the thread and
      * executed via the Choreographer.
+     * TODO(b/24810450): Separate functionality from non-client-decor so that it can be used
+     * independently.
      */
     private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
         // This is containing the last requested size by a resize command. Note that this size might
@@ -406,7 +443,11 @@
         private int mLastXOffset;
         private int mLastYOffset;
 
+        // Whether to report when next frame is drawn or not.
+        private boolean mReportNextDraw;
+
         ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
+            setName("ResizeFrame");
             mRenderer = renderer;
 
             // Create the render nodes for our frame and backdrop which can be resized independently
@@ -419,7 +460,9 @@
 
             // Set the initial bounds and draw once so that we do not get a broken frame.
             mTargetRect.set(initialBounds);
-            changeWindowSize(initialBounds);
+            synchronized (this) {
+                changeWindowSizeLocked(initialBounds);
+            }
 
             // Kick off our draw thread.
             start();
@@ -442,10 +485,12 @@
          * The window got replaced due to a configuration change.
          */
         public void onConfigurationChange() {
-            if (mRenderer != null) {
-                // Enforce a window redraw.
-                mOldTargetRect.set(0, 0, 0, 0);
-                pingRenderLocked();
+            synchronized (this) {
+                if (mRenderer != null) {
+                    // Enforce a window redraw.
+                    mOldTargetRect.set(0, 0, 0, 0);
+                    pingRenderLocked();
+                }
             }
         }
 
@@ -459,7 +504,8 @@
                     // Invalidate the current content bounds.
                     mRenderer.setContentDrawBounds(0, 0, 0, 0);
 
-                    // Remove the render nodes again (see comment above - better to do that only once).
+                    // Remove the render nodes again
+                    // (see comment above - better to do that only once).
                     mRenderer.removeRenderNode(mFrameNode);
                     mRenderer.removeRenderNode(mBackdropNode);
 
@@ -493,18 +539,56 @@
          */
         @Override
         public void doFrame(long frameTimeNanos) {
-            if (mRenderer == null) {
-                // Tell the looper to stop. We are done.
-                Looper.myLooper().quit();
-                return;
-            }
-            // Prevent someone from changing this while we are copying.
             synchronized (this) {
+                if (mRenderer == null) {
+                    reportDrawIfNeeded();
+                    // Tell the looper to stop. We are done.
+                    Looper.myLooper().quit();
+                    return;
+                }
                 mNewTargetRect.set(mTargetRect);
+                if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
+                    mOldTargetRect.set(mNewTargetRect);
+                    changeWindowSizeLocked(mNewTargetRect);
+                }
             }
-            if (!mNewTargetRect.equals(mOldTargetRect)) {
-                mOldTargetRect.set(mNewTargetRect);
-                changeWindowSize(mNewTargetRect);
+        }
+
+        /**
+         * The content is about to be drawn and we got the location of where it will be shown.
+         * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
+         * if the previous call was ignored since the size was unknown.
+         * @param xOffset The x offset where the content is drawn to.
+         * @param yOffset The y offset where the content is drawn to.
+         * @param xSize The width size of the content. This should not be 0.
+         * @param ySize The height of the content.
+         * @return true if a frame should be requested after the content is drawn; false otherwise.
+         */
+        public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
+            synchronized (this) {
+                final boolean firstCall = mLastContentWidth == 0;
+                // The current content buffer is drawn here.
+                mLastContentWidth = xSize;
+                mLastContentHeight = ySize - mLastCaptionHeight;
+                mLastXOffset = xOffset;
+                mLastYOffset = yOffset;
+
+                mRenderer.setContentDrawBounds(
+                        mLastXOffset,
+                        mLastYOffset + mLastCaptionHeight,
+                        mLastXOffset + mLastContentWidth,
+                        mLastYOffset + mLastCaptionHeight + mLastContentHeight);
+                // If this was the first call and changeWindowSizeLocked got already called prior
+                // to us, we should re-issue a changeWindowSizeLocked now.
+                return firstCall && (mLastCaptionHeight != 0 || !mShowDecor);
+            }
+        }
+
+        public void onRequsetDraw(boolean reportNextDraw) {
+            synchronized (this) {
+                mReportNextDraw = reportNextDraw;
+                mOldTargetRect.set(0, 0, 0, 0);
+                pingRenderLocked();
             }
         }
 
@@ -512,33 +596,26 @@
          * Resizing the frame to fit the new window size.
          * @param newBounds The window bounds which needs to be drawn.
          */
-        private void changeWindowSize(Rect newBounds) {
-            long startTime = System.currentTimeMillis();
-
+        private void changeWindowSizeLocked(Rect newBounds) {
             // While a configuration change is taking place the view hierarchy might become
             // inaccessible. For that case we remember the previous metrics to avoid flashes.
+            // Note that even when there is no visible caption, the caption child will exist.
             View caption = getChildAt(0);
-            View content = getChildAt(1);
-            if (caption != null && content != null) {
-                int captionHeight = caption.getHeight();
-                int contentWidth = content.getWidth();
-                int contentHeight = content.getHeight();
-                // Get the draw position within our surface (shadow offsets).
-                int[] surfaceOrigin = new int[2];
-                surfaceOrigin[0] = 0;
-                surfaceOrigin[1] = 0;
-                getLocationInSurface(surfaceOrigin);
-                // Only update if a layout has already be performed (which might not be after a
-                // relayout. Otherwise use the previous values for the content.
-                if (captionHeight != 0 && contentWidth != 0 && contentHeight != 0) {
+            if (caption != null) {
+                final int captionHeight = caption.getHeight();
+                // The caption height will probably never dynamically change while we are resizing.
+                // Once set to something other then 0 it should be kept that way.
+                if (captionHeight != 0) {
+                    // Remember the height of the caption.
                     mLastCaptionHeight = captionHeight;
-                    mLastXOffset = surfaceOrigin[0];
-                    mLastYOffset = surfaceOrigin[1];
-                    mLastContentWidth = contentWidth;
-                    mLastContentHeight = contentHeight;
                 }
             }
-
+            // Make sure that the other thread has already prepared the render draw calls for the
+            // content. If any size is 0, we have to wait for it to be drawn first.
+            if ((mLastCaptionHeight == 0 && mShowDecor) ||
+                    mLastContentWidth == 0 || mLastContentHeight == 0) {
+                return;
+            }
             // Since the surface is spanning the entire screen, we have to add the start offset of
             // the bounds to get to the surface location.
             final int left = mLastXOffset + newBounds.left;
@@ -554,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,
@@ -562,21 +640,27 @@
 
             // 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);
 
-            // The current content buffer is drawn here.
-            mRenderer.setContentDrawBounds(
-                    mLastXOffset,
-                    mLastYOffset + mLastCaptionHeight,
-                    mLastXOffset + mLastContentWidth,
-                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
-
             // We need to render both rendered nodes explicitly.
             mRenderer.drawRenderNode(mFrameNode);
             mRenderer.drawRenderNode(mBackdropNode);
+
+            reportDrawIfNeeded();
+        }
+
+        /**
+         * Notify view root that a frame has been drawn by us, if it has requested so.
+         */
+        private void reportDrawIfNeeded() {
+            if (mReportNextDraw) {
+                if (isAttachedToWindow()) {
+                    getViewRootImpl().reportDrawFinish();
+                }
+                mReportNextDraw = false;
+            }
         }
 
         /**
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/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 7679624..c4347f8 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -69,6 +69,12 @@
     private int mCollapsibleHeight;
     private int mUncollapsibleHeight;
 
+    /**
+     * The height in pixels of reserved space added to the top of the collapsed UI;
+     * e.g. chooser targets
+     */
+    private int mCollapsibleHeightReserved;
+
     private int mTopOffset;
 
     private boolean mIsDragging;
@@ -153,12 +159,62 @@
         }
     }
 
+    public void setCollapsibleHeightReserved(int heightPixels) {
+        final int oldReserved = mCollapsibleHeightReserved;
+        mCollapsibleHeightReserved = heightPixels;
+
+        final int dReserved = mCollapsibleHeightReserved - oldReserved;
+        if (dReserved != 0 && mIsDragging) {
+            mLastTouchY -= dReserved;
+        }
+
+        final int oldCollapsibleHeight = mCollapsibleHeight;
+        mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight());
+
+        if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) {
+            return;
+        }
+
+        invalidate();
+    }
+
     private boolean isMoving() {
         return mIsDragging || !mScroller.isFinished();
     }
 
+    private boolean isDragging() {
+        return mIsDragging || getNestedScrollAxes() == SCROLL_AXIS_VERTICAL;
+    }
+
+    private boolean updateCollapseOffset(int oldCollapsibleHeight, boolean remainClosed) {
+        if (oldCollapsibleHeight == mCollapsibleHeight) {
+            return false;
+        }
+
+        if (isLaidOut()) {
+            final boolean isCollapsedOld = mCollapseOffset != 0;
+            if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
+                    && mCollapseOffset == oldCollapsibleHeight)) {
+                // Stay closed even at the new height.
+                mCollapseOffset = mCollapsibleHeight;
+            } else {
+                mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+            }
+            final boolean isCollapsedNew = mCollapseOffset != 0;
+            if (isCollapsedOld != isCollapsedNew) {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            }
+        } else {
+            // Start out collapsed at first unless we restored state for otherwise
+            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
+        }
+        return true;
+    }
+
     private int getMaxCollapsedHeight() {
-        return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
+        return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight)
+                + mCollapsibleHeightReserved;
     }
 
     public void setOnDismissedListener(OnDismissedListener listener) {
@@ -676,7 +732,7 @@
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             if (lp.alwaysShow && child.getVisibility() != GONE) {
                 measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
-                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+                heightUsed += getHeightUsed(child);
             }
         }
 
@@ -688,7 +744,7 @@
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             if (!lp.alwaysShow && child.getVisibility() != GONE) {
                 measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
-                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+                heightUsed += getHeightUsed(child);
             }
         }
 
@@ -697,30 +753,43 @@
                 heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
         mUncollapsibleHeight = heightUsed - mCollapsibleHeight;
 
-        if (isLaidOut()) {
-            final boolean isCollapsedOld = mCollapseOffset != 0;
-            if (oldCollapsibleHeight < mCollapsibleHeight
-                    && mCollapseOffset == oldCollapsibleHeight) {
-                // Stay closed even at the new height.
-                mCollapseOffset = mCollapsibleHeight;
-            } else {
-                mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
-            }
-            final boolean isCollapsedNew = mCollapseOffset != 0;
-            if (isCollapsedOld != isCollapsedNew) {
-                notifyViewAccessibilityStateChangedIfNeeded(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
-            }
-        } else {
-            // Start out collapsed at first unless we restored state for otherwise
-            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
-        }
+        updateCollapseOffset(oldCollapsibleHeight, !isDragging());
 
         mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
 
         setMeasuredDimension(sourceWidth, heightSize);
     }
 
+    private int getHeightUsed(View child) {
+        // This method exists because we're taking a fast path at measuring ListViews that
+        // lets us get away with not doing the more expensive wrap_content measurement which
+        // imposes double child view measurement costs. If we're looking at a ListView, we can
+        // check against the lowest child view plus padding and margin instead of the actual
+        // measured height of the ListView. This lets the ListView hang off the edge when
+        // all of the content would fit on-screen.
+
+        int heightUsed = child.getMeasuredHeight();
+        if (child instanceof AbsListView) {
+            final AbsListView lv = (AbsListView) child;
+            final int lvPaddingBottom = lv.getPaddingBottom();
+
+            int lowest = 0;
+            for (int i = 0, N = lv.getChildCount(); i < N; i++) {
+                final int bottom = lv.getChildAt(i).getBottom() + lvPaddingBottom;
+                if (bottom > lowest) {
+                    lowest = bottom;
+                }
+            }
+
+            if (lowest < heightUsed) {
+                heightUsed = lowest;
+            }
+        }
+
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        return lp.topMargin + heightUsed + lp.bottomMargin;
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final int width = getWidth();
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_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 32a877a..3d96fab 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -749,57 +749,57 @@
 static const JNINativeMethod gMethods[] = {
     {"finalizer", "(J)V", (void*) CanvasJNI::finalizer},
     {"initRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
-    {"native_setBitmap", "(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
-    {"native_isOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
-    {"native_getWidth","(J)I", (void*) CanvasJNI::getWidth},
-    {"native_getHeight","(J)I", (void*) CanvasJNI::getHeight},
-    {"native_setHighContrastText","(JZ)V", (void*) CanvasJNI::setHighContrastText},
-    {"native_save","(JI)I", (void*) CanvasJNI::save},
-    {"native_saveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
-    {"native_saveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
-    {"native_getSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
-    {"native_restore","(JZ)V", (void*) CanvasJNI::restore},
-    {"native_restoreToCount","(JIZ)V", (void*) CanvasJNI::restoreToCount},
-    {"native_getCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
-    {"native_setMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
-    {"native_concat","(JJ)V", (void*) CanvasJNI::concat},
-    {"native_rotate","(JF)V", (void*) CanvasJNI::rotate},
-    {"native_scale","(JFF)V", (void*) CanvasJNI::scale},
-    {"native_skew","(JFF)V", (void*) CanvasJNI::skew},
-    {"native_translate","(JFF)V", (void*) CanvasJNI::translate},
-    {"native_getClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
-    {"native_quickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
-    {"native_quickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
-    {"native_clipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
-    {"native_clipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
-    {"native_clipRegion","(JJI)Z", (void*) CanvasJNI::clipRegion},
-    {"native_drawColor","(JII)V", (void*) CanvasJNI::drawColor},
-    {"native_drawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
-    {"native_drawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
-    {"native_drawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
-    {"native_drawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
-    {"native_drawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
-    {"native_drawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
-    {"native_drawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
-    {"native_drawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
-    {"native_drawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
-    {"native_drawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
-    {"native_drawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
-    {"native_drawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
-    {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
-    {"native_drawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
-    {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap},
-    {"nativeDrawBitmapMatrix", "(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix},
-    {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
-    {"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
-    {"nativeDrawBitmapMesh", "(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
-    {"native_drawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
-    {"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
-    {"native_drawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
-    {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
-    {"native_drawTextOnPath","(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
-    {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
-    {"nativeSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+    {"native_setBitmap", "!(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
+    {"native_isOpaque","!(J)Z", (void*) CanvasJNI::isOpaque},
+    {"native_getWidth","!(J)I", (void*) CanvasJNI::getWidth},
+    {"native_getHeight","!(J)I", (void*) CanvasJNI::getHeight},
+    {"native_setHighContrastText","!(JZ)V", (void*) CanvasJNI::setHighContrastText},
+    {"native_save","!(JI)I", (void*) CanvasJNI::save},
+    {"native_saveLayer","!(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
+    {"native_saveLayerAlpha","!(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
+    {"native_getSaveCount","!(J)I", (void*) CanvasJNI::getSaveCount},
+    {"native_restore","!(JZ)V", (void*) CanvasJNI::restore},
+    {"native_restoreToCount","!(JIZ)V", (void*) CanvasJNI::restoreToCount},
+    {"native_getCTM", "!(JJ)V", (void*)CanvasJNI::getCTM},
+    {"native_setMatrix","!(JJ)V", (void*) CanvasJNI::setMatrix},
+    {"native_concat","!(JJ)V", (void*) CanvasJNI::concat},
+    {"native_rotate","!(JF)V", (void*) CanvasJNI::rotate},
+    {"native_scale","!(JFF)V", (void*) CanvasJNI::scale},
+    {"native_skew","!(JFF)V", (void*) CanvasJNI::skew},
+    {"native_translate","!(JFF)V", (void*) CanvasJNI::translate},
+    {"native_getClipBounds","!(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+    {"native_quickReject","!(JJ)Z", (void*) CanvasJNI::quickRejectPath},
+    {"native_quickReject","!(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+    {"native_clipRect","!(JFFFFI)Z", (void*) CanvasJNI::clipRect},
+    {"native_clipPath","!(JJI)Z", (void*) CanvasJNI::clipPath},
+    {"native_clipRegion","!(JJI)Z", (void*) CanvasJNI::clipRegion},
+    {"native_drawColor","!(JII)V", (void*) CanvasJNI::drawColor},
+    {"native_drawPaint","!(JJ)V", (void*) CanvasJNI::drawPaint},
+    {"native_drawPoint", "!(JFFJ)V", (void*) CanvasJNI::drawPoint},
+    {"native_drawPoints", "!(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
+    {"native_drawLine", "!(JFFFFJ)V", (void*) CanvasJNI::drawLine},
+    {"native_drawLines", "!(J[FIIJ)V", (void*) CanvasJNI::drawLines},
+    {"native_drawRect","!(JFFFFJ)V", (void*) CanvasJNI::drawRect},
+    {"native_drawRegion", "!(JJJ)V", (void*) CanvasJNI::drawRegion },
+    {"native_drawRoundRect","!(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
+    {"native_drawCircle","!(JFFFJ)V", (void*) CanvasJNI::drawCircle},
+    {"native_drawOval","!(JFFFFJ)V", (void*) CanvasJNI::drawOval},
+    {"native_drawArc","!(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
+    {"native_drawPath","!(JJJ)V", (void*) CanvasJNI::drawPath},
+    {"nativeDrawVertices", "!(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+    {"native_drawNinePatch", "!(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
+    {"native_drawBitmap","!(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap},
+    {"nativeDrawBitmapMatrix", "!(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+    {"native_drawBitmap","!(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
+    {"native_drawBitmap", "!(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+    {"nativeDrawBitmapMesh", "!(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+    {"native_drawText","!(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
+    {"native_drawText","!(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
+    {"native_drawTextRun","!(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
+    {"native_drawTextRun","!(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
+    {"native_drawTextOnPath","!(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
+    {"native_drawTextOnPath","!(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
+    {"nativeSetDrawFilter", "!(JJ)V", (void*) CanvasJNI::setDrawFilter},
     {"freeCaches", "()V", (void*) CanvasJNI::freeCaches},
     {"freeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches}
 };
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_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 614e82f..c94bc64 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -24,6 +24,7 @@
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/stat.h>
 
 #include <private/android_filesystem_config.h> // for AID_SYSTEM
 
@@ -171,11 +172,32 @@
                     exit(1);
                 }
 
-                execl(AssetManager::IDMAP_BIN, AssetManager::IDMAP_BIN, "--scan",
-                        AssetManager::OVERLAY_DIR, AssetManager::TARGET_PACKAGE_NAME,
-                        AssetManager::TARGET_APK_PATH, AssetManager::IDMAP_DIR, (char*)NULL);
-                ALOGE("failed to execl for idmap: %s", strerror(errno));
-                exit(1); // should never get here
+                // Generic idmap parameters
+                const char* argv[7];
+                int argc = 0;
+                struct stat st;
+
+                memset(argv, NULL, sizeof(argv));
+                argv[argc++] = AssetManager::IDMAP_BIN;
+                argv[argc++] = "--scan";
+                argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
+                argv[argc++] = AssetManager::TARGET_APK_PATH;
+                argv[argc++] = AssetManager::IDMAP_DIR;
+
+                // Directories to scan for overlays
+                // /vendor/overlay
+                if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
+                    argv[argc++] = AssetManager::OVERLAY_DIR;
+                 }
+
+                // Finally, invoke idmap (if any overlay directory exists)
+                if (argc > 5) {
+                    execv(AssetManager::IDMAP_BIN, (char* const*)argv);
+                    ALOGE("failed to execl for idmap: %s", strerror(errno));
+                    exit(1); // should never get here
+                } else {
+                    exit(0);
+                }
             }
             break;
         default: // parent
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 437bd19..24eb961 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -267,7 +267,7 @@
     { "nativeDispose",
             "(J)V",
             (void*)nativeDispose },
-    { "nativeScheduleVsync", "(J)V",
+    { "nativeScheduleVsync", "!(J)V",
             (void*)nativeScheduleVsync }
 };
 
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index 460c1a1..b64acc3 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -174,24 +174,24 @@
 const char* const kClassPathName = "android/view/DisplayListCanvas";
 
 static JNINativeMethod gMethods[] = {
-    { "nIsAvailable",       "()Z",             (void*) android_view_DisplayListCanvas_isAvailable },
-    { "nInsertReorderBarrier","(JZ)V",         (void*) android_view_DisplayListCanvas_insertReorderBarrier },
+    { "nIsAvailable",       "!()Z",             (void*) android_view_DisplayListCanvas_isAvailable },
+    { "nInsertReorderBarrier","!(JZ)V",         (void*) android_view_DisplayListCanvas_insertReorderBarrier },
 
-    { "nCallDrawGLFunction", "(JJ)V",          (void*) android_view_DisplayListCanvas_callDrawGLFunction },
+    { "nCallDrawGLFunction", "!(JJ)V",          (void*) android_view_DisplayListCanvas_callDrawGLFunction },
 
-    { "nDrawRoundRect",     "(JJJJJJJJ)V",     (void*) android_view_DisplayListCanvas_drawRoundRectProps },
-    { "nDrawCircle",        "(JJJJJ)V",        (void*) android_view_DisplayListCanvas_drawCircleProps },
+    { "nDrawRoundRect",     "!(JJJJJJJJ)V",     (void*) android_view_DisplayListCanvas_drawRoundRectProps },
+    { "nDrawCircle",        "!(JJJJJ)V",        (void*) android_view_DisplayListCanvas_drawCircleProps },
 
-    { "nFinishRecording",   "(J)J",            (void*) android_view_DisplayListCanvas_finishRecording },
-    { "nDrawRenderNode",    "(JJ)V",           (void*) android_view_DisplayListCanvas_drawRenderNode },
+    { "nFinishRecording",   "!(J)J",            (void*) android_view_DisplayListCanvas_finishRecording },
+    { "nDrawRenderNode",    "!(JJ)V",           (void*) android_view_DisplayListCanvas_drawRenderNode },
 
-    { "nCreateDisplayListCanvas", "(II)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
-    { "nResetDisplayListCanvas", "(JII)V",     (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
+    { "nCreateDisplayListCanvas", "!(II)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
+    { "nResetDisplayListCanvas", "!(JII)V",     (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
 
-    { "nDrawLayer",               "(JJ)V",     (void*) android_view_DisplayListCanvas_drawLayer },
+    { "nDrawLayer",               "!(JJ)V",     (void*) android_view_DisplayListCanvas_drawLayer },
 
-    { "nGetMaximumTextureWidth",  "()I",       (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
-    { "nGetMaximumTextureHeight", "()I",       (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
+    { "nGetMaximumTextureWidth",  "!()I",       (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
+    { "nGetMaximumTextureHeight", "!()I",       (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
 };
 
 static JNINativeMethod gActivityThreadMethods[] = {
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 148e7c1..39eda58 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -241,7 +241,6 @@
     <protected-broadcast android:name="android.intent.action.DREAMING_STARTED" />
     <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
     <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
-    <protected-broadcast android:name="android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN" />
 
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
@@ -1715,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.
@@ -2075,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/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index dcdfb6c..41726fb 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -85,7 +85,7 @@
 
     <ListView
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="match_parent"
             android:id="@+id/resolver_list"
             android:clipToPadding="false"
             android:scrollbarStyle="outsideOverlay"
diff --git a/core/res/res/layout/list_content.xml b/core/res/res/layout/list_content.xml
index 1414032..45ade4d 100644
--- a/core/res/res/layout/list_content.xml
+++ b/core/res/res/layout/list_content.xml
@@ -44,8 +44,7 @@
             
         <ListView android:id="@android:id/list"
                 android:layout_width="match_parent" 
-                android:layout_height="match_parent"
-                android:drawSelectorOnTop="false" />
+                android:layout_height="match_parent" />
         <TextView android:id="@+android:id/internalEmpty"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index 4e895b0..f073c33 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -41,6 +41,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:padding="@dimen/preference_fragment_padding_side"
+        android:textAppearance="?android:attr/textAppearanceMedium"
         android:gravity="center"
         android:visibility="gone" />
 
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 1c496f6..0a7ac77 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -70,6 +70,7 @@
               android:minLines="2"
               android:maxLines="2"
               android:gravity="top|center_horizontal"
-              android:ellipsize="marquee" />
+              android:ellipsize="marquee"
+              android:visibility="gone" />
 </LinearLayout>
 
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 38baab2..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>
@@ -1029,7 +1029,7 @@
     <string name="adb_active_notification_message" msgid="1016654627626476142">"Toca per desactivar la depuració USB"</string>
     <string name="select_input_method" msgid="8547250819326693584">"Canvia el teclat"</string>
     <string name="configure_input_methods" msgid="4769971288371946846">"Tria els teclats"</string>
-    <string name="show_ime" msgid="9157568568695230830">"Mostra mètode d\'entrada."</string>
+    <string name="show_ime" msgid="9157568568695230830">"Mostra mètode d\'introducció"</string>
     <string name="hardware" msgid="7517821086888990278">"Maquinari"</string>
     <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selecciona una disposició de teclat"</string>
     <string name="select_keyboard_layout_notification_message" msgid="4465907700449257063">"Toca per seleccionar una disposició de teclat."</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 60f0502..82b550b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -143,7 +143,7 @@
     <string name="fcError" msgid="3327560126588500777">"Forbindelsesproblemer eller ugyldig funktionskode."</string>
     <string name="httpErrorOk" msgid="1191919378083472204">"OK"</string>
     <string name="httpError" msgid="7956392511146698522">"Der opstod en netværksfejl."</string>
-    <string name="httpErrorLookup" msgid="4711687456111963163">"Webadressen kunne ikke findes."</string>
+    <string name="httpErrorLookup" msgid="4711687456111963163">"Webadressen blev ikke fundet."</string>
     <string name="httpErrorUnsupportedAuthScheme" msgid="6299980280442076799">"Ordningen for webstedsgodkendelse understøttes ikke."</string>
     <string name="httpErrorAuth" msgid="1435065629438044534">"Der kunne ikke godkendes."</string>
     <string name="httpErrorProxyAuth" msgid="1788207010559081331">"Godkendelse via proxyserveren mislykkedes."</string>
@@ -155,7 +155,7 @@
     <string name="httpErrorFailedSslHandshake" msgid="96549606000658641">"Der kunne ikke oprettes en sikker forbindelse."</string>
     <string name="httpErrorBadUrl" msgid="3636929722728881972">"Siden kunne ikke åbnes, fordi webadressen er ugyldig."</string>
     <string name="httpErrorFile" msgid="2170788515052558676">"Der kunne ikke fås adgang til filen."</string>
-    <string name="httpErrorFileNotFound" msgid="6203856612042655084">"Den ønskede fil kunne ikke findes."</string>
+    <string name="httpErrorFileNotFound" msgid="6203856612042655084">"Den ønskede fil blev ikke fundet."</string>
     <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Der behandles for mange anmodninger. Prøv igen senere."</string>
     <string name="notification_title" msgid="8967710025036163822">"Loginfejl for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
     <string name="contentServiceSync" msgid="8353523060269335667">"Synkroniser"</string>
@@ -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>
@@ -248,7 +248,7 @@
     <string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"undersøge indholdet i et vindue, du interagerer med."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"aktivere Udforsk ved berøring"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="5800552516779249356">"De emner, der trykkes på, læses højt, og skærmen kan udforskes ved hjælp af bevægelser."</string>
-    <string name="capability_title_canRequestEnhancedWebAccessibility" msgid="1739881766522594073">"aktivere forbedrede webhjælpefunktioner"</string>
+    <string name="capability_title_canRequestEnhancedWebAccessibility" msgid="1739881766522594073">"Aktivér udvidede webhjælpefunktioner"</string>
     <string name="capability_desc_canRequestEnhancedWebAccessibility" msgid="7881063961507511765">"Der installeres muligvis scripts for at gøre appindhold mere tilgængeligt."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2103440391902412174">"observere tekst, du skriver"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="7463135292204152818">"Dette omfatter personlige data såsom kreditkortnumre og adgangskoder."</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 45d9dc3..ae6598b 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -51,7 +51,7 @@
     <string name="serviceDisabled" msgid="1937553226592516411">"سرویس غیرفعال شده است."</string>
     <string name="serviceRegistered" msgid="6275019082598102493">"ثبت با موفقیت انجام شد"</string>
     <string name="serviceErased" msgid="1288584695297200972">"پاک کردن با موفقیت انجام شد."</string>
-    <string name="passwordIncorrect" msgid="7612208839450128715">"رمز ورود اشتباه است."</string>
+    <string name="passwordIncorrect" msgid="7612208839450128715">"گذرواژه اشتباه است."</string>
     <string name="mmiComplete" msgid="8232527495411698359">"‏MMI کامل شد."</string>
     <string name="badPin" msgid="9015277645546710014">"‏پین قدیمی که نوشته‎اید صحیح نیست."</string>
     <string name="badPuk" msgid="5487257647081132201">"‏PUK که نوشته‌اید صحیح نیست."</string>
@@ -74,7 +74,7 @@
     <string name="CfMmi" msgid="5123218989141573515">"هدایت تماس"</string>
     <string name="CwMmi" msgid="9129678056795016867">"انتظار تماس"</string>
     <string name="BaMmi" msgid="455193067926770581">"محدودیت تماس"</string>
-    <string name="PwdMmi" msgid="7043715687905254199">"تغییر رمز ورود"</string>
+    <string name="PwdMmi" msgid="7043715687905254199">"تغییر گذرواژه"</string>
     <string name="PinMmi" msgid="3113117780361190304">"تغییر پین"</string>
     <string name="CnipMmi" msgid="3110534680557857162">"شماره تماس حاضر"</string>
     <string name="CnirMmi" msgid="3062102121430548731">"شماره تماس محدود شده"</string>
@@ -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>
@@ -495,7 +495,7 @@
     <string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"به دارنده امکان می‌دهد به سرویس‌های شرکت مخابراتی متصل شود. هرگز نباید برای برنامه‌های عادی مورد نیاز باشد."</string>
     <string name="permlab_access_notification_policy" msgid="4247510821662059671">"دسترسی به حالت «مزاحم نشوید»"</string>
     <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"به برنامه امکان می‌دهد پیکربندی «مزاحم نشوید» را بخواند و بنویسد."</string>
-    <string name="policylab_limitPassword" msgid="4497420728857585791">"تنظیم قوانین رمز ورود"</string>
+    <string name="policylab_limitPassword" msgid="4497420728857585791">"تنظیم قوانین گذرواژه"</string>
     <string name="policydesc_limitPassword" msgid="2502021457917874968">"کنترل طول و نوع نویسه‌هایی که در گذرواژه و پین قفل صفحه مجاز است."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"نمایش تلاش‌های قفل گشایی صفحه"</string>
     <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"‏تعداد گذرواژه‎های نادرست تایپ شده را هنگام بازکردن قفل صفحه کنترل می‌کند، و اگر دفعات زیادی گذرواژه نادرست وارد شود رایانهٔ لوحی را قفل می‌کند و همه داده‎های رایانهٔ لوحی را پاک می‌کند."</string>
@@ -532,7 +532,7 @@
     <item msgid="7897544654242874543">"محل کار"</item>
     <item msgid="1103601433382158155">"نمابر محل کار"</item>
     <item msgid="1735177144948329370">"نمابر خانه"</item>
-    <item msgid="603878674477207394">"پیجر"</item>
+    <item msgid="603878674477207394">"پی‌جو"</item>
     <item msgid="1650824275177931637">"سایر موارد"</item>
     <item msgid="9192514806975898961">"سفارشی"</item>
   </string-array>
@@ -575,7 +575,7 @@
     <string name="phoneTypeWork" msgid="8863939667059911633">"محل کار"</string>
     <string name="phoneTypeFaxWork" msgid="3517792160008890912">"نمابر محل کار"</string>
     <string name="phoneTypeFaxHome" msgid="2067265972322971467">"نمابر خانه"</string>
-    <string name="phoneTypePager" msgid="7582359955394921732">"پیجر"</string>
+    <string name="phoneTypePager" msgid="7582359955394921732">"پی‌جو"</string>
     <string name="phoneTypeOther" msgid="1544425847868765990">"سایر موارد"</string>
     <string name="phoneTypeCallback" msgid="2712175203065678206">"برگرداندن تماس"</string>
     <string name="phoneTypeCar" msgid="8738360689616716982">"خودرو"</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>
@@ -700,9 +700,9 @@
     <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"‏تلاش‎های زیادی برای کشیدن الگو صورت گرفته است"</string>
     <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"‏برای بازگشایی قفل، با حساب Google خود وارد سیستم شوید."</string>
     <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"نام کاربری (ایمیل)"</string>
-    <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"رمز ورود"</string>
+    <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"گذرواژه"</string>
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"ورود به سیستم"</string>
-    <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"نام کاربر یا رمز ورود نامعتبر است."</string>
+    <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"نام کاربر یا گذرواژه نامعتبر است."</string>
     <string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"‏نام کاربری یا گذرواژهٔ خود را فراموش کردید؟\nاز "<b>"google.com/accounts/recovery"</b>" بازدید کنید."</string>
     <string name="lockscreen_glogin_checking_password" msgid="7114627351286933867">"در حال بررسی..."</string>
     <string name="lockscreen_unlock_label" msgid="737440483220667054">"بازگشایی قفل"</string>
@@ -786,7 +786,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"به برنامه اجازه می‌دهد تا پیام‌ها را به صندوق دریافت پست صوتی شما اضافه کند."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"تغییر مجوزهای مکان جغرافیایی مرورگر"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"‏به برنامه اجازه می‎دهد تا مجوزهای جغرافیایی مرورگر را تغییر دهد. برنامه‎های مخرب می‎توانند از آن استفاده کنند تا اطلاعات موقعیت مکانی را به سایت‌های وب کتابخانه بفرستند."</string>
-    <string name="save_password_message" msgid="767344687139195790">"می‌خواهید مرورگر این رمز ورود را به خاطر داشته باشد؟"</string>
+    <string name="save_password_message" msgid="767344687139195790">"می‌خواهید مرورگر این گذرواژه را به خاطر داشته باشد؟"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"اکنون نه"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"به خاطر سپردن"</string>
     <string name="save_password_never" msgid="8274330296785855105">"هیچ‌وقت"</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 72b1c54..ab541ea 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -290,7 +290,7 @@
     <string name="permdesc_enableCarMode" msgid="4853187425751419467">"Consente all\'applicazione di abilitare la modalità automobile."</string>
     <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"chiusura altre applicazioni"</string>
     <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"Consente all\'applicazione di terminare i processi in background di altre applicazioni. Ciò potrebbe causare l\'interruzione di altre applicazioni."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"spostamento sopra altre app"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"posizionamento davanti ad altre app"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Consente all\'applicazione di spostarsi sopra ad altre applicazioni o parti dell\'interfaccia utente. Potrebbe interferire con il tuo utilizzo dell\'interfaccia in qualsiasi applicazione o cambiare ciò che credi di vedere in altre applicazioni."</string>
     <string name="permlab_persistentActivity" msgid="8841113627955563938">"esecuzione permanente delle applicazioni"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"Consente all\'applicazione di rendere persistenti in memoria alcune sue parti. Ciò può limitare la memoria disponibile per altre applicazioni, rallentando il tablet."</string>
@@ -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..4c1fc88 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -87,7 +87,7 @@
     <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"कलर ID पूर्वनिर्धारितदेखि प्रतिबन्धित छैन। अर्को कल: प्रतिबन्धित छ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"सेवाको व्यवस्था छैन।"</string>
-    <string name="CLIRPermanent" msgid="3377371145926835671">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
+    <string name="CLIRPermanent" msgid="3377371145926835671">"तपाईँ कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
     <string name="RestrictedChangedTitle" msgid="5592189398956187498">"प्रतिबन्धित पहुँच परिवर्तन भएको छ"</string>
     <string name="RestrictedOnData" msgid="8653794784690065540">"डेटा सेवा रोकिएको छ।"</string>
     <string name="RestrictedOnEmergency" msgid="6581163779072833665">"आपतकालीन सेवा रोकिएको छ।"</string>
@@ -198,7 +198,7 @@
     <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"तपाईंको TV बन्द हुनेछ।"</string>
     <string name="shutdown_confirm" product="watch" msgid="3490275567476369184">"तपाईँको घडी बन्द गरिने छ।"</string>
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"तपाईँको फोन बन्द हुने छ।"</string>
-    <string name="shutdown_confirm_question" msgid="2906544768881136183">"के तपाईं बन्द गर्न चाहनुहुन्छ?"</string>
+    <string name="shutdown_confirm_question" msgid="2906544768881136183">"के तपाईँ बन्द गर्न चाहनुहुन्छ?"</string>
     <string name="reboot_safemode_title" msgid="7054509914500140361">"सुरक्षित मोडमा पुनःबुट गर्नुहोस्"</string>
     <string name="reboot_safemode_confirm" msgid="55293944502784668">"सुरक्षित मोडमा तपाईँ पुनःबुट गर्न चाहनु हुन्छ? तपाईँले स्थापना गरेका सबै तेस्रो पक्षका अनुप्रयोगहरूलाई असक्षम गराउने छ।"</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"नयाँ"</string>
@@ -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>
@@ -786,7 +786,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"तपाईँको भ्वाइसमेल इनबक्समा सन्देश थप्नको लागि अनुप्रयोगलाई अनुमति दिन्छ।"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"भूस्थान अनुमतिहरू ब्राउजर परिवर्तन गर्नुहोस्"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउजरको भू-स्थान अनुमतिहरू परिमार्जन गर्न अनुप्रयोगलाई अनुमति दिन्छ। खराब अनुप्रयोगहरूले  स्थान सूचना मनपरी वेब साइटहरूमा पठाउने अनुमतिको लागि यसलाई प्रयोग गर्न सक्छन्।"</string>
-    <string name="save_password_message" msgid="767344687139195790">"के तपाईं ब्राउजरले यो पासवर्ड सम्झेको चाहनुहुन्छ?"</string>
+    <string name="save_password_message" msgid="767344687139195790">"के तपाईँ ब्राउजरले यो पासवर्ड सम्झेको चाहनुहुन्छ?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"अहिले होइन"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"सम्झनुहोस्"</string>
     <string name="save_password_never" msgid="8274330296785855105">"कहिल्यै पनि होइन"</string>
@@ -907,13 +907,13 @@
     <string name="aerr_process_silence" msgid="4226685530196000222">"साइलेन्स पुनःबुट नभएसम्म <xliff:g id="PROCESS">%1$s</xliff:g> बाट क्र्यास हुन्छ।"</string>
     <string name="anr_title" msgid="4351948481459135709"></string>
     <string name="anr_activity_application" msgid="1904477189057199066">"<xliff:g id="APPLICATION">%2$s</xliff:g>ले कार्य गरिरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
-    <string name="anr_activity_process" msgid="5776209883299089767">"गतिविधि <xliff:g id="ACTIVITY">%1$s</xliff:g> ले प्रतिक्रिया देखाइरहेको छैन।\n\nके तपाईं यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
+    <string name="anr_activity_process" msgid="5776209883299089767">"गतिविधि <xliff:g id="ACTIVITY">%1$s</xliff:g> ले प्रतिक्रिया देखाइरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="anr_application_process" msgid="8941757607340481057">"<xliff:g id="APPLICATION">%1$s</xliff:g> जवाफ दिइरहेको छैन। के तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="anr_process" msgid="6513209874880517125">"प्रक्रिया <xliff:g id="PROCESS">%1$s</xliff:g>ले कार्य गरिरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="force_close" msgid="8346072094521265605">"ठिक छ"</string>
     <string name="report" msgid="4060218260984795706">"रिपोर्ट गर्नुहोस्"</string>
     <string name="wait" msgid="7147118217226317732">"प्रतीक्षा गर्नुहोस्"</string>
-    <string name="webpage_unresponsive" msgid="3272758351138122503">"पृष्ठ गैर जिम्मेवारी भएको छ।\n\nके तपाईं यसलाई बन्द गर्न चाहनुहुन्छ?"</string>
+    <string name="webpage_unresponsive" msgid="3272758351138122503">"पृष्ठ गैर जिम्मेवारी भएको छ।\n\nके तपाईँ यसलाई बन्द गर्न चाहनुहुन्छ?"</string>
     <string name="launch_warning_title" msgid="1547997780506713581">"अनुप्रयोग पुनः निर्देशीत"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले चलिरहेको छ।"</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> वास्तविक सुरुवात भएको थियो।"</string>
@@ -1103,7 +1103,7 @@
     <string name="deny" msgid="2081879885755434506">"अस्वीकार गर्नुहोस्"</string>
     <string name="permission_request_notification_title" msgid="6486759795926237907">"अनुरोध गरिएको अनुमति"</string>
     <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">\n"खाता <xliff:g id="ACCOUNT">%s</xliff:g>को लागि अनुरोध गरिएको अनुमति।"</string>
-    <string name="forward_intent_to_owner" msgid="1207197447013960896">"तपाईं तपाईँको कार्य प्रोफाइल बाहिर यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
+    <string name="forward_intent_to_owner" msgid="1207197447013960896">"तपाईँ तपाईँको कार्य प्रोफाइल बाहिर यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
     <string name="forward_intent_to_work" msgid="621480743856004612">"तपाईँ आफ्नो कार्य प्रोफाइलमा यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
     <string name="input_method_binding_label" msgid="1283557179944992649">"इनपुट विधि"</string>
     <string name="sync_binding_label" msgid="3687969138375092423">"सिङ्क गर्नुहोस्"</string>
@@ -1151,7 +1151,7 @@
     <string name="gpsVerifYes" msgid="2346566072867213563">"हो"</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_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>
     <string name="sync_undo_deletes" msgid="2941317360600338602">"मेटिएकाहरू पूर्ववत बनाउनुहोस्।"</string>
     <string name="sync_do_nothing" msgid="3743764740430821845">"अहिलेको लागि केही नगर्नुहोस्"</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 5062e76..6c576ad 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -45,7 +45,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Mesaj vocal"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problemă de conexiune sau cod MMI nevalid."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Operaţia este limitată la numerele cu apelări restricţionate."</string>
+    <string name="mmiFdnError" msgid="5224398216385316471">"Operația este limitată la numerele cu apelări restricţionate."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Serviciul a fost activat."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Serviciul a fost activat pentru:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Serviciul a fost dezactivat."</string>
@@ -73,7 +73,7 @@
     <string name="ColpMmi" msgid="3065121483740183974">"ID-ul liniei conectate"</string>
     <string name="ColrMmi" msgid="4996540314421889589">"Restricționarea ID-ului liniei conectate"</string>
     <string name="CfMmi" msgid="5123218989141573515">"Redirecționarea apelurilor"</string>
-    <string name="CwMmi" msgid="9129678056795016867">"Apel în aşteptare"</string>
+    <string name="CwMmi" msgid="9129678056795016867">"Apel în așteptare"</string>
     <string name="BaMmi" msgid="455193067926770581">"Blocarea apelurilor"</string>
     <string name="PwdMmi" msgid="7043715687905254199">"Modificare parolă"</string>
     <string name="PinMmi" msgid="3113117780361190304">"Cod PIN modificat"</string>
@@ -270,7 +270,7 @@
     <string name="permlab_receiveMms" msgid="1821317344668257098">"primeşte mesaje text (MMS)"</string>
     <string name="permdesc_receiveMms" msgid="533019437263212260">"Permite aplicației să primească și să proceseze mesaje MMS. Acest lucru înseamnă că aplicația ar putea monitoriza sau șterge mesajele trimise pe dispozitivul dvs. fără a vi le arăta."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"citeşte mesajele cu transmisie celulară"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitivul dvs. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgenţă. Aplicaţiile rău intenţionate pot afecta performanţa sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgenţă."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitivul dvs. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a vă avertiza cu privire la situațiile de urgenţă. Aplicaţiile rău intenţionate pot afecta performanța sau funcționarea dispozitivului dvs. când este primită o transmisie celulară de urgenţă."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"citire feeduri abonat"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Permite aplicației să obţină detalii despre feedurile sincronizate în prezent."</string>
     <string name="permlab_sendSms" msgid="7544599214260982981">"trimită și să vadă mesajele SMS"</string>
@@ -635,7 +635,7 @@
     <string name="relationTypeReferredBy" msgid="101573059844135524">"Denumit după"</string>
     <string name="relationTypeRelative" msgid="1799819930085610271">"Rudă"</string>
     <string name="relationTypeSister" msgid="1735983554479076481">"Soră"</string>
-    <string name="relationTypeSpouse" msgid="394136939428698117">"Soţ/Soţie"</string>
+    <string name="relationTypeSpouse" msgid="394136939428698117">"Soț/Soție"</string>
     <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Personalizată"</string>
     <string name="sipAddressTypeHome" msgid="6093598181069359295">"Ecran pornire"</string>
     <string name="sipAddressTypeWork" msgid="6920725730797099047">"Serviciu"</string>
@@ -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>
@@ -791,7 +791,7 @@
     <string name="save_password_notnow" msgid="6389675316706699758">"Nu acum"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Reţineţi"</string>
     <string name="save_password_never" msgid="8274330296785855105">"Niciodată"</string>
-    <string name="open_permission_deny" msgid="7374036708316629800">"Nu aveţi permisiunea de a deschide această pagină."</string>
+    <string name="open_permission_deny" msgid="7374036708316629800">"Nu aveți permisiunea de a deschide această pagină."</string>
     <string name="text_copied" msgid="4985729524670131385">"Text copiat în clipboard."</string>
     <string name="more_item_label" msgid="4650918923083320495">"Mai multe"</string>
     <string name="prepend_shortcut_label" msgid="2572214461676015642">"Meniu+"</string>
@@ -861,7 +861,7 @@
     <string name="selectAll" msgid="6876518925844129331">"Selectați-le pe toate"</string>
     <string name="cut" msgid="3092569408438626261">"Decupaţi"</string>
     <string name="copy" msgid="2681946229533511987">"Copiați"</string>
-    <string name="paste" msgid="5629880836805036433">"Inseraţi"</string>
+    <string name="paste" msgid="5629880836805036433">"Inserați"</string>
     <string name="replace" msgid="5781686059063148930">"Înlocuiţi..."</string>
     <string name="delete" msgid="6098684844021697789">"Ștergeți"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiați adresa URL"</string>
@@ -989,7 +989,7 @@
     <string name="accept" msgid="1645267259272829559">"Acceptaţi"</string>
     <string name="decline" msgid="2112225451706137894">"Refuzaţi"</string>
     <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitaţia a fost trimisă."</string>
-    <string name="wifi_p2p_invitation_to_connect_title" msgid="4958803948658533637">"Invitaţie pentru conectare"</string>
+    <string name="wifi_p2p_invitation_to_connect_title" msgid="4958803948658533637">"Invitație pentru conectare"</string>
     <string name="wifi_p2p_from_message" msgid="570389174731951769">"De la:"</string>
     <string name="wifi_p2p_to_message" msgid="248968974522044099">"Către:"</string>
     <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduceţi codul PIN necesar:"</string>
@@ -1156,7 +1156,7 @@
     <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"Există <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> (de) elemente şterse pentru <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, contul <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Ce doriți să faceţi?"</string>
     <string name="sync_really_delete" msgid="2572600103122596243">"Ștergeți elementele"</string>
     <string name="sync_undo_deletes" msgid="2941317360600338602">"Anulați aceste ştergeri"</string>
-    <string name="sync_do_nothing" msgid="3743764740430821845">"Nu trebuie să luaţi nicio măsură deocamdată"</string>
+    <string name="sync_do_nothing" msgid="3743764740430821845">"Nu trebuie să luați nicio măsură deocamdată"</string>
     <string name="choose_account_label" msgid="5655203089746423927">"Alegeţi un cont"</string>
     <string name="add_account_label" msgid="2935267344849993553">"Adăugaţi un cont"</string>
     <string name="add_account_button_label" msgid="3611982894853435874">"Adăugaţi un cont"</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 29fd6a1..aac6ba3 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -163,7 +163,7 @@
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"För många <xliff:g id="CONTENT_TYPE">%s</xliff:g>-borttagningar."</string>
     <string name="low_memory" product="tablet" msgid="6494019234102154896">"Pekdatorns lagringsutrymme är fullt. Ta bort några filer för att frigöra utrymme."</string>
     <string name="low_memory" product="watch" msgid="4415914910770005166">"Klockans lagringsutrymme är fullt. Ta bort några filer för att frigöra utrymme."</string>
-    <string name="low_memory" product="tv" msgid="516619861191025923">"Lagringsutrymmet på tv:n är fullt. Ta bort några filer för att frigöra utrymme."</string>
+    <string name="low_memory" product="tv" msgid="516619861191025923">"Lagringsutrymmet på TV:n är fullt. Ta bort några filer för att frigöra utrymme."</string>
     <string name="low_memory" product="default" msgid="3475999286680000541">"Mobilens lagringsutrymme är fullt. Ta bort några filer för att frigöra utrymme."</string>
     <string name="ssl_ca_cert_warning" msgid="5848402127455021714">"Nätverket kan vara övervakat"</string>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4475437862189850602">"Av en okänd tredje part"</string>
@@ -177,7 +177,7 @@
     <string name="factory_reset_message" msgid="4905025204141900666">"Administratörsappen saknar delar eller är skadad och kan inte användas. Enheten kommer nu att rensas. Kontakta administratören om du behöver hjälp."</string>
     <string name="me" msgid="6545696007631404292">"Jag"</string>
     <string name="power_dialog" product="tablet" msgid="8545351420865202853">"Alternativ för surfplattan"</string>
-    <string name="power_dialog" product="tv" msgid="6153888706430556356">"Tv-alternativ"</string>
+    <string name="power_dialog" product="tv" msgid="6153888706430556356">"TV-alternativ"</string>
     <string name="power_dialog" product="default" msgid="1319919075463988638">"Telefonalternativ"</string>
     <string name="silent_mode" msgid="7167703389802618663">"Tyst läge"</string>
     <string name="turn_on_radio" msgid="3912793092339962371">"Aktivera trådlöst"</string>
@@ -195,7 +195,7 @@
     <string name="reboot_to_reset_message" msgid="2432077491101416345">"Startar om …"</string>
     <string name="shutdown_progress" msgid="2281079257329981203">"Avslutar…"</string>
     <string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"Din surfplatta stängs av."</string>
-    <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"Tv:n stängs av."</string>
+    <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"TV:n stängs av."</string>
     <string name="shutdown_confirm" product="watch" msgid="3490275567476369184">"Klockan stängs av."</string>
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"Din telefon stängs av."</string>
     <string name="shutdown_confirm_question" msgid="2906544768881136183">"Vill du stänga av?"</string>
@@ -276,7 +276,7 @@
     <string name="permdesc_sendSms" msgid="7094729298204937667">"Tillåter att appen skickar SMS. Detta kan leda till oväntade avgifter. Skadliga appar kan skicka meddelanden utan ditt godkännande vilket kan kosta pengar."</string>
     <string name="permlab_readSms" msgid="8745086572213270480">"läsa dina textmeddelanden (SMS eller MMS)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Tillåter att appen läser SMS som sparats på surfplattan eller på SIM-kortet. Med den här behörigheten tillåts appen att läsa alla SMS oavsett innehåll eller sekretess."</string>
-    <string name="permdesc_readSms" product="tv" msgid="5102425513647038535">"Tillåter att appen läser sms som har sparats på tv:n eller SIM-kortet. På så sätt kan appen läsa alla sms oavsett innehåll eller sekretess."</string>
+    <string name="permdesc_readSms" product="tv" msgid="5102425513647038535">"Tillåter att appen läser sms som har sparats på TV:n eller SIM-kortet. På så sätt kan appen läsa alla sms oavsett innehåll eller sekretess."</string>
     <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Tillåter att appen läser SMS som sparats på mobilen eller på SIM-kortet. Med den här behörigheten tillåts appen att läsa alla SMS oavsett innehåll eller sekretess."</string>
     <string name="permlab_receiveWapPush" msgid="5991398711936590410">"ta emot textmeddelanden (WAP)"</string>
     <string name="permdesc_receiveWapPush" msgid="748232190220583385">"Tillåter att appen tar emot och hanterar WAP-meddelanden. Med den här behörigheten kan appen övervaka eller ta bort meddelanden som skickats till dig utan att visa dem för dig."</string>
@@ -294,7 +294,7 @@
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Tillåter att appen att dras ovanpå andra appar eller delar av användargränssnittet. De kan störa din användning av gränssnittet i olika appar eller ändra vad du tror visas i andra appar."</string>
     <string name="permlab_persistentActivity" msgid="8841113627955563938">"se till att appen alltid körs"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"Tillåter att delar av appen läggs beständigt i minnet. Detta kan innebära att det tillgängliga minnet för andra appar begränsas, vilket gör surfplattan långsam."</string>
-    <string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"Tillåter att en app gör vissa delar beständiga i minnet. Det kan begränsa mängden minne som är tillgänglig för andra appar och gör tv:n långsammare."</string>
+    <string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"Tillåter att en app gör vissa delar beständiga i minnet. Det kan begränsa mängden minne som är tillgänglig för andra appar och gör TV:n långsammare."</string>
     <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"Tillåter att delar av appen läggs beständigt i minnet. Detta kan innebära att det tillgängliga minnet för andra appar begränsas, vilket gör mobilen långsam."</string>
     <string name="permlab_getPackageSize" msgid="7472921768357981986">"mäta appens lagringsplats"</string>
     <string name="permdesc_getPackageSize" msgid="3921068154420738296">"Tillåter att appen hämtar kod, data och cachestorlekar"</string>
@@ -306,33 +306,33 @@
     <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"Tillåter att appen startar automatiskt när systemet har startats om. Detta kan innebära att det tar längre tid att starta mobilen och att mobilen blir långsammare i och med att appen hela tiden körs i bakgrunden."</string>
     <string name="permlab_broadcastSticky" msgid="7919126372606881614">"Skicka sticky broadcast"</string>
     <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"Tillåter att appen skickar sticky broadcasts, som finns kvar när sändningen är slut. Vid intensiv användning kan mobilen bli långsam eller instabil eftersom minnet överbelastas."</string>
-    <string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"Tillåter att appen skickar sticky broadcasts som finns kvar när sändningen är slut. Överdriven användning kan göra tv:n seg eller instabil eftersom den använder för mycket minne."</string>
+    <string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"Tillåter att appen skickar sticky broadcasts som finns kvar när sändningen är slut. Överdriven användning kan göra TV:n seg eller instabil eftersom den använder för mycket minne."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"Tillåter att appen skickar sticky broadcast, som finns kvar när sändningen är slut. Vid intensiv användning kan mobilen bli långsam eller instabil eftersom minnet överbelastas."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"läsa dina kontakter"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Tillåter att appen läser kontaktuppgifter som sparats på surfplattan, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"Tillåter att appen läser data om dina kontakter som sparats på tv:n, bland annat hur ofta du har ringt, skickat e-post eller kommunicerat på andra sätt med enskilda individer. Med den här behörigheten kan appar spara dina kontaktuppgifter och skadliga appar kan dela kontaktuppgifter utan att du vet om det."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"Tillåter att appen läser data om dina kontakter som sparats på TV:n, bland annat hur ofta du har ringt, skickat e-post eller kommunicerat på andra sätt med enskilda individer. Med den här behörigheten kan appar spara dina kontaktuppgifter och skadliga appar kan dela kontaktuppgifter utan att du vet om det."</string>
     <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Tillåter att appen läser kontaktuppgifter som sparats på mobilen, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
     <string name="permlab_writeContacts" msgid="5107492086416793544">"ändra kontakterna"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Tillåter att appen ändrar kontaktuppgifter som sparats på surfplattan, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appar att ta bort kontaktuppgifter."</string>
-    <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"Tillåter att appen ändrar uppgifterna om dina kontakter som har sparats på tv:n, bland annat hur ofta du har ringt, skickat e-post eller kommunicerat på andra sätt med särskilda kontakter. Med den här behörigheten kan appar ta bort kontaktuppgifter."</string>
+    <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"Tillåter att appen ändrar uppgifterna om dina kontakter som har sparats på TV:n, bland annat hur ofta du har ringt, skickat e-post eller kommunicerat på andra sätt med särskilda kontakter. Med den här behörigheten kan appar ta bort kontaktuppgifter."</string>
     <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Tillåter att appen ändrar kontaktuppgifter som sparats på mobilen, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appar att ta bort kontaktuppgifter."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"läs samtalslogg"</string>
     <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"Tillåter att appen läser pekdatorns samtalslista, inklusive uppgifter om inkommande och utgående samtal. Med den här behörigheten tillåts appen att spara samtalshistoriken. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
-    <string name="permdesc_readCallLog" product="tv" msgid="5611770887047387926">"Tillåter att appen läser tv:ns samtalslista, bland annat data om inkommande och utgående samtal. Med den här behörigheten kan appar spara data i dina samtalslistor och skadliga appar kan dela data i samtalslistor utan att du vet om det."</string>
+    <string name="permdesc_readCallLog" product="tv" msgid="5611770887047387926">"Tillåter att appen läser TV:ns samtalslista, bland annat data om inkommande och utgående samtal. Med den här behörigheten kan appar spara data i dina samtalslistor och skadliga appar kan dela data i samtalslistor utan att du vet om det."</string>
     <string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"Tillåter att appen läser mobilens samtalslista, inklusive uppgifter om inkommande och utgående samtal. Med den här behörigheten tillåts appen att spara samtalshistoriken. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"skriv samtalslogg"</string>
     <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Tillåter att appen gör ändringar i pekdatorns samtalslista, inklusive i uppgifter om inkommande och utgående samtal. Skadliga program kan använda detta för att radera eller ändra din samtalslista."</string>
-    <string name="permdesc_writeCallLog" product="tv" msgid="4225034892248398019">"Tillåter att appen gör ändringar i tv:ns samtalslista, inklusive i uppgifter om inkommande och utgående samtal. Skadliga appar kan använda detta för att rensa eller ändra din samtalslista."</string>
+    <string name="permdesc_writeCallLog" product="tv" msgid="4225034892248398019">"Tillåter att appen gör ändringar i TV:ns samtalslista, inklusive i uppgifter om inkommande och utgående samtal. Skadliga appar kan använda detta för att rensa eller ändra din samtalslista."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Tillåter att appen gör ändringar i mobilens samtalslista, inklusive i uppgifter om inkommande och utgående samtal. Skadliga program kan använda detta för att radera eller ändra din samtalslista."</string>
     <string name="permlab_bodySensors" msgid="4871091374767171066">"kroppssens. (för hjärtat m.m.)"</string>
     <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"Ger appen åtkomst till information från sensorer om ditt fysiska tillstånd, till exempel din puls."</string>
     <string name="permlab_readCalendar" msgid="5972727560257612398">"läsa kalenderuppgifter plus konfidentiell information"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="4216462049057658723">"Tillåter att appen läser alla kalenderuppgifter som sparats på surfplattan, inklusive dina vänners eller kollegors uppgifter. Med den här behörigheten kan appen tillåtas att dela eller spara kalenderuppgifter även om de är sekretessbelagda eller känsliga."</string>
-    <string name="permdesc_readCalendar" product="tv" msgid="3191352452242394196">"Tillåter att appen läser alla kalenderhändelser som sparats på tv:n, bland annat de som tillhör vänner eller kollegor. På så sätt kan appen dela eller spara dina kalenderhändelser oavsett sekretess eller känslighet."</string>
+    <string name="permdesc_readCalendar" product="tv" msgid="3191352452242394196">"Tillåter att appen läser alla kalenderhändelser som sparats på TV:n, bland annat de som tillhör vänner eller kollegor. På så sätt kan appen dela eller spara dina kalenderhändelser oavsett sekretess eller känslighet."</string>
     <string name="permdesc_readCalendar" product="default" msgid="7434548682470851583">"Tillåter att appen läser alla kalenderuppgifter som sparats på mobilen, inklusive dina vänners eller kollegors uppgifter. Med den här behörigheten kan appen tillåtas att dela eller spara kalenderuppgifter även om de är sekretessbelagda eller känsliga."</string>
     <string name="permlab_writeCalendar" msgid="8438874755193825647">"lägga till eller ändra kalenderuppgifter och skicka e-post till gäster utan ägarens vetskap"</string>
     <string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Tillåter att appen lägger till, tar bort och ändrar sådana händelser som du kan ändra på surfplattan, inklusive dina vänners eller kollegors uppgifter. Detta kan innebära att appen tillåts skicka meddelanden som ser ut att komma från kalenderns ägare eller ändra händelser utan ägarens vetskap."</string>
-    <string name="permdesc_writeCalendar" product="tv" msgid="1273290605500902507">"Tillåter att appen lägger till, tar bort och ändrar händelser som du kan ändra på tv:n, inklusive dina vänners eller kollegors uppgifter. Appen kan på så sätt skicka meddelanden som ser ut att komma från kalenderns ägare eller ändra uppgifter utan ägarens vetskap."</string>
+    <string name="permdesc_writeCalendar" product="tv" msgid="1273290605500902507">"Tillåter att appen lägger till, tar bort och ändrar händelser som du kan ändra på TV:n, inklusive dina vänners eller kollegors uppgifter. Appen kan på så sätt skicka meddelanden som ser ut att komma från kalenderns ägare eller ändra uppgifter utan ägarens vetskap."</string>
     <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Tillåter att appen lägger till, tar bort och ändrar sådana händelser som du kan ändra på mobilen, inklusive dina vänners eller kollegors uppgifter. Detta kan innebära att appen tillåts skicka meddelanden som ser ut att komma från kalenderns ägare eller ändra händelser utan ägarens vetskap."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få åtkomst till extra kommandon för platsleverantör"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="6078307221056649927">"Tillåter att appen får åtkomst till extra kommandon för platsleverantör. Detta kan innebära att appen tillåts störa funktionen för GPS eller andra platskällor."</string>
@@ -359,14 +359,14 @@
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"läsa telefonens status och identitet"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Tillåter att appen kommer åt enhetens telefonfunktioner. Med den här behörigheten tillåts appen att identifiera mobilens telefonnummer och enhets-ID, om ett samtal pågår och vilket nummer samtalet är kopplat till."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"förhindra att surfplattan går in i viloläge"</string>
-    <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"förhindra att tv:n försätts i viloläge"</string>
+    <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"förhindra att TV:n försätts i viloläge"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"förhindra att telefonen sätts i viloläge"</string>
     <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"Tillåter att appen förhindrar att surfplattan går in i viloläge."</string>
-    <string name="permdesc_wakeLock" product="tv" msgid="3208534859208996974">"Tillåter att appen förhindrar att tv:n försätts i viloläge."</string>
+    <string name="permdesc_wakeLock" product="tv" msgid="3208534859208996974">"Tillåter att appen förhindrar att TV:n försätts i viloläge."</string>
     <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"Tillåter att appen förhindrar att mobilen går in i viloläge."</string>
     <string name="permlab_transmitIr" msgid="7545858504238530105">"tillåt IR-sändning"</string>
     <string name="permdesc_transmitIr" product="tablet" msgid="5358308854306529170">"Tillåter att appen använder surfplattans IR-sändare."</string>
-    <string name="permdesc_transmitIr" product="tv" msgid="3926790828514867101">"Tillåter att appen använder den infraröda sändaren på tv:n."</string>
+    <string name="permdesc_transmitIr" product="tv" msgid="3926790828514867101">"Tillåter att appen använder den infraröda sändaren på TV:n."</string>
     <string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"Tillåter att appen använder mobilens IR-sändare."</string>
     <string name="permlab_setWallpaper" msgid="6627192333373465143">"ange bakgrund"</string>
     <string name="permdesc_setWallpaper" msgid="7373447920977624745">"Tillåter att appen anger systemets bakgrund."</string>
@@ -374,11 +374,11 @@
     <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"Tillåter att appen ger tips om systemets bakgrundsstorlek."</string>
     <string name="permlab_setTimeZone" msgid="2945079801013077340">"ange tidszon"</string>
     <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"Tillåter att appen ändrar pekdatorns tidszon."</string>
-    <string name="permdesc_setTimeZone" product="tv" msgid="888864653946175955">"Tillåter att appen ändrar tidszonen på tv:n."</string>
+    <string name="permdesc_setTimeZone" product="tv" msgid="888864653946175955">"Tillåter att appen ändrar tidszonen på TV:n."</string>
     <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"Tillåter att appen ändrar mobilens tidszon."</string>
     <string name="permlab_getAccounts" msgid="1086795467760122114">"hitta konton på enheten"</string>
     <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"Tillåter att appen hämtar en lista över alla kända konton på surfplattan. Detta kan inkludera konton som har skapats av appar som du har installerat."</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"Tillåter att appen hämtar listan med konton som tv:n kan identifiera. Den kan innehålla konton som skapats av appar som du har installerat."</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"Tillåter att appen hämtar listan med konton som TV:n kan identifiera. Den kan innehålla konton som skapats av appar som du har installerat."</string>
     <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"Tillåter att appen hämtar en lista över alla kända konton på mobilen. Detta kan inkludera konton som har skapats av appar som du har installerat."</string>
     <string name="permlab_accessNetworkState" msgid="4951027964348974773">"visa nätverksanslutningar"</string>
     <string name="permdesc_accessNetworkState" msgid="8318964424675960975">"Tillåter att appen kommer åt information om nätverksanslutningarna, till exempel vilka nätverk som finns och är anslutna."</string>
@@ -394,21 +394,21 @@
     <string name="permdesc_changeWifiState" msgid="7137950297386127533">"Tillåter att appen ansluter till och kopplar från Wi-Fi-åtkomstpunkter samt gör ändringar i enhetens konfiguration för Wi-Fi-nätverk."</string>
     <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"tillåt Wi-Fi multicast-mottagning"</string>
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Tillåter att appen tar emot paket som skickats med multicast-adress till alla enheter i ett Wi-Fi-nätverk och inte bara till den här surfplattan. Detta drar mer batteri än när multicastläget inte används."</string>
-    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Tillåter att appen tar emot paket som skickats till alla enheter i ett Wi-Fi-nätverk med multicastadress, inte bara till tv:n. Detta drar mer batteri än när multicastläget inte används."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Tillåter att appen tar emot paket som skickats till alla enheter i ett Wi-Fi-nätverk med multicastadress, inte bara till TV:n. Detta drar mer batteri än när multicastläget inte används."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Tillåter att appen tar emot paket som skickats med multicast-adress till alla enheter i ett Wi-Fi-nätverk och inte bara till den här mobilen. Detta drar mer batteri än när multicastläget inte används."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"få åtkomst till Bluetooth-inställningar"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Tillåter att appen konfigurerar den lokala Bluetooth-surfplattan samt upptäcker och parkopplar den med fjärranslutna enheter."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Tillåter att appen konfigurerar den lokala Bluetooth-tv:n och identifierar och kopplar den till fjärrenheter."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Tillåter att appen konfigurerar den lokala Bluetooth-TV:n och identifierar och kopplar den till fjärrenheter."</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Tillåter att appen konfigurerar den lokala Bluetooth-mobilen samt upptäcker och parkopplar den med fjärranslutna enheter."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"ansluta till och koppla från WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Tillåter att appen avgör om WiMAX är aktiverat och kommer åt information om eventuella anslutna WiMAX-nätverk."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"ändra WiMAX-status"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Tillåter att appen ansluter surfplattan till eller kopplar från WiMAX-nätverk."</string>
-    <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Tillåter att appen ansluter tv:n till och kopplar från tv:n från WiMAX-nätverk."</string>
+    <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Tillåter att appen ansluter TV:n till och kopplar från TV:n från WiMAX-nätverk."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Tillåter att appen ansluter mobilen till eller kopplar från WiMAX-nätverk."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"koppla till Bluetooth-enheter"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Tillåter att appen kommer åt pekdatorns Bluetooth-konfiguration och upprättar och godkänner anslutningar till parkopplade enheter."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Tillåter att appen visar konfigurationen av Bluetooth på tv:n och godkänner alla anslutningar till kopplade enheter."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Tillåter att appen visar konfigurationen av Bluetooth på TV:n och godkänner alla anslutningar till kopplade enheter."</string>
     <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Tillåter att appen kommer åt mobilens Bluetooth-konfiguration och upprättar och godkänner anslutningar till parkopplade enheter."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"kontrollera närfältskommunikationen"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"Tillåter att appen kommunicerar med etiketter, kort och läsare för närfältskommunikation (NFC)."</string>
@@ -499,10 +499,10 @@
     <string name="policydesc_limitPassword" msgid="2502021457917874968">"Styr tillåten längd och tillåtna tecken i lösenord och pinkoder för skärmlåset."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Övervaka försök att låsa upp skärmen"</string>
     <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"Övervaka antalet felaktiga lösenord som angetts för skärmlåset och lås surfplattan eller ta bort alla data från surfplattan om för många felaktiga försök görs."</string>
-    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"Övervakar antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och låser tv:n eller rensar alla uppgifter på tv:n om för många felaktiga lösenord har skrivits in."</string>
+    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"Övervakar antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och låser TV:n eller rensar alla uppgifter på TV:n om för många felaktiga lösenord har skrivits in."</string>
     <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"Övervaka antalet felaktiga lösenord som angivits för skärmlåset och lås mobilen eller ta bort alla data från mobilen om för många felaktiga försök görs."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"Övervaka antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och lås surfplattan eller rensa alla uppgifter för den här användaren om för många felaktiga lösenord har skrivits in."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"Övervaka antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och lås tv:n eller rensa alla uppgifter för den här användaren om för många felaktiga lösenord har skrivits in."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"Övervaka antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och lås TV:n eller rensa alla uppgifter för den här användaren om för många felaktiga lösenord har skrivits in."</string>
     <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"Övervaka antalet felaktiga lösenord som skrivits in vid upplåsning av skärmen och lås mobilen eller rensa alla uppgifter för den här användaren om för många felaktiga lösenord har skrivits in."</string>
     <string name="policylab_resetPassword" msgid="4934707632423915395">"Ändra skärmlåset"</string>
     <string name="policydesc_resetPassword" msgid="1278323891710619128">"Ändra skärmlåset."</string>
@@ -510,11 +510,11 @@
     <string name="policydesc_forceLock" msgid="1141797588403827138">"Kontrollera hur och när skärmlåset aktiveras."</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"Radera all data"</string>
     <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"Ta bort data från surfplattan utan förvarning genom att återställa standardinställningarna."</string>
-    <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"Rensar uppgifterna på tv:n utan föregående varning genom att återställa standardinställningarna."</string>
+    <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"Rensar uppgifterna på TV:n utan föregående varning genom att återställa standardinställningarna."</string>
     <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"Ta bort data från mobilen utan förvarning genom att återställa standardinställningarna."</string>
     <string name="policylab_wipeData_secondaryUser" msgid="8362863289455531813">"Radera användaruppgifter"</string>
     <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="6336255514635308054">"Rensa användarens uppgifter på den här surfplattan utan förvarning."</string>
-    <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2086473496848351810">"Rensa användarens uppgifter på den här tv:n utan förvarning."</string>
+    <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2086473496848351810">"Rensa användarens uppgifter på den här TV:n utan förvarning."</string>
     <string name="policydesc_wipeData_secondaryUser" product="default" msgid="6787904546711590238">"Rensa användarens data på den här mobilen utan förvarning."</string>
     <string name="policylab_setGlobalProxy" msgid="2784828293747791446">"Ange global proxyserver"</string>
     <string name="policydesc_setGlobalProxy" msgid="8459859731153370499">"Ange enhetens globala proxy som ska användas när policyn aktiveras. Det är bara enhetens ägare som kan ange global proxy."</string>
@@ -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>
@@ -663,7 +663,7 @@
     <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Du har försökt låsa upp med Ansiktslås för många gånger"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"Inget SIM-kort"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Inget SIM-kort i surfplattan."</string>
-    <string name="lockscreen_missing_sim_message" product="tv" msgid="1943633865476989599">"Det finns inget SIM-kort i tv:n."</string>
+    <string name="lockscreen_missing_sim_message" product="tv" msgid="1943633865476989599">"Det finns inget SIM-kort i TV:n."</string>
     <string name="lockscreen_missing_sim_message" product="default" msgid="2186920585695169078">"Inget SIM-kort i telefonen."</string>
     <string name="lockscreen_missing_sim_instructions" msgid="5372787138023272615">"Sätt i ett SIM-kort."</string>
     <string name="lockscreen_missing_sim_instructions_long" msgid="3526573099019319472">"SIM-kort saknas eller kan inte läsas. Sätt i ett SIM-kort."</string>
@@ -686,13 +686,13 @@
     <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. \n\nFörsök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. \n\nFörsök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="9191611984625460820">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till ombeds du att låsa upp surfplattan med din Google-inloggning.\n\n Försök igen om  <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Du har ritat fel mönster för upplåsning <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök blir du ombedd att låsa upp tv:n genom att logga in på Google.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="5316664559603394684">"Du har ritat fel mönster för upplåsning <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök blir du ombedd att låsa upp TV:n genom att logga in på Google.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="2590227559763762751">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till ombeds du att låsa upp mobilen med uppgifterna som du använder när du loggar in på Google.\n\n Försök igen om  <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="6128106399745755604">"Du har försökt låsa upp surfplattan på fel sätt <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök till kommer surfplattan att återställas till fabriksinställningarna. Du förlorar då alla användardata."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="950408382418270260">"Du har misslyckats med att låsa upp tv:n <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök kommer tv:n att återställas till standardinställningarna och alla användaruppgifter kommer att gå förlorade."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="950408382418270260">"Du har misslyckats med att låsa upp TV:n <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök kommer TV:n att återställas till standardinställningarna och alla användaruppgifter kommer att gå förlorade."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="8603565142156826565">"Du har försökt låsa upp mobilen på fel sätt <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök till kommer mobilen att återställas till fabriksinställningarna. Du förlorar då alla användardata."</string>
     <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="280873516493934365">"Du har försökt låsa upp surfplattan på fel sätt <xliff:g id="NUMBER">%d</xliff:g> gånger. Surfplattan återställs nu till fabriksinställningarna."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="3195755534096192191">"Du har misslyckats med att låsa upp tv:n <xliff:g id="NUMBER">%d</xliff:g> gånger. Tv:n kommer nu att återställas till standardinställningarna."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="3195755534096192191">"Du har misslyckats med att låsa upp TV:n <xliff:g id="NUMBER">%d</xliff:g> gånger. TV:n kommer nu att återställas till standardinställningarna."</string>
     <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"Du har försökt låsa upp mobilen på fel sätt <xliff:g id="NUMBER">%d</xliff:g> gånger. Mobilen återställs nu till fabriksinställningarna."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Glömt ditt grafiska lösenord?"</string>
@@ -778,7 +778,7 @@
     <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"Tillåter att appen läser historiken för besökta sidor och alla bokmärken i webbläsaren. Observera att den här behörigheten kanske inte är tillämplig för webbläsare från tredje part eller andra appar med surffunktion."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"skriva bokmärken och historik på webben"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"Tillåter att appen ändrar historiken för besökta sidor i webbläsaren eller bokmärken som sparats på surfplattan. Det kan innebära att appen kan ta bort eller ändra webbläsarinformation. Observera att den här behörigheten kanske inte är tillämplig för webbläsare från tredje part eller andra appar med surffunktion."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="7007393823197766548">"Tillåter att appen ändrar webbläsarens historik eller bokmärken som har sparats på tv:n. Appen kan därmed rensa eller ändra uppgifter i webbläsaren. Obs! Den här behörigheten kanske inte gäller för webbläsare från tredje part eller andra appar med webbfunktioner."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="7007393823197766548">"Tillåter att appen ändrar webbläsarens historik eller bokmärken som har sparats på TV:n. Appen kan därmed rensa eller ändra uppgifter i webbläsaren. Obs! Den här behörigheten kanske inte gäller för webbläsare från tredje part eller andra appar med webbfunktioner."</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Tillåter att appen ändrar historiken för besökta sidor i webbläsaren eller bokmärken som sparats på telefonen. Det kan innebära att appen kan ta bort eller ändra webbläsarinformation. Observera att den här behörigheten kanske inte är tillämplig för webbläsare från tredje part eller andra appar med surffunktion."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"ställa in ett alarm"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillåter att appen ställer in ett alarm i en befintlig alarmapp. Vissa alarmappar har inte den här funktionen."</string>
@@ -988,7 +988,7 @@
     <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ange den obligatoriska PIN-koden:"</string>
     <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kod:"</string>
     <string name="wifi_p2p_frequency_conflict_message" product="tablet" msgid="8012981257742232475">"Surfplattans Wi-Fi-anslutning kommer tillfälligt att avbrytas när den är ansluten till <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
-    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="3087858235069421128">"Tv:n kopplas tillfälligt från Wi-Fi-nätverket när den är ansluten till <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="wifi_p2p_frequency_conflict_message" product="tv" msgid="3087858235069421128">"TV:n kopplas tillfälligt från Wi-Fi-nätverket när den är ansluten till <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"Mobilen kommer tillfälligt att kopplas från Wi-Fi när den är ansluten till <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="select_character" msgid="3365550120617701745">"Infoga tecken"</string>
     <string name="sms_control_title" msgid="7296612781128917719">"Skickar SMS"</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>
@@ -1289,13 +1289,13 @@
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. \n\nFörsök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. \n\nFörsök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1575557200627128949">"Du har försökt låsa upp mobilen på fel sätt <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök återställs surfplattan till fabriksinställningarna. Du förlorar då alla användardata."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"Du har misslyckats med att låsa upp tv:n <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök kommer tv:n att återställas till standardinställningarna och alla användaruppgifter kommer att gå förlorade."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="5621231220154419413">"Du har misslyckats med att låsa upp TV:n <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök kommer TV:n att återställas till standardinställningarna och alla användaruppgifter kommer att gå förlorade."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="4051015943038199910">"Du har försökt låsa upp mobilen på fel sätt <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök återställs mobilen till fabriksinställningarna. Du förlorar då alla användardata."</string>
     <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2072996269148483637">"Du har försökt låsa upp surfplattan på fel sätt <xliff:g id="NUMBER">%d</xliff:g> gånger. Surfplattan återställs nu till fabriksinställningarna."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"Du har misslyckats med att låsa upp tv:n <xliff:g id="NUMBER">%d</xliff:g> gånger. Tv:n kommer nu att återställas till standardinställningarna."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"Du har misslyckats med att låsa upp TV:n <xliff:g id="NUMBER">%d</xliff:g> gånger. TV:n kommer nu att återställas till standardinställningarna."</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="4817627474419471518">"Du har försökt låsa upp mobilen på fel sätt <xliff:g id="NUMBER">%d</xliff:g> gånger. Mobilen återställs nu till fabriksinställningarna."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> försök ombeds du låsa upp surfplattan med ett e-postkonto.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Du har ritat fel mönster för upplåsning <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök blir du ombedd att låsa upp tv:n via ett e-postkonto.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"Du har ritat fel mönster för upplåsning <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> misslyckade försök blir du ombedd att låsa upp TV:n via ett e-postkonto.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%d</xliff:g> försök ombeds du låsa upp mobilen med hjälp av ett e-postkonto.\n\n Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" – "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"Ta bort"</string>
@@ -1414,7 +1414,7 @@
     </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Försök igen senare"</string>
     <string name="immersive_cling_title" msgid="8394201622932303336">"Visar på fullskärm"</string>
-    <string name="immersive_cling_description" msgid="3482371193207536040">"Dra nedåt från skärmens överkant för att avsluta."</string>
+    <string name="immersive_cling_description" msgid="3482371193207536040">"Svep nedåt från skärmens överkant för att avsluta."</string>
     <string name="immersive_cling_positive" msgid="5016839404568297683">"OK"</string>
     <string name="done_label" msgid="2093726099505892398">"Klart"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Cirkelreglage för timmar"</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 d367887..1d54bcf 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -589,7 +589,7 @@
     <string name="phoneTypeTelex" msgid="3367879952476250512">"Teleksi"</string>
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Nambari ya Simu ya Mkononi ya Kazini"</string>
-    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Kiunda ujumbe cha Kazini"</string>
+    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Peja ya Kazini"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"Msaidizi"</string>
     <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
     <string name="eventTypeCustom" msgid="7837586198458073404">"Maalum"</string>
@@ -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..960a314 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">"Servis 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 439b9f2..8c0b816 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -530,8 +530,8 @@
     <item msgid="8901098336658710359">"Nhà riêng"</item>
     <item msgid="869923650527136615">"Di Động"</item>
     <item msgid="7897544654242874543">"Cơ quan"</item>
-    <item msgid="1103601433382158155">"Số fax Cơ quan"</item>
-    <item msgid="1735177144948329370">"Số fax Nhà riêng"</item>
+    <item msgid="1103601433382158155">"Số fax cơ quan"</item>
+    <item msgid="1735177144948329370">"Số fax nhà riêng"</item>
     <item msgid="603878674477207394">"Số máy nhắn tin"</item>
     <item msgid="1650824275177931637">"Khác"</item>
     <item msgid="9192514806975898961">"Tùy chỉnh"</item>
@@ -573,8 +573,8 @@
     <string name="phoneTypeHome" msgid="2570923463033985887">"Nhà riêng"</string>
     <string name="phoneTypeMobile" msgid="6501463557754751037">"Di Động"</string>
     <string name="phoneTypeWork" msgid="8863939667059911633">"Cơ quan"</string>
-    <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Số fax Cơ quan"</string>
-    <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Số fax Nhà riêng"</string>
+    <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Số fax cơ quan"</string>
+    <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Số fax nhà riêng"</string>
     <string name="phoneTypePager" msgid="7582359955394921732">"Số máy nhắn tin"</string>
     <string name="phoneTypeOther" msgid="1544425847868765990">"Khác"</string>
     <string name="phoneTypeCallback" msgid="2712175203065678206">"Số gọi lại"</string>
@@ -586,8 +586,8 @@
     <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string>
     <string name="phoneTypeTelex" msgid="3367879952476250512">"Số telex"</string>
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
-    <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Số điện thoại di động tại Cơ quan"</string>
-    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Số Máy nhắn tin tại Cơ quan"</string>
+    <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Di động tại cơ quan"</string>
+    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Số máy nhắn tin cơ quan"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"Số điện thoại Hỗ trợ"</string>
     <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
     <string name="eventTypeCustom" msgid="7837586198458073404">"Tùy chỉnh"</string>
@@ -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ụ"</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 c13ef14..29b729a 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -223,7 +223,7 @@
     <string name="global_action_lockdown" msgid="8751542514724332873">"立即锁定"</string>
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
-    <string name="android_system_label" msgid="6577375335728551336">"Android系统"</string>
+    <string name="android_system_label" msgid="6577375335728551336">"Android 系统"</string>
     <string name="user_owner_label" msgid="2804351898001038951">"个人"</string>
     <string name="managed_profile_label" msgid="6260850669674791528">"工作"</string>
     <string name="permgrouplab_contacts" msgid="3657758145679177612">"通讯录"</string>
@@ -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-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 091d57f..07ac471 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1059,7 +1059,7 @@
          at the same time.
 
          <p>The default value is <code>false</code> for applications with
-         <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#M} and
+         <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and
          <code>true</code> otherwise.
 
          <p>NOTE: A task's root activity value is applied to all additional activities launched in
@@ -1067,11 +1067,24 @@
          all other activities in the task as resizeable and will not if the root activity isn't
          resizeable.
 
-         <p>NOTE: The value of {@link android.R.attr#screenOrientation} will be ignored for
-         resizeable activities as the system doesn't support fixed orientation on a resizeable
-         activity. -->
+         <p>NOTE: The value of {@link android.R.attr#screenOrientation} is ignored for
+         resizeable activities when in multi-window mode. -->
     <attr name="resizeableActivity" format="boolean" />
 
+    <!-- Indicates that the activity supports the picture-in-picture (PiP) form of multi-window.
+         While it makes sense to be able to resize most activities types in multi-window mode when
+         {@link android.R.attr#resizeableActivity} is set. It only makes sense to put specific types
+         of activities in PiP mode of multi-window. For example, activities that play video. When
+         set the activity will be allowed to enter PiP mode when the system deems it appropriate on
+         devices that support PiP.
+
+         <p>The default value is <code>false</code> for applications with
+         <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and
+         <code>true</code> otherwise.
+
+         <p>NOTE: Attribute is only used if {@link android.R.attr#resizeableActivity} is true. -->
+    <attr name="supportsPictureInPicture" format="boolean" />
+
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
          While in lockTask mode the system will not launch non-permitted tasks until
          lockTask mode is disabled.
@@ -1826,6 +1839,7 @@
         <attr name="relinquishTaskIdentity" />
         <attr name="resumeWhilePausing" />
         <attr name="resizeableActivity" />
+        <attr name="supportsPictureInPicture" />
         <attr name="lockTaskMode" />
         <attr name="showForAllUsers" />
     </declare-styleable>
@@ -2093,6 +2107,10 @@
             <enum name="hdpi" value="240" />
             <!-- An extra high density screen, approximately 320dpi. -->
             <enum name="xhdpi" value="320" />
+            <!-- An extra extra high density screen, approximately 480dpi. -->
+            <enum name="xxhdpi" value="480" />
+            <!-- An extra extra extra high density screen, approximately 640dpi. -->
+            <enum name="xxxhdpi" value="640" />
         </attr>
     </declare-styleable>
 
@@ -2193,18 +2211,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/colors_holo.xml b/core/res/res/values/colors_holo.xml
index 9d1dda5..917c781 100644
--- a/core/res/res/values/colors_holo.xml
+++ b/core/res/res/values/colors_holo.xml
@@ -78,14 +78,14 @@
     <!-- Forward compatibility for Material-style theme colors -->
     <eat-comment />
 
-    <color name="holo_primary_dark">#ff000000</color>
+    <color name="holo_primary_dark">@color/black</color>
     <color name="holo_primary">#ff222222</color>
     <color name="holo_control_activated">@color/holo_blue_light</color>
     <color name="holo_control_normal">#39cccccc</color>
     <color name="holo_button_pressed">#59f0f0f0</color>
     <color name="holo_button_normal">#bd292f34</color>
 
-    <color name="holo_light_primary_dark">#ff000000</color>
+    <color name="holo_light_primary_dark">@color/black</color>
     <color name="holo_light_primary">#ffe6e6e6</color>
     <color name="holo_light_control_activated">@color/holo_blue_light</color>
     <color name="holo_light_control_normal">#dacccccc</color>
diff --git a/core/res/res/values/colors_legacy.xml b/core/res/res/values/colors_legacy.xml
index ad22845..a3ce652 100644
--- a/core/res/res/values/colors_legacy.xml
+++ b/core/res/res/values/colors_legacy.xml
@@ -17,6 +17,12 @@
 <!-- Colors specific to pre-Holo themes. -->
 <resources>
 
+    <!-- A bright Android-y green -->
+    <color name="legacy_green">#ff90df25</color>
+
+    <!-- A bright orange suitable for use in the early 2000s -->
+    <color name="legacy_orange">#fffea50b</color>
+
     <!-- Highlight colors for the legacy themes -->
     <eat-comment />
 
@@ -27,19 +33,17 @@
     <!-- Forward compatibility for Material-style theme colors -->
     <eat-comment />
 
-    <color name="legacy_primary_dark">#ff000000</color>
-    <color name="legacy_primary">#ffe6e6e6</color>
-    <color name="legacy_primary_light">#ffffffff</color>
-    <color name="legacy_control_activated">#ff90df25</color>
+    <color name="legacy_primary_dark">@color/black</color>
+    <color name="legacy_primary">#ff222222</color>
+    <color name="legacy_control_activated">@color/legacy_green</color>
     <color name="legacy_control_normal">#99ffffff</color>
-    <color name="legacy_button_pressed">#fffea50b</color>
+    <color name="legacy_button_pressed">@color/legacy_orange</color>
     <color name="legacy_button_normal">#f3dbdbdb</color>
 
-    <color name="legacy_light_primary_dark">@color/legacy_primary_dark</color>
-    <color name="legacy_light_primary">@color/legacy_primary</color>
-    <color name="legacy_light_primary_light">@color/legacy_primary_light</color>
-    <color name="legacy_light_control_activated">@color/legacy_control_activated</color>
+    <color name="legacy_light_primary_dark">@color/black</color>
+    <color name="legacy_light_primary">#ffe6e6e6</color>
+    <color name="legacy_light_control_activated">@color/legacy_green</color>
     <color name="legacy_light_control_normal">#99000000</color>
-    <color name="legacy_light_button_pressed">@color/legacy_button_pressed</color>
-    <color name="legacy_light_button_normal">@color/legacy_button_normal</color>
+    <color name="legacy_light_button_pressed">@color/legacy_orange</color>
+    <color name="legacy_light_button_normal">#f3dbdbdb</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 24d760f..400c822 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -938,6 +938,9 @@
     <!-- Is the device capable of hot swapping an UICC Card -->
     <bool name="config_hotswapCapable">false</bool>
 
+    <!-- Component name of the ICC hotswap prompt for restart dialog -->
+    <string name="config_iccHotswapPromptForRestartDialogComponent" translateable="false">@null</string>
+
     <!-- Enable puk unlockscreen by default.
          If unlock screen is disabled, the puk should be unlocked through Emergency Dialer -->
     <bool name="config_enable_puk_unlock_screen">true</bool>
@@ -1246,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>
 
@@ -1282,6 +1288,10 @@
          device is data-only. -->
     <bool name="config_voice_capable">true</bool>
 
+    <!-- Flag indicating that an outbound call must have a call capable phone account
+         that has declared it can process the call's handle. -->
+    <bool name="config_requireCallCapableAccountForHandle">false</bool>
+
     <!-- Flag indicating if the user is notified when the mobile network access is restricted -->
     <bool name="config_user_notification_of_restrictied_mobile_access">true</bool>
 
@@ -2318,7 +2328,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>
 
@@ -2349,4 +2359,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..037f1c4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2662,9 +2662,11 @@
 
     <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="supportsPictureInPicture" />
     <public type="attr" name="titleMargin" />
     <public type="attr" name="titleMarginStart" />
     <public type="attr" name="titleMarginEnd" />
@@ -2676,6 +2678,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 af5c74d..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>
@@ -3993,6 +3993,9 @@
     <!-- Zen mode condition - line two: ending time. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_until">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
 
+    <!-- Zen mode condition - line one: Until next alarm. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_alarm">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g> (next alarm)</string>
+
     <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_forever">Until you turn this off</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 06de81d..951a825 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,7 @@
   <java-symbol type="bool" name="config_ui_enableFadingMarquee" />
   <java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
   <java-symbol type="bool" name="config_voice_capable" />
+  <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
   <java-symbol type="bool" name="config_user_notification_of_restrictied_mobile_access" />
   <java-symbol type="bool" name="config_wifiDisplaySupportsProtectedBuffers" />
   <java-symbol type="bool" name="preferences_prefer_dual_pane" />
@@ -370,6 +371,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 +1688,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 +1958,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" />
@@ -2078,6 +2082,7 @@
   <java-symbol type="string" name="zen_mode_default_events_name" />
   <java-symbol type="array" name="config_system_condition_providers" />
   <java-symbol type="string" name="muted_by" />
+  <java-symbol type="string" name="zen_mode_alarm" />
 
   <java-symbol type="string" name="select_day" />
   <java-symbol type="string" name="select_year" />
@@ -2326,4 +2331,8 @@
   <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
 
   <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/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index 6d20503..7164747 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -618,7 +618,7 @@
     }
 
     @SmallTest
-    public void testASeek() throws Throwable {
+    public void testSeek() throws Throwable {
         final MyListener l1 = new MyListener();
         final MyListener l2 = new MyListener();
         final MyUpdateListener updateListener1 = new MyUpdateListener();
@@ -757,6 +757,227 @@
 
     }
 
+    @SmallTest
+    public void testSeekWhileRunning() throws Throwable {
+        // Seek one animator to the beginning and the other one to the end when they are running.
+        final MyListener l1 = new MyListener();
+        final MyListener l2 = new MyListener();
+        a1.addListener(l1);
+        a2.addListener(l2);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.startCalled);
+                assertFalse(l2.startCalled);
+                assertEquals(0f, a1.getAnimatedFraction());
+                assertEquals(0f, a2.getAnimatedFraction());
+                a1.start();
+                a2.start();
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.endCalled);
+                assertFalse(l2.endCalled);
+                assertTrue(a1.isRunning());
+                assertTrue(a2.isRunning());
+                // During the run, seek one to the beginning, the other to the end
+                a1.setCurrentFraction(0f);
+                a2.setCurrentFraction(1f);
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Check that a2 has finished due to the seeking, but a1 hasn't finished.
+                assertFalse(l1.endCalled);
+                assertTrue(l2.endCalled);
+                assertEquals(1f, a2.getAnimatedFraction());
+            }
+        });
+
+        Thread.sleep(a1.getTotalDuration());
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // By now a1 should finish also.
+                assertTrue(l1.endCalled);
+                assertEquals(1f, a1.getAnimatedFraction());
+            }
+        });
+    }
+
+    @SmallTest
+    public void testZeroDuration() throws Throwable {
+        // Run two animators with zero duration, with one running forward and the other one
+        // backward. Check that the animations start and finish with the correct end fractions.
+        a1.setDuration(0);
+        a2.setDuration(0);
+
+        // Set a fraction on an animation with 0-duration
+        final ValueAnimator a3 = ValueAnimator.ofInt(0, 100);
+        a3.setDuration(0);
+        a3.setCurrentFraction(1.0f);
+        assertEquals(1.0f, a3.getAnimatedFraction());
+
+        final MyListener l1 = new MyListener();
+        final MyListener l2 = new MyListener();
+        final MyListener l3 = new MyListener();
+        a1.addListener(l1);
+        a2.addListener(l2);
+        a3.addListener(l3);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.startCalled);
+                assertFalse(l2.startCalled);
+                assertFalse(l3.startCalled);
+                assertFalse(l1.endCalled);
+                assertFalse(l2.endCalled);
+                assertFalse(l3.endCalled);
+                a1.start();
+                a2.reverse();
+                a3.start();
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Check that the animators have started and finished with the right values.
+                assertTrue(l1.startCalled);
+                assertTrue(l2.startCalled);
+                assertTrue(l3.startCalled);
+                assertTrue(l1.endCalled);
+                assertTrue(l2.endCalled);
+                assertTrue(l3.endCalled);
+                assertEquals(1.0f, a1.getAnimatedFraction());
+                assertEquals(0f, a2.getAnimatedFraction());
+                assertEquals(1f, a3.getAnimatedFraction());
+                assertEquals(A1_END_VALUE, a1.getAnimatedValue());
+                assertEquals(A2_START_VALUE, a2.getAnimatedValue());
+                assertEquals(100, a3.getAnimatedValue());
+            }
+        });
+    }
+
+    @SmallTest
+    public void testZeroScale() throws Throwable {
+        // Test whether animations would end properly when the scale is forced to be zero
+        float scale = ValueAnimator.getDurationScale();
+        ValueAnimator.setDurationScale(0f);
+
+        // Run two animators, one of which has a start delay, after setting the duration scale to 0
+        a1.setStartDelay(200);
+        final MyListener l1 =  new MyListener();
+        final MyListener l2 = new MyListener();
+        a1.addListener(l1);
+        a2.addListener(l2);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.startCalled);
+                assertFalse(l2.startCalled);
+                assertFalse(l1.endCalled);
+                assertFalse(l2.endCalled);
+
+                a1.start();
+                a2.start();
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(l1.startCalled);
+                assertTrue(l2.startCalled);
+                assertTrue(l1.endCalled);
+                assertTrue(l2.endCalled);
+            }
+        });
+
+        // Restore duration scale
+        ValueAnimator.setDurationScale(scale);
+    }
+
+    @SmallTest
+    public void testReverse() throws Throwable {
+        // Prolong animators duration so that we can do multiple checks during their run
+        final ValueAnimator a3 = ValueAnimator.ofInt(0, 100);
+        a1.setDuration(400);
+        a2.setDuration(600);
+        a3.setDuration(400);
+        final MyListener l1 = new MyListener();
+        final MyListener l2 = new MyListener();
+        final MyListener l3 = new MyListener();
+        a1.addListener(l1);
+        a2.addListener(l2);
+        a3.addListener(l3);
+
+        // Reverse three animators, seek one to the beginning and another to the end, and force
+        // to end the third one during reversing.
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.startCalled);
+                assertFalse(l2.startCalled);
+                assertFalse(l3.startCalled);
+                assertFalse(l1.endCalled);
+                assertFalse(l2.endCalled);
+                assertFalse(l3.endCalled);
+                a1.reverse();
+                a2.reverse();
+                a3.reverse();
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(l1.startCalled);
+                assertTrue(l2.startCalled);
+                assertTrue(l3.startCalled);
+
+                a1.setCurrentFraction(0f);
+                a2.setCurrentFraction(1f);
+                a3.end();
+
+                // Check that the fraction has been set, and the getter returns the correct values.
+                assertEquals(1f, a1.getAnimatedFraction());
+                assertEquals(0f, a2.getAnimatedFraction());
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+
+        // By now, a2 should have finished due to the seeking. It wouldn't have finished otherwise.
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Check that both animations have started, and a2 has finished.
+                assertFalse(l1.endCalled);
+                assertTrue(l2.endCalled);
+                assertTrue(l3.endCalled);
+            }
+        });
+        Thread.sleep(a1.getTotalDuration());
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Verify that a1 has finished as well.
+                assertTrue(l1.endCalled);
+                assertEquals(0f, a1.getAnimatedFraction());
+                assertEquals(0f, a2.getAnimatedFraction());
+                assertEquals(0f, a3.getAnimatedFraction());
+            }
+        });
+    }
+
     class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
         boolean wasRunning = false;
         long firstRunningFrameTime = -1;
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index 075ceaa..b6b4f4f 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -48,6 +48,11 @@
     private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
     private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
 
+    // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
+    private InetAddress Address(String addrString) {
+        return NetworkUtils.numericToInetAddress(addrString);
+    }
+
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
         // Check implementation of equals(), element by element.
         assertTrue(source.isIdenticalInterfaceName(target));
@@ -647,5 +652,26 @@
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertTrue(v6lp.isReachable(kOnLinkDns));
         assertTrue(v6lp.isReachable(DNS6));
+
+        // Check isReachable on stacked links. This requires that the source IP address be assigned
+        // on the interface returned by the route lookup.
+        LinkProperties stacked = new LinkProperties();
+
+        // Can't add a stacked link without an interface name.
+        stacked.setInterfaceName("v4-test0");
+        v6lp.addStackedLink(stacked);
+
+        InetAddress stackedAddress = Address("192.0.0.4");
+        LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addLinkAddress(stackedLinkAddress);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addRoute(new RouteInfo(stackedLinkAddress));
+        assertTrue(stacked.isReachable(stackedAddress));
+        assertTrue(v6lp.isReachable(stackedAddress));
+
+        assertFalse(v6lp.isReachable(DNS1));
+        stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
+        assertTrue(v6lp.isReachable(DNS1));
     }
 }
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/core/tests/coretests/src/android/util/OrientationUtil.java b/core/tests/coretests/src/android/util/OrientationUtil.java
new file mode 100644
index 0000000..ecdca5d
--- /dev/null
+++ b/core/tests/coretests/src/android/util/OrientationUtil.java
@@ -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
+ */
+
+package android.util;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.pm.ActivityInfo;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Utilities for manipulating screen orientation.
+ */
+public final class OrientationUtil {
+
+    private final Activity mActivity;
+    private final Instrumentation mInstrumentation;
+
+    private final Runnable mSetToPortrait = new Runnable() {
+        @Override
+        public void run() {
+            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+    };
+
+    private final Runnable mSetToLandscape = new Runnable() {
+        @Override
+        public void run() {
+            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        }
+    };
+
+    public static OrientationUtil initializeAndStartActivityIfNotStarted(
+            ActivityInstrumentationTestCase2 testCase) {
+        Preconditions.checkNotNull(testCase);
+        return new OrientationUtil(testCase.getActivity(), testCase.getInstrumentation());
+    }
+
+    private OrientationUtil(Activity activity, Instrumentation instrumentation) {
+        mActivity = activity;
+        mInstrumentation = instrumentation;
+    }
+
+    public void setPortraitOrientation() {
+        mInstrumentation.runOnMainSync(mSetToPortrait);
+    }
+
+    public void setLandscapeOrientation() {
+        mInstrumentation.runOnMainSync(mSetToLandscape);
+    }
+}
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
index 74c8e04..2370627 100644
--- a/core/tests/coretests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java
@@ -436,15 +436,17 @@
         assertFormatDuration("+100ms", 100);
         assertFormatDuration("+101ms", 101);
         assertFormatDuration("+330ms", 330);
+        assertFormatDuration("+1s0ms", 1000);
         assertFormatDuration("+1s330ms", 1330);
         assertFormatDuration("+10s24ms", 10024);
+        assertFormatDuration("+1m0s30ms", 60030);
+        assertFormatDuration("+1h0m0s30ms", 3600030);
+        assertFormatDuration("+1d0h0m0s30ms", 86400030);
     }
 
     public void testFormatHugeDuration() {
-        //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
-        // TODO: improve formatDuration() API
-        assertFormatDuration("+999d23h59m59s999ms", 1342833071555L);
-        assertFormatDuration("-999d23h59m59s999ms", -1342833071555L);
+        assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
+        assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L);
     }
 
     private void assertFormatDuration(String expected, long duration) {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 4db1d9a..6a76a27 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -36,6 +36,7 @@
 
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.OrientationUtil;
 import android.view.KeyEvent;
 
 /**
@@ -43,14 +44,20 @@
  */
 public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
 
+    private OrientationUtil mOrientationUtil;
+
     public TextViewActivityTest() {
         super(TextViewActivity.class);
     }
 
+    @Override
+    public void setUp() {
+        mOrientationUtil = OrientationUtil.initializeAndStartActivityIfNotStarted(this);
+        mOrientationUtil.setPortraitOrientation();
+    }
+
     @SmallTest
     public void testTypedTextIsOnScreen() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello world!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -60,8 +67,6 @@
 
     @SmallTest
     public void testPositionCursorAtTextAtIndex() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello world!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -74,8 +79,6 @@
 
     @SmallTest
     public void testLongPressToSelect() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello Kirk!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -87,8 +90,6 @@
 
     @SmallTest
     public void testLongPressEmptySpace() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello big round sun!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -102,8 +103,6 @@
 
     @SmallTest
     public void testLongPressAndDragToSelect() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello little handsome boy!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -115,8 +114,6 @@
 
     @SmallTest
     public void testDoubleTapToSelect() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello SuetYi!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -128,8 +125,6 @@
 
     @SmallTest
     public void testDoubleTapAndDragToSelect() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello young beautiful girl!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -141,8 +136,6 @@
 
     @SmallTest
     public void testSelectBackwordsByTouch() throws Exception {
-        getActivity();
-
         final String helloWorld = "Hello king of the Jungle!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
@@ -154,8 +147,6 @@
 
     @SmallTest
     public void testToolbarAppearsAfterSelection() throws Exception {
-        getActivity();
-
         // It'll be nice to check that the toolbar is not visible (or does not exist) here
         // I can't currently find a way to do this. I'll get to it later.
 
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/data/keyboards/Vendor_0079_Product_0011.kl b/data/keyboards/Vendor_0079_Product_0011.kl
index 2ae2a01..32f8c82 100644
--- a/data/keyboards/Vendor_0079_Product_0011.kl
+++ b/data/keyboards/Vendor_0079_Product_0011.kl
@@ -12,10 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Classic NES Controller
+# Classic [S]NES Controller
 
+key 288 BUTTON_X
 key 289 BUTTON_A
 key 290 BUTTON_B
+key 291 BUTTON_Y
+key 292 BUTTON_L1
+key 293 BUTTON_R1
 key 297 BUTTON_START
 key 296 BUTTON_SELECT
 
diff --git a/data/keyboards/Vendor_18d1_Product_5018.kcm b/data/keyboards/Vendor_18d1_Product_5018.kcm
new file mode 100644
index 0000000..0ca85a2
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_5018.kcm
@@ -0,0 +1,321 @@
+# 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.
+
+#
+# Key character map for Google Pixel C Keyboard
+#
+
+type FULL
+
+### Basic QWERTY keys ###
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+    alt:                                '\u00e7'
+    shift+alt:                          '\u00c7'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+    alt:                                '\u0301'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+    alt:                                '\u0302'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+    alt:                                '\u0303'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+    ralt:                               '['
+    ralt+shift:                         '{'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+    ralt:                               ']'
+    ralt+shift:                         '}'
+}
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+    alt:                                '\u00df'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+    alt:                                '\u0308'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+    ralt:                               replace ESCAPE
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+    ralt:                               '`'
+    ralt+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:                              '^'
+    alt+shift:                          '\u0302'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key SPACE {
+    label:                              ' '
+    base:                               ' '
+    alt, meta:                          fallback SEARCH
+    ctrl:                               fallback LANGUAGE_SWITCH
+}
+
+key ENTER {
+    label:                              '\n'
+    base:                               '\n'
+}
+
+key TAB {
+    label:                              '\t'
+    base:                               '\t'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+    ralt:                               '\\'
+    ralt+shift:                         '|'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift:                              ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+}
+
+### Non-printing keys ###
+
+key ESCAPE {
+    base:                               fallback BACK
+    alt, meta:                          fallback HOME
+    ctrl:                               fallback MENU
+}
diff --git a/data/keyboards/Vendor_18d1_Product_5018.kl b/data/keyboards/Vendor_18d1_Product_5018.kl
new file mode 100644
index 0000000..e95ccb5
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_5018.kl
@@ -0,0 +1,84 @@
+# 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.
+
+#
+# Key layout for Google Pixel C Keyboard
+#
+
+# Row 1
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 12    MINUS
+key 14    DEL         # Backspace
+
+# Row 2
+key 15    TAB
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 13    EQUALS
+key 28    ENTER
+
+# Row 3
+key 125   META_LEFT   # "Search key"
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+
+# Row 4
+key 42    SHIFT_LEFT
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 54    SHIFT_RIGHT
+
+# Row 5
+key 29    CTRL_LEFT
+key 56    ALT_LEFT
+key 57    SPACE
+key 100   ALT_RIGHT
+key 103   DPAD_UP
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 108   DPAD_DOWN
diff --git a/docs/html/guide/topics/manifest/compatible-screens-element.jd b/docs/html/guide/topics/manifest/compatible-screens-element.jd
index 3606b15..de921d2 100644
--- a/docs/html/guide/topics/manifest/compatible-screens-element.jd
+++ b/docs/html/guide/topics/manifest/compatible-screens-element.jd
@@ -9,7 +9,7 @@
 <pre>
 &lt;<a href="#compatible-screens">compatible-screens</a>&gt;
     &lt;<a href="#screen">screen</a> android:<a href="#screenSize">screenSize</a>=["small" | "normal" | "large" | "xlarge"]
-            android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi"] /&gt;
+            android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi" | "xxhdpi" | "xxxhdpi"] /&gt;
     ...
 &lt;/compatible-screens&gt;
 </pre>
@@ -94,11 +94,9 @@
             <li>{@code mdpi}</li>
             <li>{@code hdpi}</li>
             <li>{@code xhdpi}</li>
+            <li>{@code xxhdpi}</li>
+            <li>{@code xxxhdpi}</li>
           </ul>
-          <p class="note"><strong>Note:</strong> This attribute currently does not accept
-          {@code xxhdpi} as a valid value, but you can instead specify {@code 480}
-          as the value, which is the approximate threshold for xhdpi screens.</p>
-
           <p>For information about the different screen densities, see <a
 href="{@docRoot}guide/practices/screens_support.html#range">Supporting Multiple Screens</a>.</p>
         </dd>
@@ -110,8 +108,8 @@
 <dt>example</dt>
 <dd>
 <p>If your application is compatible with only small and normal screens, regardless
-of screen density, then you must specify eight different {@code &lt;screen&gt;} elements,
-because each screen size has four different density configurations. You must declare each one of
+of screen density, then you must specify twelve different {@code &lt;screen&gt;} elements,
+because each screen size has six different density configurations. You must declare each one of
 these; any combination of size and density that you do <em>not</em> specify is considered a screen
 configuration with which your application is <em>not</em> compatible. Here's what the manifest
 entry looks like if your application is compatible with only small and normal screens:</p>
@@ -125,11 +123,15 @@
         &lt;screen android:screenSize="small" android:screenDensity="mdpi" />
         &lt;screen android:screenSize="small" android:screenDensity="hdpi" />
         &lt;screen android:screenSize="small" android:screenDensity="xhdpi" />
+        &lt;screen android:screenSize="small" android:screenDensity="xxhdpi" />
+        &lt;screen android:screenSize="small" android:screenDensity="xxxhdpi" />
         &lt;!-- all normal size screens -->
         &lt;screen android:screenSize="normal" android:screenDensity="ldpi" />
         &lt;screen android:screenSize="normal" android:screenDensity="mdpi" />
         &lt;screen android:screenSize="normal" android:screenDensity="hdpi" />
         &lt;screen android:screenSize="normal" android:screenDensity="xhdpi" />
+        &lt;screen android:screenSize="normal" android:screenDensity="xxhdpi" />
+        &lt;screen android:screenSize="normal" android:screenDensity="xxxhdpi" />
     &lt;/compatible-screens>
     &lt;application ... >
         ...
diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd
index 07ca45a..c179a2e 100644
--- a/docs/html/guide/topics/resources/providing-resources.jd
+++ b/docs/html/guide/topics/resources/providing-resources.jd
@@ -646,7 +646,8 @@
         <code>xxhdpi</code><br/>
         <code>xxxhdpi</code><br/>
         <code>nodpi</code><br/>
-        <code>tvdpi</code>
+        <code>tvdpi</code><br/>
+        <code>anydpi</code>
       </td>
       <td>
         <ul class="nolist">
@@ -667,7 +668,11 @@
           <li>{@code tvdpi}: Screens somewhere between mdpi and hdpi; approximately 213dpi. This is
 not considered a "primary" density group. It is mostly intended for televisions and most
 apps shouldn't need it&mdash;providing mdpi and hdpi resources is sufficient for most apps and
-the system will scale them as appropriate. This qualifier was introduced with API level 13.</li>
+the system will scale them as appropriate. <em>Added in API Level 13</em></li>
+          <li>{@code anydpi}: This qualifier matches all screen densities and takes precedence over
+other qualifiers. This is useful for
+<a href="{@docRoot}training/material/drawables.html#VectorDrawables">vector drawables</a>.
+<em>Added in API Level 21</em></li>
         </ul>
         <p>There is a 3:4:6:8:12:16 scaling ratio between the six primary densities (ignoring the
 tvdpi density). So, a 9x9 bitmap in ldpi is 12x12 in mdpi, 18x18 in hdpi, 24x24 in xhdpi and so on.
diff --git a/docs/html/sdk/installing/index.jd b/docs/html/sdk/installing/index.jd
index dc258db..0375a2f 100644
--- a/docs/html/sdk/installing/index.jd
+++ b/docs/html/sdk/installing/index.jd
@@ -42,9 +42,9 @@
 JDK 6 or higher (the JRE alone is not sufficient)&mdash;JDK 7 is required when
 developing for Android 5.0 and higher. To check if you
 have JDK installed (and which version), open a terminal and type <code>javac -version</code>.
-If the JDK is not available or the version is lower than 6,
-<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" class="external-link"
->go download JDK</a>.</p>
+If the JDK is not available or the version is lower than version 6, download the 
+<a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html" class="external-link"
+>Java SE Development Kit 7</a>.</p>
 
 
 <div class="procedure-box">
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/Point.java b/graphics/java/android/graphics/Point.java
index e0d8ccc..abcccbd 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.io.PrintWriter;
+
 
 /**
  * Point holds two integer coordinates
@@ -95,6 +97,11 @@
         return "Point(" + x + ", " + y + ")";
     }
 
+    /** @hide */
+    public void printShortString(PrintWriter pw) {
+        pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
+    }
+
     /**
      * Parcelable interface methods
      */
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 cc0943f..4385e70 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,8 @@
 include $(CLEAR_VARS)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
+HWUI_NEW_OPS := false
+
 hwui_src_files := \
     font/CacheTexture.cpp \
     font/Font.cpp \
@@ -25,6 +27,7 @@
     utils/LinearAllocator.cpp \
     utils/NinePatchImpl.cpp \
     utils/StringUtils.cpp \
+    utils/TestWindowContext.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -82,8 +85,17 @@
 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 += \
+        BakedOpRenderer.cpp \
+        OpReorderer.cpp \
+        RecordingCanvas.cpp
+
+    hwui_cflags += -DHWUI_NEW_OPS
+
+endif
 
 ifndef HWUI_COMPILE_SYMBOLS
     hwui_cflags += -fvisibility=hidden
@@ -141,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
 # ------------------------
 
@@ -162,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 += \
@@ -172,6 +205,13 @@
     unit_tests/LinearAllocatorTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
+ifeq (true, $(HWUI_NEW_OPS))
+    LOCAL_SRC_FILES += \
+        unit_tests/BakedOpStateTests.cpp \
+        unit_tests/RecordingCanvasTests.cpp \
+        unit_tests/OpReordererTests.cpp
+endif
+
 include $(BUILD_NATIVE_TEST)
 
 # ------------------------
@@ -190,25 +230,43 @@
 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 \
+    tests/TreeContentAnimation.cpp \
     tests/main.cpp
 
 include $(BUILD_EXECUTABLE)
+
+# ------------------------
+# Micro-bench app
+# ---------------------
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_MODULE:= hwuimicro
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := hwuimicro
+LOCAL_MODULE_STEM_64 := hwuimicro64
+LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
+LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_C_INCLUDES += bionic/benchmarks/
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
+LOCAL_STATIC_LIBRARIES := libbenchmark libbase
+
+LOCAL_SRC_FILES += \
+    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
new file mode 100644
index 0000000..7dbba25
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -0,0 +1,215 @@
+/*
+ * 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 "BakedOpRenderer.h"
+
+#include "Caches.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+void BakedOpRenderer::Info::setViewport(uint32_t width, uint32_t height) {
+    viewportWidth = width;
+    viewportHeight = height;
+    orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
+
+    renderState.setViewport(width, height);
+    renderState.blend().syncEnabled();
+}
+
+Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
+    Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
+    if (!texture) {
+        return caches.textureCache.get(bitmap);
+    }
+    return texture;
+}
+
+void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
+    bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
+    renderState.scissor().setEnabled(useScissor);
+    if (useScissor) {
+        const Rect& clip = state.computedState.clipRect;
+        renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
+            clip.getWidth(), clip.getHeight());
+    }
+    renderState.render(glop, orthoMatrix);
+    didDraw = true;
+}
+
+Layer* BakedOpRenderer::startLayer(Info& info, uint32_t width, uint32_t height) {
+    info.caches.textureState().activateTexture(0);
+    Layer* layer = info.caches.layerCache.get(info.renderState, width, height);
+    LOG_ALWAYS_FATAL_IF(!layer, "need layer...");
+
+    info.layer = layer;
+    layer->texCoords.set(0.0f, width / float(layer->getHeight()),
+            height / float(layer->getWidth()), 0.0f);
+
+    layer->setFbo(info.renderState.genFramebuffer());
+    info.renderState.bindFramebuffer(layer->getFbo());
+    layer->bindTexture();
+
+    // Initialize the texture if needed
+    if (layer->isEmpty()) {
+        layer->allocateTexture();
+        layer->setEmpty(false);
+    }
+
+    // attach the texture to the FBO
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+            layer->getTextureId(), 0);
+    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
+    LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
+            "framebuffer incomplete!");
+
+    // Clear the FBO
+    info.renderState.scissor().setEnabled(false);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    // Change the viewport & ortho projection
+    info.setViewport(width, height);
+    return layer;
+}
+
+void BakedOpRenderer::endLayer(Info& info) {
+    Layer* layer = info.layer;
+    info.layer = nullptr;
+
+    // Detach the texture from the FBO
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
+    layer->removeFbo(false);
+}
+
+void BakedOpRenderer::startFrame(Info& info, uint32_t width, uint32_t height) {
+    info.renderState.bindFramebuffer(0);
+    info.setViewport(width, height);
+    Caches::getInstance().clearGarbage();
+
+    if (!info.opaque) {
+        // TODO: partial invalidate!
+        info.renderState.scissor().setEnabled(false);
+        glClear(GL_COLOR_BUFFER_BIT);
+        info.didDraw = true;
+    }
+}
+void BakedOpRenderer::endFrame(Info& info) {
+    info.caches.pathCache.trim();
+    info.caches.tessellationCache.trim();
+
+#if DEBUG_OPENGL
+    GLUtils::dumpGLErrors();
+#endif
+
+#if DEBUG_MEMORY_USAGE
+    info.caches.dumpMemoryUsage();
+#else
+    if (Properties::debugLevel & kDebugMemory) {
+        info.caches.dumpMemoryUsage();
+    }
+#endif
+}
+
+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);
+    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)
+            .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);
+}
+
+void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
+    Glop 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);
+}
+
+void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
+    Glop 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);
+}
+
+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) {
+    Layer* layer = *op.layerHandle;
+
+    // TODO: make this work for HW layers
+    layer->setPaint(op.paint);
+    layer->setBlend(true);
+    float layerAlpha = (layer->getAlpha() / 255.0f) * state.alpha;
+
+    const bool tryToSnap = state.computedState.transform.isPureTranslate();
+    Glop glop;
+    GlopBuilder(info.renderState, info.caches, &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUvQuad(nullptr, layer->texCoords)
+            .setFillLayer(layer->getTexture(), layer->getColorFilter(), layerAlpha, layer->getMode(), Blend::ModeOrderSwap::NoSwap)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+            .build();
+    info.renderGlop(state, glop);
+
+    // return layer to cache, since each clipped savelayer is only drawn once.
+    layer->setConvexMask(nullptr);
+    if (!info.caches.layerCache.put(layer)) {
+        // Failing to add the layer to the cache should happen only if the layer is too large
+        LAYER_LOGD("Deleting layer");
+        layer->decStrong(nullptr);
+    }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
new file mode 100644
index 0000000..616adde
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.h
@@ -0,0 +1,78 @@
+/*
+ * 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 ANDROID_HWUI_BAKED_OP_RENDERER_H
+#define ANDROID_HWUI_BAKED_OP_RENDERER_H
+
+#include "BakedOpState.h"
+#include "Matrix.h"
+
+namespace android {
+namespace uirenderer {
+
+class Caches;
+struct Glop;
+class Layer;
+class RenderState;
+
+class BakedOpRenderer {
+public:
+    class Info {
+    public:
+        Info(Caches& caches, RenderState& renderState, bool opaque)
+                : renderState(renderState)
+                , caches(caches)
+                , opaque(opaque) {
+        }
+
+        void setViewport(uint32_t width, uint32_t height);
+
+        Texture* getTexture(const SkBitmap* bitmap);
+
+        void renderGlop(const BakedOpState& state, const Glop& glop);
+        RenderState& renderState;
+        Caches& caches;
+
+        bool didDraw = false;
+
+        Layer* layer = nullptr;
+
+        // where should these live? layer state object?
+        bool opaque;
+        uint32_t viewportWidth = 0;
+        uint32_t viewportHeight = 0;
+        Matrix4 orthoMatrix;
+    };
+
+    static Layer* startLayer(Info& info, uint32_t width, uint32_t height);
+    static void endLayer(Info& info);
+    static void startFrame(Info& info, uint32_t width, uint32_t height);
+    static void endFrame(Info& info);
+
+    /**
+     * Declare all "onBitmapOp(...)" style function for every op type.
+     *
+     * 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);
+    MAP_OPS(BAKED_OP_RENDERER_METHOD);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_RENDERER_H
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
new file mode 100644
index 0000000..ddb8c84
--- /dev/null
+++ b/libs/hwui/BakedOpState.h
@@ -0,0 +1,142 @@
+/*
+ * 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 ANDROID_HWUI_BAKED_OP_STATE_H
+#define ANDROID_HWUI_BAKED_OP_STATE_H
+
+#include "Matrix.h"
+#include "RecordedOp.h"
+#include "Rect.h"
+#include "Snapshot.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace OpClipSideFlags {
+    enum {
+        None = 0x0,
+        Left = 0x1,
+        Top = 0x2,
+        Right = 0x4,
+        Bottom = 0x8,
+        Full = 0xF,
+        // ConservativeFull = 0x1F  needed?
+    };
+}
+
+/**
+ * Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot
+ */
+class ResolvedRenderState {
+public:
+    // TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
+    ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) {
+        /* TODO: benchmark a fast path for translate-only matrices, such as:
+        if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
+                && recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
+            float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX();
+            float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY();
+            transform.loadTranslate(translateX, translateY, 0);
+
+            // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
+            clipRect = recordedOp.localClipRect;
+            clipRect.translate(translateX, translateY);
+            clipRect.doIntersect(snapshot.getClipRect());
+            clipRect.snapToPixelBoundaries();
+
+            // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
+            clippedBounds = recordedOp.unmappedBounds;
+            clippedBounds.translate(translateX, translateY);
+        } ... */
+
+        // resolvedMatrix = parentMatrix * localMatrix
+        transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
+
+        // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
+        clipRect = recordedOp.localClipRect;
+        snapshot.transform->mapRect(clipRect);
+        clipRect.doIntersect(snapshot.getRenderTargetClip());
+        clipRect.snapToPixelBoundaries();
+
+        // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
+        clippedBounds = recordedOp.unmappedBounds;
+        transform.mapRect(clippedBounds);
+
+        if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
+        if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
+        if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
+        if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
+        clippedBounds.doIntersect(clipRect);
+
+        /**
+         * TODO: once we support complex clips, we may want to reject to avoid that work where
+         * possible. Should we:
+         * 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp)
+         * 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection
+         *         and early return null in one place.
+         */
+    }
+    Matrix4 transform;
+    Rect clipRect;
+    int clipSideFlags = 0;
+    Rect clippedBounds;
+};
+
+/**
+ * Self-contained op wrapper, containing all resolved state required to draw the op.
+ *
+ * Stashed pointers within all point to longer lived objects, with no ownership implied.
+ */
+class BakedOpState {
+public:
+    static BakedOpState* tryConstruct(LinearAllocator& allocator,
+            const Snapshot& snapshot, const RecordedOp& recordedOp) {
+        BakedOpState* bakedOp = new (allocator) BakedOpState(
+                snapshot, recordedOp);
+        if (bakedOp->computedState.clippedBounds.isEmpty()) {
+            // bounds are empty, so op is rejected
+            allocator.rewindIfLastAlloc(bakedOp);
+            return nullptr;
+        }
+        return bakedOp;
+    }
+
+    static void* operator new(size_t size, LinearAllocator& allocator) {
+        return allocator.alloc(size);
+    }
+
+    // computed state:
+    const ResolvedRenderState computedState;
+
+    // simple state (straight pointer/value storage):
+    const float alpha;
+    const RoundRectClipState* roundRectClipState;
+    const ProjectionPathMask* projectionPathMask;
+    const RecordedOp* op;
+
+private:
+    BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp)
+            : computedState(snapshot, recordedOp)
+            , alpha(snapshot.alpha)
+            , roundRectClipState(snapshot.roundRectClipState)
+            , projectionPathMask(snapshot.projectionPathMask)
+            , op(&recordedOp) {}
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_STATE_H
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index c128ca7..6a6cc42 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -28,10 +28,21 @@
         , mWidth(-1)
         , mHeight(-1)
         , mSaveCount(1)
-        , mFirstSnapshot(new Snapshot)
         , mCanvas(renderer)
-        , mSnapshot(mFirstSnapshot) {
+        , mSnapshot(&mFirstSnapshot) {
+}
 
+CanvasState::~CanvasState() {
+    // First call freeSnapshot on all but mFirstSnapshot
+    // to invoke all the dtors
+    freeAllSnapshots();
+
+    // Now actually release the memory
+    while (mSnapshotPool) {
+        void* temp = mSnapshotPool;
+        mSnapshotPool = mSnapshotPool->previous;
+        free(temp);
+    }
 }
 
 void CanvasState::initializeSaveStack(
@@ -41,11 +52,12 @@
     if (mWidth != viewportWidth || mHeight != viewportHeight) {
         mWidth = viewportWidth;
         mHeight = viewportHeight;
-        mFirstSnapshot->initializeViewport(viewportWidth, viewportHeight);
+        mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
         mCanvas.onViewportInitialized();
     }
 
-    mSnapshot = new Snapshot(mFirstSnapshot,
+    freeAllSnapshots();
+    mSnapshot = allocSnapshot(&mFirstSnapshot,
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
     mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
     mSnapshot->fbo = mCanvas.getTargetFbo();
@@ -53,6 +65,38 @@
     mSaveCount = 1;
 }
 
+Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) {
+    void* memory;
+    if (mSnapshotPool) {
+        memory = mSnapshotPool;
+        mSnapshotPool = mSnapshotPool->previous;
+        mSnapshotPoolCount--;
+    } else {
+        memory = malloc(sizeof(Snapshot));
+    }
+    return new (memory) Snapshot(previous, savecount);
+}
+
+void CanvasState::freeSnapshot(Snapshot* snapshot) {
+    snapshot->~Snapshot();
+    // Arbitrary number, just don't let this grown unbounded
+    if (mSnapshotPoolCount > 10) {
+        free((void*) snapshot);
+    } else {
+        snapshot->previous = mSnapshotPool;
+        mSnapshotPool = snapshot;
+        mSnapshotPoolCount++;
+    }
+}
+
+void CanvasState::freeAllSnapshots() {
+    while (mSnapshot != &mFirstSnapshot) {
+        Snapshot* temp = mSnapshot;
+        mSnapshot = mSnapshot->previous;
+        freeSnapshot(temp);
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Save (layer)
 ///////////////////////////////////////////////////////////////////////////////
@@ -64,7 +108,7 @@
  * stack, and ensures restoreToCount() doesn't call back into subclass overrides.
  */
 int CanvasState::saveSnapshot(int flags) {
-    mSnapshot = new Snapshot(mSnapshot, flags);
+    mSnapshot = allocSnapshot(mSnapshot, flags);
     return mSaveCount++;
 }
 
@@ -76,14 +120,16 @@
  * Guaranteed to restore without side-effects.
  */
 void CanvasState::restoreSnapshot() {
-    sp<Snapshot> toRemove = mSnapshot;
-    sp<Snapshot> toRestore = mSnapshot->previous;
+    Snapshot* toRemove = mSnapshot;
+    Snapshot* toRestore = mSnapshot->previous;
 
     mSaveCount--;
     mSnapshot = toRestore;
 
     // subclass handles restore implementation
     mCanvas.onSnapshotRestored(*toRemove, *toRestore);
+
+    freeSnapshot(toRemove);
 }
 
 void CanvasState::restore() {
@@ -213,7 +259,7 @@
     currentTransform()->mapRect(r);
     r.snapGeometryToPixelBoundaries(snapOut);
 
-    Rect clipRect(currentClipRect());
+    Rect clipRect(currentRenderTargetClip());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
@@ -241,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 f0fb9ba..4709ef4 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -17,12 +17,12 @@
 #ifndef ANDROID_HWUI_CANVAS_STATE_H
 #define ANDROID_HWUI_CANVAS_STATE_H
 
+#include "Snapshot.h"
+
 #include <SkMatrix.h>
 #include <SkPath.h>
 #include <SkRegion.h>
 
-#include "Snapshot.h"
-
 namespace android {
 namespace uirenderer {
 
@@ -74,6 +74,7 @@
 class CanvasState {
 public:
     CanvasState(CanvasStateClient& renderer);
+    ~CanvasState();
 
     /**
      * Initializes the first snapshot, computing the projection matrix,
@@ -146,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(); }
@@ -157,11 +158,15 @@
     int getHeight() const { return mHeight; }
     bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); }
 
-    inline const Snapshot* currentSnapshot() const { return mSnapshot.get(); }
-    inline Snapshot* writableSnapshot() { return mSnapshot.get(); }
-    inline const Snapshot* firstSnapshot() const { return mFirstSnapshot.get(); }
+    inline const Snapshot* currentSnapshot() const { return mSnapshot; }
+    inline Snapshot* writableSnapshot() { return mSnapshot; }
+    inline const Snapshot* firstSnapshot() const { return &mFirstSnapshot; }
 
 private:
+    Snapshot* allocSnapshot(Snapshot* previous, int savecount);
+    void freeSnapshot(Snapshot* snapshot);
+    void freeAllSnapshots();
+
     /// indicates that the clip has been changed since the last time it was consumed
     bool mDirtyClip;
 
@@ -172,13 +177,18 @@
     int mSaveCount;
 
     /// Base state
-    sp<Snapshot> mFirstSnapshot;
+    Snapshot mFirstSnapshot;
 
     /// Host providing callbacks
     CanvasStateClient& mCanvas;
 
     /// Current state
-    sp<Snapshot> mSnapshot;
+    Snapshot* mSnapshot;
+
+    // Pool of allocated snapshots to re-use
+    // NOTE: The dtors have already been invoked!
+    Snapshot* mSnapshotPool = nullptr;
+    int mSnapshotPoolCount = 0;
 
 }; // class CanvasState
 
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 8e7efb4..a9d1e42 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -23,14 +23,6 @@
 namespace android {
 namespace uirenderer {
 
-static bool intersect(Rect& r, const Rect& r2) {
-    bool hasIntersection = r.intersect(r2);
-    if (!hasIntersection) {
-        r.setEmpty();
-    }
-    return hasIntersection;
-}
-
 static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
     Vertex v = {x, y};
     transform.mapPoint(v.x, v.y);
@@ -67,9 +59,8 @@
     return mTransform == other.mTransform;
 }
 
-bool TransformedRectangle::intersectWith(const TransformedRectangle& other) {
-    Rect translatedBounds(other.mBounds);
-    return intersect(mBounds, translatedBounds);
+void TransformedRectangle::intersectWith(const TransformedRectangle& other) {
+    mBounds.doIntersect(other.mBounds);
 }
 
 bool TransformedRectangle::isEmpty() const {
@@ -146,7 +137,7 @@
         if (index == 0) {
             bounds = tr.transformedBounds();
         } else {
-            bounds.intersect(tr.transformedBounds());
+            bounds.doIntersect(tr.transformedBounds());
         }
     }
     return bounds;
@@ -275,10 +266,7 @@
     if (transform->rectToRect()) {
         Rect transformed(r);
         transform->mapRect(transformed);
-        bool hasIntersection = mClipRect.intersect(transformed);
-        if (!hasIntersection) {
-            mClipRect.setEmpty();
-        }
+        mClipRect.doIntersect(transformed);
         return;
     }
 
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
index 38fefe5..f88fd92 100644
--- a/libs/hwui/ClipArea.h
+++ b/libs/hwui/ClipArea.h
@@ -33,7 +33,7 @@
     TransformedRectangle(const Rect& bounds, const Matrix4& transform);
 
     bool canSimplyIntersectWith(const TransformedRectangle& other) const;
-    bool intersectWith(const TransformedRectangle& other);
+    void intersectWith(const TransformedRectangle& other);
 
     bool isEmpty() const;
 
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 0c29a9e..a1825c5 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -528,7 +528,7 @@
     int insertBatchIndex = mBatches.size();
     if (!mBatches.empty()) {
         if (state->mBounds.isEmpty()) {
-            // don't know the bounds for op, so add to last batch and start from scratch on next op
+            // don't know the bounds for op, so create new batch and start from scratch on next op
             DrawBatch* b = new DrawBatch(deferInfo);
             b->add(op, state, deferInfo.opaqueOverBounds);
             mBatches.push_back(b);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 7873fbd..2d5979f 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -49,7 +49,7 @@
 
 class DeferredDisplayState {
 public:
-    /** static void* operator new(size_t size); PURPOSELY OMITTED **/
+    static void* operator new(size_t size) = delete;
     static void* operator new(size_t size, LinearAllocator& allocator) {
         return allocator.alloc(size);
     }
@@ -61,7 +61,6 @@
     bool mClipValid;
     Rect mClip;
     int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared
-    bool mClipped;
     mat4 mMatrix;
     float mAlpha;
     const RoundRectClipState* mRoundRectClipState;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 0af9420..59f0d7c 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -21,30 +21,49 @@
 
 #include "Debug.h"
 #include "DisplayList.h"
+#include "RenderNode.h"
+
+#if HWUI_NEW_OPS
+#include "RecordedOp.h"
+#else
 #include "DisplayListOp.h"
+#endif
 
 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() {
-    ResourceCache& resourceCache = ResourceCache::getInstance();
-    resourceCache.lock();
+void DisplayList::cleanupResources() {
+    if (CC_UNLIKELY(patchResources.size())) {
+        ResourceCache& resourceCache = ResourceCache::getInstance();
+        resourceCache.lock();
 
-    for (size_t i = 0; i < patchResources.size(); i++) {
-        resourceCache.decrementRefcountLocked(patchResources[i]);
+        for (size_t i = 0; i < patchResources.size(); i++) {
+            resourceCache.decrementRefcountLocked(patchResources[i]);
+        }
+
+        resourceCache.unlock();
     }
 
-    resourceCache.unlock();
-
     for (size_t i = 0; i < pathResources.size(); i++) {
         const SkPath* path = pathResources[i];
         if (path->unique() && Caches::hasInstance()) {
@@ -59,10 +78,10 @@
     regions.clear();
 }
 
-size_t DisplayListData::addChild(DrawRenderNodeOp* op) {
-    mReferenceHolders.push_back(op->renderNode());
-    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 0bdb816..86796c5 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -55,12 +55,19 @@
 class Rect;
 class Layer;
 
-class ClipRectOp;
-class SaveLayerOp;
-class SaveOp;
-class RestoreToCountOp;
+#if HWUI_NEW_OPS
+struct RecordedOp;
+struct RenderNodeOp;
+
+typedef RecordedOp BaseOpType;
+typedef RenderNodeOp NodeOpType;
+#else
 class DrawRenderNodeOp;
 
+typedef DisplayListOp BaseOpType;
+typedef DrawRenderNodeOp NodeOpType;
+#endif
+
 /**
  * Holds data used in the playback a tree of DisplayLists.
  */
@@ -105,15 +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;
 
@@ -121,32 +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;
-    }
+    const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
+    const LsaVector<Functor*>& getFunctors() const { return functors; }
 
-    size_t addChild(DrawRenderNodeOp* childOp);
-    const std::vector<DrawRenderNodeOp*>& children() { return mChildren; }
+    size_t addChild(NodeOpType* childOp);
+
 
     void ref(VirtualLightRefBase* prop) {
-        mReferenceHolders.push_back(prop);
+        referenceHolders.push_back(prop);
     }
 
     size_t getUsedSize() {
@@ -157,15 +158,27 @@
     }
 
 private:
-    std::vector< sp<VirtualLightRefBase> > mReferenceHolders;
-
-    // list of children display lists for quick, non-drawing traversal
-    std::vector<DrawRenderNodeOp*> mChildren;
-
-    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 77bde86..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,24 @@
         op->setQuickRejected(rejected);
     }
 
-    mDisplayListData->hasDrawOps = true;
+    mDisplayList->hasDrawOps = true;
     return flushAndAddOp(op);
 }
 
 size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
     int opIndex = addDrawOp(op);
-    int childIndex = mDisplayListData->addChild(op);
+#if !HWUI_NEW_OPS
+    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()) {
+    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 ddfc533..cb638a4 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1396,26 +1396,27 @@
 
 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)
             : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
-            , mRenderNode(renderNode)
+            , renderNode(renderNode)
             , mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
             , mTransformFromParent(transformFromParent)
             , mSkipInOrderDraw(false) {}
 
     virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
             bool useQuickReject) override {
-        if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
-            mRenderNode->defer(deferStruct, level + 1);
+        if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+            renderNode->defer(deferStruct, level + 1);
         }
     }
 
     virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
             bool useQuickReject) override {
-        if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
-            mRenderNode->replay(replayStruct, level + 1);
+        if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+            renderNode->replay(replayStruct, level + 1);
         }
     }
 
@@ -1424,18 +1425,16 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const override {
-        OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName());
-        if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
-            mRenderNode->output(level + 1);
+        OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName());
+        if (renderNode && (logFlags & kOpLogFlag_Recurse)) {
+            renderNode->output(level + 1);
         }
     }
 
     virtual const char* name() override { return "DrawRenderNode"; }
 
-    RenderNode* renderNode() { return mRenderNode; }
-
 private:
-    RenderNode* mRenderNode;
+    RenderNode* renderNode;
 
     /**
      * This RenderNode was drawn into a DisplayList with the canvas in a state that will likely
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index fa166ae..d2da851 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -461,12 +461,12 @@
 // Transform
 ////////////////////////////////////////////////////////////////////////////////
 
-void GlopBuilder::setTransform(const Matrix4& canvas,
-        const int transformFlags) {
+GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) {
     TRIGGER_STAGE(kTransformStage);
 
     mOutGlop->transform.canvas = canvas;
     mOutGlop->transform.transformFlags = transformFlags;
+    return *this;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -632,5 +632,42 @@
     mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds);
 }
 
+void GlopBuilder::dump(const Glop& glop) {
+    ALOGD("Glop Mesh");
+    const Glop::Mesh& mesh = glop.mesh;
+    ALOGD("    primitive mode: %d", mesh.primitiveMode);
+    ALOGD("    indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices);
+
+    const Glop::Mesh::Vertices& vertices = glop.mesh.vertices;
+    ALOGD("    vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d",
+            vertices.bufferObject, vertices.attribFlags,
+            vertices.position, vertices.texCoord, vertices.color, vertices.stride);
+    ALOGD("    element count: %d", mesh.elementCount);
+
+    ALOGD("Glop Fill");
+    const Glop::Fill& fill = glop.fill;
+    ALOGD("    program %p", fill.program);
+    if (fill.texture.texture) {
+        ALOGD("    texture %p, target %d, filter %d, clamp %d",
+                fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp);
+        if (fill.texture.textureTransform) {
+            fill.texture.textureTransform->dump("texture transform");
+        }
+    }
+    ALOGD_IF(fill.colorEnabled, "    color (argb) %.2f %.2f %.2f %.2f",
+            fill.color.a, fill.color.r, fill.color.g, fill.color.b);
+    ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None,
+            "    filterMode %d", (int)fill.filterMode);
+    ALOGD_IF(fill.skiaShaderData.skiaShaderType, "    shader type %d",
+            fill.skiaShaderData.skiaShaderType);
+
+    ALOGD("Glop transform");
+    glop.transform.modelView.dump("model view");
+    glop.transform.canvas.dump("canvas");
+
+    ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst);
+    ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds));
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 8d05570..6f5802e 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -71,9 +71,9 @@
     GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
 
     GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) {
-        setTransform(*snapshot.transform, transformFlags);
-        return *this;
+        return setTransform(*snapshot.transform, transformFlags);
     }
+    GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
 
     GlopBuilder& setModelViewMapUnitToRect(const Rect destination);
     GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination);
@@ -98,11 +98,12 @@
     GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
 
     void build();
+
+    static void dump(const Glop& glop);
 private:
     void setFill(int color, float alphaScale,
             SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
             const SkShader* shader, const SkColorFilter* colorFilter);
-    void setTransform(const Matrix4& canvas, const int transformFlags);
 
     enum StageFlags {
         kInitialStage = 0,
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index f99d92b..489ebc1 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -155,8 +155,7 @@
 
     if (fbo) {
         if (flush) LayerRenderer::flushLayer(renderState, this);
-        // If put fails the cache will delete the FBO
-        caches.fboCache.put(fbo);
+        renderState.deleteFramebuffer(fbo);
         fbo = 0;
     }
 }
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index c63b559..e9e5d81 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -58,7 +58,7 @@
         mLayer->region.clear();
         dirty.set(0.0f, 0.0f, width, height);
     } else {
-        dirty.intersect(0.0f, 0.0f, width, height);
+        dirty.doIntersect(0.0f, 0.0f, width, height);
         android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom);
         mLayer->region.subtractSelf(r);
     }
@@ -189,7 +189,7 @@
     LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
 
     Caches& caches = Caches::getInstance();
-    GLuint fbo = caches.fboCache.get();
+    GLuint fbo = renderState.genFramebuffer();
     if (!fbo) {
         ALOGW("Could not obtain an FBO");
         return nullptr;
@@ -204,7 +204,7 @@
 
     // We first obtain a layer before comparing against the max texture size
     // because layers are not allocated at the exact desired size. They are
-    // always created slighly larger to improve recycling
+    // always created slightly larger to improve recycling
     const uint32_t maxTextureSize = caches.maxTextureSize;
     if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) {
         ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -357,7 +357,7 @@
             && bitmap->width() <= caches.maxTextureSize
             && bitmap->height() <= caches.maxTextureSize) {
 
-        GLuint fbo = caches.fboCache.get();
+        GLuint fbo = renderState.getFramebuffer();
         if (!fbo) {
             ALOGW("Could not obtain an FBO");
             return false;
@@ -465,7 +465,7 @@
         layer->setAlpha(alpha, mode);
         layer->setFbo(previousLayerFbo);
         caches.textureState().deleteTexture(texture);
-        caches.fboCache.put(fbo);
+        renderState.deleteFramebuffer(fbo);
         renderState.setViewport(previousViewportWidth, previousViewportHeight);
 
         return status;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index ed517ac..c017638 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -126,6 +126,9 @@
     void loadMultiply(const Matrix4& u, const Matrix4& v);
 
     void loadOrtho(float left, float right, float bottom, float top, float near, float far);
+    void loadOrtho(int width, int height) {
+        loadOrtho(0, width, height, 0, -1, 1);
+    }
 
     uint8_t getType() const;
 
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
new file mode 100644
index 0000000..cde42f8
--- /dev/null
+++ b/libs/hwui/OpReorderer.cpp
@@ -0,0 +1,456 @@
+/*
+ * 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 "OpReorderer.h"
+
+#include "utils/PaintUtils.h"
+#include "RenderNode.h"
+
+#include "SkCanvas.h"
+#include "utils/Trace.h"
+
+namespace android {
+namespace uirenderer {
+
+class BatchBase {
+
+public:
+    BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
+        : mBatchId(batchId)
+        , mMerging(merging) {
+        mBounds = op->computedState.clippedBounds;
+        mOps.push_back(op);
+    }
+
+    bool intersects(const Rect& rect) const {
+        if (!rect.intersects(mBounds)) return false;
+
+        for (const BakedOpState* op : mOps) {
+            if (rect.intersects(op->computedState.clippedBounds)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    batchid_t getBatchId() const { return mBatchId; }
+    bool isMerging() const { return mMerging; }
+
+    const std::vector<BakedOpState*>& getOps() const { return mOps; }
+
+    void dump() const {
+        ALOGD("    Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+                this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
+    }
+protected:
+    batchid_t mBatchId;
+    Rect mBounds;
+    std::vector<BakedOpState*> mOps;
+    bool mMerging;
+};
+
+class OpBatch : public BatchBase {
+public:
+    static void* operator new(size_t size, LinearAllocator& allocator) {
+        return allocator.alloc(size);
+    }
+
+    OpBatch(batchid_t batchId, BakedOpState* op)
+            : BatchBase(batchId, op, false) {
+    }
+
+    void batchOp(BakedOpState* op) {
+        mBounds.unionWith(op->computedState.clippedBounds);
+        mOps.push_back(op);
+    }
+};
+
+class MergingOpBatch : public BatchBase {
+public:
+    static void* operator new(size_t size, LinearAllocator& allocator) {
+        return allocator.alloc(size);
+    }
+
+    MergingOpBatch(batchid_t batchId, BakedOpState* op)
+            : BatchBase(batchId, op, true) {
+    }
+
+    /*
+     * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
+     * and clip side flags. Positive bounds delta means new bounds fit in old.
+     */
+    static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
+            float boundsDelta) {
+        bool currentClipExists = currentFlags & side;
+        bool newClipExists = newFlags & side;
+
+        // if current is clipped, we must be able to fit new bounds in current
+        if (boundsDelta > 0 && currentClipExists) return false;
+
+        // if new is clipped, we must be able to fit current bounds in new
+        if (boundsDelta < 0 && newClipExists) return false;
+
+        return true;
+    }
+
+    static bool paintIsDefault(const SkPaint& paint) {
+        return paint.getAlpha() == 255
+                && paint.getColorFilter() == nullptr
+                && paint.getShader() == nullptr;
+    }
+
+    static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
+        return a.getAlpha() == b.getAlpha()
+                && a.getColorFilter() == b.getColorFilter()
+                && a.getShader() == b.getShader();
+    }
+
+    /*
+     * Checks if a (mergeable) op can be merged into this batch
+     *
+     * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
+     * important to consider all paint attributes used in the draw calls in deciding both a) if an
+     * op tries to merge at all, and b) if the op can merge with another set of ops
+     *
+     * False positives can lead to information from the paints of subsequent merged operations being
+     * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
+     */
+    bool canMergeWith(BakedOpState* op) const {
+        bool isTextBatch = getBatchId() == OpBatchType::Text
+                || getBatchId() == OpBatchType::ColorText;
+
+        // Overlapping other operations is only allowed for text without shadow. For other ops,
+        // multiDraw isn't guaranteed to overdraw correctly
+        if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
+            if (intersects(op->computedState.clippedBounds)) return false;
+        }
+
+        const BakedOpState* lhs = op;
+        const BakedOpState* rhs = mOps[0];
+
+        if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
+
+        // Identical round rect clip state means both ops will clip in the same way, or not at all.
+        // As the state objects are const, we can compare their pointers to determine mergeability
+        if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
+        if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
+
+        /* Clipping compatibility check
+         *
+         * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
+         * clip for that side.
+         */
+        const int currentFlags = mClipSideFlags;
+        const int newFlags = op->computedState.clipSideFlags;
+        if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
+            const Rect& opBounds = op->computedState.clippedBounds;
+            float boundsDelta = mBounds.left - opBounds.left;
+            if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
+            boundsDelta = mBounds.top - opBounds.top;
+            if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
+
+            // right and bottom delta calculation reversed to account for direction
+            boundsDelta = opBounds.right - mBounds.right;
+            if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
+            boundsDelta = opBounds.bottom - mBounds.bottom;
+            if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
+        }
+
+        const SkPaint* newPaint = op->op->paint;
+        const SkPaint* oldPaint = mOps[0]->op->paint;
+
+        if (newPaint == oldPaint) {
+            // if paints are equal, then modifiers + paint attribs don't need to be compared
+            return true;
+        } else if (newPaint && !oldPaint) {
+            return paintIsDefault(*newPaint);
+        } else if (!newPaint && oldPaint) {
+            return paintIsDefault(*oldPaint);
+        }
+        return paintsAreEquivalent(*newPaint, *oldPaint);
+    }
+
+    void mergeOp(BakedOpState* op) {
+        mBounds.unionWith(op->computedState.clippedBounds);
+        mOps.push_back(op);
+
+        const int newClipSideFlags = op->computedState.clipSideFlags;
+        mClipSideFlags |= newClipSideFlags;
+
+        const Rect& opClip = op->computedState.clipRect;
+        if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
+        if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
+        if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
+        if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
+    }
+
+private:
+    int mClipSideFlags = 0;
+    Rect mClipRect;
+};
+
+// 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 OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
+        BatchBase** targetBatch, size_t* insertBatchIndex) const {
+    for (int i = mBatches.size() - 1; i >= 0; i--) {
+        BatchBase* overBatch = mBatches[i];
+
+        if (overBatch == *targetBatch) break;
+
+        // TODO: also consider shader shared between batch types
+        if (batchId == overBatch->getBatchId()) {
+            *insertBatchIndex = i + 1;
+            if (!*targetBatch) break; // found insert position, quit
+        }
+
+        if (overBatch->intersects(clippedBounds)) {
+            // NOTE: it may be possible to optimize for special cases where two operations
+            // of the same batch/paint could swap order, such as with a non-mergeable
+            // (clipped) and a mergeable text operation
+            *targetBatch = nullptr;
+            break;
+        }
+    }
+}
+
+void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
+        BakedOpState* op, batchid_t batchId) {
+    OpBatch* targetBatch = mBatchLookup[batchId];
+
+    size_t insertBatchIndex = mBatches.size();
+    if (targetBatch) {
+        locateInsertIndex(batchId, op->computedState.clippedBounds,
+                (BatchBase**)(&targetBatch), &insertBatchIndex);
+    }
+
+    if (targetBatch) {
+        targetBatch->batchOp(op);
+    } else  {
+        // new non-merging batch
+        targetBatch = new (allocator) OpBatch(batchId, op);
+        mBatchLookup[batchId] = targetBatch;
+        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+    }
+}
+
+// insertion point of a new batch, will hopefully be immediately after similar batch
+// (generally, should be similar shader)
+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 = mMergingBatchLookup[batchId].find(mergeId);
+    if (getResult != mMergingBatchLookup[batchId].end()) {
+        targetBatch = getResult->second;
+        if (!targetBatch->canMergeWith(op)) {
+            targetBatch = nullptr;
+        }
+    }
+
+    size_t insertBatchIndex = mBatches.size();
+    locateInsertIndex(batchId, op->computedState.clippedBounds,
+            (BatchBase**)(&targetBatch), &insertBatchIndex);
+
+    if (targetBatch) {
+        targetBatch->mergeOp(op);
+    } else  {
+        // new merging batch
+        targetBatch = new (allocator) MergingOpBatch(batchId, op);
+        mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
+
+        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+    }
+}
+
+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(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+        const std::vector< sp<RenderNode> >& nodes)
+        : mCanvasState(*this) {
+    ATRACE_NAME("prepare drawing commands");
+
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    mLayerStack.push_back(0);
+
+    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();
+    }
+}
+
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+        : mCanvasState(*this) {
+    ATRACE_NAME("prepare drawing commands");
+
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    mLayerStack.push_back(0);
+
+    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
+            0, 0, viewportWidth, viewportHeight, Vector3());
+    deferImpl(displayList);
+}
+
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
+/**
+ * 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");
+}
+
+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) {
+    const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+    const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
+    mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+    mCanvasState.writableSnapshot()->transform->loadIdentity();
+    mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
+    mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
+
+    // create a new layer, and push its index on the stack
+    mLayerStack.push_back(mLayerReorderers.size());
+    mLayerReorderers.emplace_back(layerWidth, layerHeight);
+    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,
+            &mLayerReorderers[finishedLayerIndex].layer);
+    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
new file mode 100644
index 0000000..f32b858
--- /dev/null
+++ b/libs/hwui/OpReorderer.h
@@ -0,0 +1,219 @@
+/*
+ * 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 ANDROID_HWUI_OP_REORDERER_H
+#define ANDROID_HWUI_OP_REORDERER_H
+
+#include "BakedOpState.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RecordedOp.h"
+
+#include <vector>
+#include <unordered_map>
+
+struct SkRect;
+
+namespace android {
+namespace uirenderer {
+
+class BakedOpState;
+class BatchBase;
+class MergingOpBatch;
+class OpBatch;
+class Rect;
+
+typedef int batchid_t;
+typedef const void* mergeid_t;
+
+namespace OpBatchType {
+    enum {
+        None = 0, // Don't batch
+        Bitmap,
+        Patch,
+        AlphaVertices,
+        Vertices,
+        AlphaMaskTexture,
+        Text,
+        ColorText,
+
+        Count // must be last
+    };
+}
+
+class OpReorderer : public CanvasStateClient {
+    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
+
+    /**
+     * Stores the deferred render operations and state used to compute ordering
+     * for a single FBO/layer.
+     */
+    class LayerReorderer {
+    public:
+        LayerReorderer(uint32_t width, uint32_t height)
+                : width(width)
+                , height(height) {}
+
+        // 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;
+
+        bool empty() const {
+            return mBatches.empty();
+        }
+
+        void clear() {
+            mBatches.clear();
+        }
+
+        void dump() const;
+
+        Layer* layer = nullptr;
+        const BeginLayerOp* beginLayerOp = nullptr;
+        const uint32_t width;
+        const uint32_t height;
+    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:
+    // TODO: not final, just presented this way for simplicity. Layers too?
+    OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+            const std::vector< sp<RenderNode> >& nodes);
+
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+
+    virtual ~OpReorderer() {}
+
+    /**
+     * 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.
+     *
+     * For example a BitmapOp would resolve, via the lambda lookup, to calling:
+     *
+     * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
+     */
+#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); \
+    },
+    template <typename StaticReceiver, typename Arg>
+    void replayBakedOps(Arg& arg) {
+        static BakedOpReceiver receivers[] = {
+            MAP_OPS(BAKED_OP_RECEIVER)
+        };
+
+        // 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 >= 1; i--) {
+            LayerReorderer& layer = mLayerReorderers[i];
+            if (!layer.empty()) {
+                layer.layer = StaticReceiver::startLayer(arg, layer.width, layer.height);
+                layer.replayBakedOpsImpl((void*)&arg, receivers);
+                StaticReceiver::endLayer(arg);
+            }
+        }
+
+        const LayerReorderer& fbo0 = mLayerReorderers[0];
+        StaticReceiver::startFrame(arg, fbo0.width, fbo0.height);
+        fbo0.replayBakedOpsImpl((void*)&arg, receivers);
+        StaticReceiver::endFrame(arg);
+    }
+
+    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);
+
+    /**
+     * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
+     *
+     * These private methods are called from within deferImpl to defer each individual op
+     * type differently.
+     */
+#define INTERNAL_OP_HANDLER(Type) \
+    void on##Type(const Type& op);
+    MAP_OPS(INTERNAL_OP_HANDLER)
+
+    // List of every deferred layer's render state. Replayed in reverse order to render a frame.
+    std::vector<LayerReorderer> mLayerReorderers;
+
+    /*
+     * 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;
+
+    CanvasState mCanvasState;
+
+    // contains ResolvedOps and Batches
+    LinearAllocator mAllocator;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OP_REORDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 9f24e37..8c3603b 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,8 @@
     currentTransform()->mapRect(bounds);
 
     // Layers only make sense if they are in the framebuffer's bounds
-    if (bounds.intersect(mState.currentClipRect())) {
+    bounds.doIntersect(mState.currentRenderTargetClip());
+    if (!bounds.isEmpty()) {
         // We cannot work with sub-pixels in this case
         bounds.snapToPixelBoundaries();
 
@@ -497,23 +498,20 @@
         // of the framebuffer
         const Snapshot& previous = *(currentSnapshot()->previous);
         Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
-        if (!bounds.intersect(previousViewport)) {
-            bounds.setEmpty();
-        } else if (fboLayer) {
+
+        bounds.doIntersect(previousViewport);
+        if (!bounds.isEmpty() && fboLayer) {
             clip.set(bounds);
             mat4 inverse;
             inverse.loadInverse(*currentTransform());
             inverse.mapRect(clip);
             clip.snapToPixelBoundaries();
-            if (clip.intersect(untransformedBounds)) {
+            clip.doIntersect(untransformedBounds);
+            if (!clip.isEmpty()) {
                 clip.translate(-untransformedBounds.left, -untransformedBounds.top);
                 bounds.set(untransformedBounds);
-            } else {
-                clip.setEmpty();
             }
         }
-    } else {
-        bounds.setEmpty();
     }
 }
 
@@ -675,7 +673,7 @@
 
 bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
     layer->clipRect.set(clip);
-    layer->setFbo(mCaches.fboCache.get());
+    layer->setFbo(mRenderState.genFramebuffer());
 
     writableSnapshot()->region = &writableSnapshot()->layer->region;
     writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
@@ -1038,7 +1036,8 @@
 }
 
 void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
-    if (CC_LIKELY(!bounds.isEmpty() && bounds.intersect(mState.currentClipRect()))) {
+    bounds.doIntersect(mState.currentRenderTargetClip());
+    if (!bounds.isEmpty()) {
         bounds.snapToPixelBoundaries();
         android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
         if (!dirty.isEmpty()) {
@@ -1085,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);
 
@@ -1100,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) {
@@ -1112,7 +1111,8 @@
             // is used, it should more closely duplicate the quickReject logic (in how it uses
             // snapToPixelBoundaries)
 
-            if (!clippedBounds.intersect(currentClip)) {
+            clippedBounds.doIntersect(currentClip);
+            if (clippedBounds.isEmpty()) {
                 // quick rejected
                 return true;
             }
@@ -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,
@@ -1242,9 +1242,8 @@
         Rect bounds = tr.getBounds();
         if (transform.rectToRect()) {
             transform.mapRect(bounds);
-            if (!bounds.intersect(scissorBox)) {
-                bounds.setEmpty();
-            } else {
+            bounds.doIntersect(scissorBox);
+            if (!bounds.isEmpty()) {
                 handlePointNoTransform(rectangleVertices, bounds.left, bounds.top);
                 handlePointNoTransform(rectangleVertices, bounds.right, bounds.top);
                 handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom);
@@ -1431,7 +1430,7 @@
             return;
         }
 
-        DeferredDisplayList deferredList(mState.currentClipRect());
+        DeferredDisplayList deferredList(mState.currentRenderTargetClip());
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
         renderNode->defer(deferStruct, 0);
 
@@ -1766,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;
@@ -2031,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);
@@ -2192,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
new file mode 100644
index 0000000..6c31b42
--- /dev/null
+++ b/libs/hwui/RecordedOp.h
@@ -0,0 +1,151 @@
+/*
+ * 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 ANDROID_HWUI_RECORDED_OP_H
+#define ANDROID_HWUI_RECORDED_OP_H
+
+#include "utils/LinearAllocator.h"
+#include "Rect.h"
+#include "Matrix.h"
+
+#include "SkXfermode.h"
+
+class SkBitmap;
+class SkPaint;
+
+namespace android {
+namespace uirenderer {
+
+class Layer;
+class RenderNode;
+struct Vertex;
+
+/**
+ * The provided macro is executed for each op type in order, with the results separated by commas.
+ *
+ * This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs.
+ */
+#define MAP_OPS(OP_FN) \
+        OP_FN(BitmapOp) \
+        OP_FN(RectOp) \
+        OP_FN(RenderNodeOp) \
+        OP_FN(SimpleRectsOp) \
+        OP_FN(BeginLayerOp) \
+        OP_FN(EndLayerOp) \
+        OP_FN(LayerOp)
+
+// Generate OpId enum
+#define IDENTITY_FN(Type) Type,
+namespace RecordedOpId {
+    enum {
+        MAP_OPS(IDENTITY_FN)
+        Count,
+    };
+}
+static_assert(RecordedOpId::BitmapOp == 0,
+        "First index must be zero for LUTs to work");
+
+#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint
+#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect
+#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint)
+#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr)
+
+struct RecordedOp {
+    /* ID from RecordedOpId - generally used for jumping into function tables */
+    const int opId;
+
+    /* bounds in *local* space, without accounting for DisplayList transformation */
+    const Rect unmappedBounds;
+
+    /* transform in recording space (vs DisplayList origin) */
+    const Matrix4 localMatrix;
+
+    /* clip in recording space */
+    const Rect localClipRect;
+
+    /* optional paint, stored in base object to simplify merging logic */
+    const SkPaint* paint;
+protected:
+    RecordedOp(unsigned int opId, BASE_PARAMS)
+            : opId(opId)
+            , unmappedBounds(unmappedBounds)
+            , localMatrix(localMatrix)
+            , localClipRect(localClipRect)
+            , paint(paint) {}
+};
+
+struct RenderNodeOp : RecordedOp {
+    RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
+            : SUPER_PAINTLESS(RenderNodeOp)
+            , renderNode(renderNode) {}
+    RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
+};
+
+struct BitmapOp : RecordedOp {
+    BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
+            : SUPER(BitmapOp)
+            , bitmap(bitmap) {}
+    const SkBitmap* bitmap;
+    // TODO: asset atlas/texture id lookup?
+};
+
+struct RectOp : RecordedOp {
+    RectOp(BASE_PARAMS)
+            : SUPER(RectOp) {}
+};
+
+struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
+    SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
+            : SUPER(SimpleRectsOp)
+            , vertices(vertices)
+            , vertexCount(vertexCount) {}
+    Vertex* vertices;
+    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, Layer** layerHandle)
+            : SUPER(LayerOp)
+            , layerHandle(layerHandle) {}
+    // Records a handle to the Layer object, since the Layer itself won't be
+    // constructed until after this operation is constructed.
+    Layer** layerHandle;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RECORDED_OP_H
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
new file mode 100644
index 0000000..1f113bc
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -0,0 +1,474 @@
+/*
+ * 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 "RecordingCanvas.h"
+
+#include "RecordedOp.h"
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+RecordingCanvas::RecordingCanvas(size_t width, size_t height)
+        : mState(*this)
+        , mResourceCache(ResourceCache::getInstance()) {
+    reset(width, height);
+}
+
+RecordingCanvas::~RecordingCanvas() {
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
+            "Destroyed a RecordingCanvas during a record!");
+}
+
+void RecordingCanvas::reset(int width, int height) {
+    LOG_ALWAYS_FATAL_IF(mDisplayList,
+            "prepareDirty called a second time during a recording!");
+    mDisplayList = new DisplayList();
+
+    mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
+
+    mDeferredBarrierType = kBarrier_InOrder;
+    mState.setDirtyClip(false);
+    mRestoreSaveCount = -1;
+}
+
+DisplayList* RecordingCanvas::finishRecording() {
+    mPaintMap.clear();
+    mRegionMap.clear();
+    mPathMap.clear();
+    DisplayList* displayList = mDisplayList;
+    mDisplayList = nullptr;
+    mSkiaCanvasProxy.reset(nullptr);
+    return displayList;
+}
+
+SkCanvas* RecordingCanvas::asSkCanvas() {
+    LOG_ALWAYS_FATAL_IF(!mDisplayList,
+            "attempting to get an SkCanvas when we are not recording!");
+    if (!mSkiaCanvasProxy) {
+        mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
+    }
+
+    // SkCanvas instances default to identity transform, but should inherit
+    // the state of this Canvas; if this code was in the SkiaCanvasProxy
+    // constructor, we couldn't cache mSkiaCanvasProxy.
+    SkMatrix parentTransform;
+    getMatrix(&parentTransform);
+    mSkiaCanvasProxy.get()->setMatrix(parentTransform);
+
+    return mSkiaCanvasProxy.get();
+}
+
+// ----------------------------------------------------------------------------
+// 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)
+int RecordingCanvas::save(SkCanvas::SaveFlags flags) {
+    return mState.save((int) flags);
+}
+
+void RecordingCanvas::RecordingCanvas::restore() {
+    if (mRestoreSaveCount < 0) {
+        restoreToCount(getSaveCount() - 1);
+        return;
+    }
+
+    mRestoreSaveCount--;
+    mState.restore();
+}
+
+void RecordingCanvas::restoreToCount(int saveCount) {
+    mRestoreSaveCount = saveCount;
+    mState.restoreToCount(saveCount);
+}
+
+int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
+        SkCanvas::SaveFlags flags) {
+    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
+void RecordingCanvas::rotate(float degrees) {
+    if (degrees == 0) return;
+
+    mState.rotate(degrees);
+}
+
+void RecordingCanvas::scale(float sx, float sy) {
+    if (sx == 1 && sy == 1) return;
+
+    mState.scale(sx, sy);
+}
+
+void RecordingCanvas::skew(float sx, float sy) {
+    mState.skew(sx, sy);
+}
+
+void RecordingCanvas::translate(float dx, float dy) {
+    if (dx == 0 && dy == 0) return;
+
+    mState.translate(dx, dy, 0);
+}
+
+// Clip
+bool RecordingCanvas::getClipBounds(SkRect* outRect) const {
+    Rect bounds = mState.getLocalClipBounds();
+    *outRect = SkRect::MakeLTRB(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    return !(outRect->isEmpty());
+}
+bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
+    return mState.quickRejectConservative(left, top, right, bottom);
+}
+bool RecordingCanvas::quickRejectPath(const SkPath& path) const {
+    SkRect bounds = path.getBounds();
+    return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+}
+bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+    return mState.clipRect(left, top, right, bottom, op);
+}
+bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
+    return mState.clipPath(path, op);
+}
+bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
+    return mState.clipRegion(region, op);
+}
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas draw operations
+// ----------------------------------------------------------------------------
+void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setXfermodeMode(mode);
+    drawPaint(paint);
+}
+
+void RecordingCanvas::drawPaint(const SkPaint& paint) {
+    // TODO: more efficient recording?
+    Matrix4 identity;
+    identity.loadIdentity();
+
+    addOp(new (alloc()) RectOp(
+            mState.getRenderTargetClipBounds(),
+            identity,
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint)));
+}
+
+// Geometry
+void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
+    addOp(new (alloc()) RectOp(
+            Rect(left, top, right, bottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint)));
+}
+
+void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
+    if (rects == nullptr) return;
+
+    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex));
+    Vertex* vertex = rectData;
+
+    float left = FLT_MAX;
+    float top = FLT_MAX;
+    float right = FLT_MIN;
+    float bottom = FLT_MIN;
+    for (int index = 0; index < vertexCount; index += 4) {
+        float l = rects[index + 0];
+        float t = rects[index + 1];
+        float r = rects[index + 2];
+        float b = rects[index + 3];
+
+        Vertex::set(vertex++, l, t);
+        Vertex::set(vertex++, r, t);
+        Vertex::set(vertex++, l, b);
+        Vertex::set(vertex++, r, b);
+
+        left = std::min(left, l);
+        top = std::min(top, t);
+        right = std::max(right, r);
+        bottom = std::max(bottom, b);
+    }
+    addOp(new (alloc()) SimpleRectsOp(
+            Rect(left, top, right, bottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), rectData, vertexCount));
+}
+
+void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+    if (paint.getStyle() == SkPaint::kFill_Style
+            && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
+        int count = 0;
+        Vector<float> rects;
+        SkRegion::Iterator it(region);
+        while (!it.done()) {
+            const SkIRect& r = it.rect();
+            rects.push(r.fLeft);
+            rects.push(r.fTop);
+            rects.push(r.fRight);
+            rects.push(r.fBottom);
+            count += 4;
+            it.next();
+        }
+        drawSimpleRects(rects.array(), count, &paint);
+    } else {
+        SkRegion::Iterator it(region);
+        while (!it.done()) {
+            const SkIRect& r = it.rect();
+            drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
+            it.next();
+        }
+    }
+}
+void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+
+// Bitmap-based
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
+    save(SkCanvas::kMatrix_SaveFlag);
+    translate(left, top);
+    drawBitmap(&bitmap, paint);
+    restore();
+}
+
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+                            const SkPaint* paint) {
+    if (matrix.isIdentity()) {
+        drawBitmap(&bitmap, paint);
+    } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
+            && MathUtils::isPositive(matrix.getScaleX())
+            && MathUtils::isPositive(matrix.getScaleY())) {
+        // SkMatrix::isScaleTranslate() not available in L
+        SkRect src;
+        SkRect dst;
+        bitmap.getBounds(&src);
+        matrix.mapRect(&dst, src);
+        drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
+                   dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
+    } else {
+        save(SkCanvas::kMatrix_SaveFlag);
+        concat(matrix);
+        drawBitmap(&bitmap, paint);
+        restore();
+    }
+}
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop,
+            float dstRight, float dstBottom, const SkPaint* paint) {
+    if (srcLeft == 0 && srcTop == 0
+            && srcRight == bitmap.width()
+            && srcBottom == bitmap.height()
+            && (srcBottom - srcTop == dstBottom - dstTop)
+            && (srcRight - srcLeft == dstRight - dstLeft)) {
+        // transform simple rect to rect drawing case into position bitmap ops, since they merge
+        save(SkCanvas::kMatrix_SaveFlag);
+        translate(dstLeft, dstTop);
+        drawBitmap(&bitmap, paint);
+        restore();
+    } else {
+        LOG_ALWAYS_FATAL("TODO!");
+    }
+}
+void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors, const SkPaint* paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+            float dstLeft, float dstTop, float dstRight, float dstBottom,
+            const SkPaint* paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+
+// Text
+void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
+            const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
+            float boundsRight, float boundsBottom, float totalAdvance) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
+            int posCount, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) {
+    LOG_ALWAYS_FATAL("TODO!");
+}
+
+void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
+    addOp(new (alloc()) BitmapOp(
+            Rect(0, 0, bitmap->width(), bitmap->height()),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), refBitmap(*bitmap)));
+}
+void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
+    RenderNodeOp* op = new (alloc()) RenderNodeOp(
+            Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            renderNode);
+    int opIndex = addOp(op);
+    int childIndex = mDisplayList->addChild(op);
+
+    // update the chunk's child indices
+    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
+    chunk.endChildIndex = childIndex + 1;
+
+    if (renderNode->stagingProperties().isProjectionReceiver()) {
+        // use staging property, since recording on UI thread
+        mDisplayList->projectionReceiveIndex = opIndex;
+    }
+}
+
+size_t RecordingCanvas::addOp(RecordedOp* op) {
+    // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
+    int insertIndex = mDisplayList->ops.size();
+    mDisplayList->ops.push_back(op);
+    if (mDeferredBarrierType != kBarrier_None) {
+        // op is first in new chunk
+        mDisplayList->chunks.emplace_back();
+        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
+        newChunk.beginOpIndex = insertIndex;
+        newChunk.endOpIndex = insertIndex + 1;
+        newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
+
+        int nextChildIndex = mDisplayList->children.size();
+        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
+        mDeferredBarrierType = kBarrier_None;
+    } else {
+        // standard case - append to existing chunk
+        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
+    }
+    return insertIndex;
+}
+
+void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
+    if (!shader) return;
+
+    // If this paint has an SkShader that has an SkBitmap add
+    // it to the bitmap pile
+    SkBitmap bitmap;
+    SkShader::TileMode xy[2];
+    if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+        refBitmap(bitmap);
+        return;
+    }
+    SkShader::ComposeRec rec;
+    if (shader->asACompose(&rec)) {
+        refBitmapsInShader(rec.fShaderA);
+        refBitmapsInShader(rec.fShaderB);
+        return;
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
new file mode 100644
index 0000000..9c32b1a
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.h
@@ -0,0 +1,300 @@
+/*
+ * 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 ANDROID_HWUI_RECORDING_CANVAS_H
+#define ANDROID_HWUI_RECORDING_CANVAS_H
+
+#include "Canvas.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "utils/LinearAllocator.h"
+#include "utils/NinePatch.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "Snapshot.h"
+
+#include "SkDrawFilter.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class OpReceiver;
+struct RecordedOp;
+
+class RecordingCanvas: public Canvas, public CanvasStateClient {
+public:
+    RecordingCanvas(size_t width, size_t height);
+    virtual ~RecordingCanvas();
+
+    void reset(int width, int height);
+    __attribute__((warn_unused_result)) DisplayList* finishRecording();
+
+// ----------------------------------------------------------------------------
+// MISC HWUI OPERATIONS - TODO: CATEGORIZE
+// ----------------------------------------------------------------------------
+    void insertReorderBarrier(bool enableReorder) {}
+    void drawRenderNode(RenderNode* renderNode);
+
+// ----------------------------------------------------------------------------
+// CanvasStateClient interface
+// ----------------------------------------------------------------------------
+    virtual void onViewportInitialized() override;
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+    virtual GLuint getTargetFbo() const override { return -1; }
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas interface
+// ----------------------------------------------------------------------------
+    virtual SkCanvas* asSkCanvas() override;
+
+    virtual void setBitmap(const SkBitmap& bitmap) override {
+        LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap.");
+    }
+
+    virtual bool isOpaque() override { return false; }
+    virtual int width() override { return mState.getWidth(); }
+    virtual int height() override { return mState.getHeight(); }
+
+    virtual void setHighContrastText(bool highContrastText) override {
+        mHighContrastText = highContrastText;
+    }
+    virtual bool isHighContrastText() override { return mHighContrastText; }
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas state operations
+// ----------------------------------------------------------------------------
+    // Save (layer)
+    virtual int getSaveCount() const override { return mState.getSaveCount(); }
+    virtual int save(SkCanvas::SaveFlags flags) override;
+    virtual void restore() override;
+    virtual void restoreToCount(int saveCount) override;
+
+    virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
+        SkCanvas::SaveFlags flags) override;
+    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+            int alpha, SkCanvas::SaveFlags flags) override {
+        SkPaint paint;
+        paint.setAlpha(alpha);
+        return saveLayer(left, top, right, bottom, &paint, flags);
+    }
+
+    // Matrix
+    virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
+    virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); }
+
+    virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); }
+    virtual void rotate(float degrees) override;
+    virtual void scale(float sx, float sy) override;
+    virtual void skew(float sx, float sy) override;
+    virtual void translate(float dx, float dy) override;
+
+    // Clip
+    virtual bool getClipBounds(SkRect* outRect) const override;
+    virtual bool quickRejectRect(float left, float top, float right, float bottom) const override;
+    virtual bool quickRejectPath(const SkPath& path) const override;
+
+    virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override;
+    virtual bool clipPath(const SkPath* path, SkRegion::Op op) override;
+    virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override;
+
+    // Misc
+    virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); }
+    virtual void setDrawFilter(SkDrawFilter* filter) override {
+        mDrawFilter.reset(SkSafeRef(filter));
+    }
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas draw operations
+// ----------------------------------------------------------------------------
+    virtual void drawColor(int color, SkXfermode::Mode mode) override;
+    virtual void drawPaint(const SkPaint& paint) override;
+
+    // Geometry
+    virtual void drawPoint(float x, float y, const SkPaint& paint) override {
+        float points[2] = { x, y };
+        drawPoints(points, 2, paint);
+    }
+    virtual void drawPoints(const float* points, int count, const SkPaint& paint) override;
+    virtual void drawLine(float startX, float startY, float stopX, float stopY,
+            const SkPaint& paint) override {
+        float points[4] = { startX, startY, stopX, stopY };
+        drawLines(points, 4, paint);
+    }
+    virtual void drawLines(const float* points, int count, const SkPaint& paint) override;
+    virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
+    virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
+    virtual void drawRoundRect(float left, float top, float right, float bottom,
+            float rx, float ry, const SkPaint& paint) override;
+    virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
+    virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override;
+    virtual void drawArc(float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
+    virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
+    virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+            const float* verts, const float* tex, const int* colors,
+            const uint16_t* indices, int indexCount, const SkPaint& paint) override
+        { /* RecordingCanvas does not support drawVertices(); ignore */ }
+
+    // Bitmap-based
+    virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
+    virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+                            const SkPaint* paint) override;
+    virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop,
+            float dstRight, float dstBottom, const SkPaint* paint) override;
+    virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors, const SkPaint* paint) override;
+    virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+            float dstLeft, float dstTop, float dstRight, float dstBottom,
+            const SkPaint* paint) override;
+
+    // Text
+    virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+            const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
+            float boundsRight, float boundsBottom, float totalAdvance) override;
+    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
+            int posCount, const SkPaint& paint) override;
+    virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+            float hOffset, float vOffset, const SkPaint& paint) override;
+    virtual bool drawTextAbsolutePos() const override { return false; }
+
+private:
+    enum DeferredBarrierType {
+        kBarrier_None,
+        kBarrier_InOrder,
+        kBarrier_OutOfOrder,
+    };
+
+    void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+    void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
+
+
+    size_t addOp(RecordedOp* op);
+// ----------------------------------------------------------------------------
+// lazy object copy
+// ----------------------------------------------------------------------------
+    LinearAllocator& alloc() { return mDisplayList->allocator; }
+
+    void refBitmapsInShader(const SkShader* shader);
+
+    template<class T>
+    inline const T* refBuffer(const T* srcBuffer, int32_t count) {
+        if (!srcBuffer) return nullptr;
+
+        T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T));
+        memcpy(dstBuffer, srcBuffer, count * sizeof(T));
+        return dstBuffer;
+    }
+
+    inline char* refText(const char* text, size_t byteLength) {
+        return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
+    }
+
+    inline const SkPath* refPath(const SkPath* path) {
+        if (!path) return nullptr;
+
+        // 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);
+        mDisplayList->pathResources.push_back(cachedPath);
+        return cachedPath;
+    }
+
+    inline const SkPaint* refPaint(const SkPaint* paint) {
+        if (!paint) return nullptr;
+
+        // If there is a draw filter apply it here and store the modified paint
+        // so that we don't need to modify the paint every time we access it.
+        SkTLazy<SkPaint> filteredPaint;
+        if (mDrawFilter.get()) {
+            filteredPaint.set(*paint);
+            mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type);
+            paint = filteredPaint.get();
+        }
+
+        // compute the hash key for the paint and check the cache.
+        const uint32_t key = paint->getHash();
+        const SkPaint* cachedPaint = mPaintMap.valueFor(key);
+        // In the unlikely event that 2 unique paints have the same hash we do a
+        // object equality check to ensure we don't erroneously dedup them.
+        if (cachedPaint == nullptr || *cachedPaint != *paint) {
+            cachedPaint = new SkPaint(*paint);
+            std::unique_ptr<const SkPaint> copy(cachedPaint);
+            mDisplayList->paints.push_back(std::move(copy));
+
+            // replaceValueFor() performs an add if the entry doesn't exist
+            mPaintMap.replaceValueFor(key, cachedPaint);
+            refBitmapsInShader(cachedPaint->getShader());
+        }
+
+        return cachedPaint;
+    }
+
+    inline const SkRegion* refRegion(const SkRegion* region) {
+        if (!region) {
+            return region;
+        }
+
+        const SkRegion* cachedRegion = mRegionMap.valueFor(region);
+        // TODO: Add generation ID to SkRegion
+        if (cachedRegion == nullptr) {
+            std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
+            cachedRegion = copy.get();
+            mDisplayList->regions.push_back(std::move(copy));
+
+            // replaceValueFor() performs an add if the entry doesn't exist
+            mRegionMap.replaceValueFor(region, cachedRegion);
+        }
+
+        return cachedRegion;
+    }
+
+    inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
+        // Note that this assumes the bitmap is immutable. There are cases this won't handle
+        // correctly, such as creating the bitmap from scratch, drawing with it, changing its
+        // contents, and drawing again. The only fix would be to always copy it the first time,
+        // which doesn't seem worth the extra cycles for this unlikely case.
+        SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
+        alloc().autoDestroy(localBitmap);
+        mDisplayList->bitmapResources.push_back(localBitmap);
+        return localBitmap;
+    }
+
+    inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
+        mDisplayList->patchResources.push_back(patch);
+        mResourceCache.incrementRefcount(patch);
+        return patch;
+    }
+
+    DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap;
+    DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap;
+    DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
+
+    CanvasState mState;
+    std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
+    ResourceCache& mResourceCache;
+    DeferredBarrierType mDeferredBarrierType = kBarrier_None;
+    DisplayList* mDisplayList = nullptr;
+    bool mHighContrastText = false;
+    SkAutoTUnref<SkDrawFilter> mDrawFilter;
+    int mRestoreSaveCount = -1;
+}; // class RecordingCanvas
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RECORDING_CANVAS_H
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 4c4cd3d..50199db 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -125,25 +125,32 @@
     }
 
     bool intersects(float l, float t, float r, float b) const {
-        return !intersectWith(l, t, r, b).isEmpty();
+        float tempLeft = std::max(left, l);
+        float tempTop = std::max(top, t);
+        float tempRight = std::min(right, r);
+        float tempBottom = std::min(bottom, b);
+
+        return ((tempLeft < tempRight) && (tempTop < tempBottom)); // !isEmpty
     }
 
     bool intersects(const Rect& r) const {
         return intersects(r.left, r.top, r.right, r.bottom);
     }
 
-    bool intersect(float l, float t, float r, float b) {
-        Rect tmp(l, t, r, b);
-        intersectWith(tmp);
-        if (!tmp.isEmpty()) {
-            set(tmp);
-            return true;
-        }
-        return false;
+    /**
+     * This method is named 'doIntersect' instead of 'intersect' so as not to be confused with
+     * SkRect::intersect / android.graphics.Rect#intersect behavior, which do not modify the object
+     * if the intersection of the rects would be empty.
+     */
+    void doIntersect(float l, float t, float r, float b) {
+        left = std::max(left, l);
+        top = std::max(top, t);
+        right = std::min(right, r);
+        bottom = std::min(bottom, b);
     }
 
-    bool intersect(const Rect& r) {
-        return intersect(r.left, r.top, r.right, r.bottom);
+    void doIntersect(const Rect& r) {
+        doIntersect(r.left, r.top, r.right, r.bottom);
     }
 
     inline bool contains(float l, float t, float r, float b) const {
@@ -271,24 +278,6 @@
     void dump(const char* label = nullptr) const {
         ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
     }
-
-private:
-    void intersectWith(Rect& tmp) const {
-        tmp.left = std::max(left, tmp.left);
-        tmp.top = std::max(top, tmp.top);
-        tmp.right = std::min(right, tmp.right);
-        tmp.bottom = std::min(bottom, tmp.bottom);
-    }
-
-    Rect intersectWith(float l, float t, float r, float b) const {
-        Rect tmp;
-        tmp.left = std::max(left, l);
-        tmp.top = std::max(top, t);
-        tmp.right = std::min(right, r);
-        tmp.bottom = std::min(bottom, b);
-        return tmp;
-    }
-
 }; // class Rect
 
 }; // namespace uirenderer
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index bf1b4d0..351fbaa 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -25,6 +25,9 @@
 
 #include "DamageAccumulator.h"
 #include "Debug.h"
+#if HWUI_NEW_OPS
+#include "RecordedOp.h"
+#endif
 #include "DisplayListOp.h"
 #include "LayerRenderer.h"
 #include "OpenGLRenderer.h"
@@ -45,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]->mRenderNode->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();
@@ -72,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;
 }
 
 /**
@@ -94,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());
@@ -170,20 +177,20 @@
     }
 
     pnode->clear_children();
-    if (mDisplayListData) {
-        for (auto&& child : mDisplayListData->children()) {
-            child->mRenderNode->copyTo(pnode->add_children());
+    if (mDisplayList) {
+        for (auto&& child : mDisplayList->getChildren()) {
+            child->renderNode->copyTo(pnode->add_children());
         }
     }
 }
 
 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;
 }
@@ -314,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);
@@ -326,12 +333,16 @@
     if (info.mode == TreeInfo::MODE_FULL) {
         pushStagingDisplayListChanges(info);
     }
-    prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
+    prepareSubTree(info, childFunctorsNeedLayer, mDisplayList);
     pushLayerUpdate(info);
 
     info.damageAccumulator->popTransform();
 }
 
+void RenderNode::syncProperties() {
+    mProperties = mStagingProperties;
+}
+
 void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
     // Push the animators first so that setupStartValueIfNecessary() is called
     // before properties() is trampled by stagingProperties(), as they are
@@ -343,7 +354,7 @@
         mDirtyPropertyFields = 0;
         damageSelf(info);
         info.damageAccumulator->popTransform();
-        mProperties = mStagingProperties;
+        syncProperties();
         applyLayerPropertiesToLayer(info);
         // We could try to be clever and only re-damage if the matrix changed.
         // However, we don't need to worry about that. The cost of over-damaging
@@ -364,56 +375,63 @@
     mLayer->setBlend(props.needsBlending());
 }
 
-void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
-    if (mNeedsDisplayListDataSync) {
-        mNeedsDisplayListDataSync = false;
-        // Make sure we inc first so that we don't fluctuate between 0 and 1,
-        // which would thrash the layer cache
-        if (mStagingDisplayListData) {
-            for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) {
-                mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount();
-            }
+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 (mStagingDisplayList) {
+        for (auto&& child : mStagingDisplayList->getChildren()) {
+            child->renderNode->incParentRefCount();
         }
+    }
+    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 (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);
-        deleteDisplayListData();
-        mDisplayListData = mStagingDisplayListData;
-        mStagingDisplayListData = nullptr;
-        if (mDisplayListData) {
-            for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
-                (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
-            }
-        }
+        syncDisplayList();
         damageSelf(info);
     }
 }
 
-void RenderNode::deleteDisplayListData() {
-    if (mDisplayListData) {
-        for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
-            mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
+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 (size_t i = 0; i < subtree->children().size(); i++) {
-            DrawRenderNodeOp* op = subtree->children()[i];
-            RenderNode* childNode = op->mRenderNode;
+        for (auto&& op : subtree->getChildren()) {
+            RenderNode* childNode = op->renderNode;
+#if HWUI_NEW_OPS
+            info.damageAccumulator->pushTransform(&op->localMatrix);
+            bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
+#else
             info.damageAccumulator->pushTransform(&op->mTransformFromParent);
             bool childFunctorsNeedLayer = functorsNeedLayer
                     // Recorded with non-rect clip, or canvas-rotated by parent
                     || op->mRecordedWithPotentialStencilClip;
+#endif
             childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
             info.damageAccumulator->popTransform();
         }
@@ -425,14 +443,14 @@
         LayerRenderer::destroyLayer(mLayer);
         mLayer = nullptr;
     }
-    if (mDisplayListData) {
-        for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
-            mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources();
+    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();
         }
     }
 }
@@ -449,6 +467,34 @@
     }
 }
 
+bool RenderNode::applyViewProperties(CanvasState& canvasState) const {
+    const Outline& outline = properties().getOutline();
+    if (properties().getAlpha() <= 0
+            || (outline.getShouldClip() && outline.isEmpty())
+            || properties().getScaleX() == 0
+            || properties().getScaleY() == 0) {
+        return false; // rejected
+    }
+
+    if (properties().getLeft() != 0 || properties().getTop() != 0) {
+        canvasState.translate(properties().getLeft(), properties().getTop());
+    }
+    if (properties().getStaticMatrix()) {
+        canvasState.concatMatrix(*properties().getStaticMatrix());
+    } else if (properties().getAnimationMatrix()) {
+        canvasState.concatMatrix(*properties().getAnimationMatrix());
+    }
+    if (properties().hasTransformMatrix()) {
+        if (properties().isTransformTranslateOnly()) {
+            canvasState.translate(properties().getTranslationX(), properties().getTranslationY());
+        } else {
+            canvasState.concatMatrix(*properties().getTransformMatrix());
+        }
+    }
+    return !canvasState.quickRejectConservative(
+            0, 0, properties().getWidth(), properties().getHeight());
+}
+
 /*
  * For property operations, we pass a savecount of 0, since the operations aren't part of the
  * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
@@ -580,24 +626,27 @@
  * which are flagged to not draw in the standard draw loop.
  */
 void RenderNode::computeOrdering() {
+#if !HWUI_NEW_OPS
     ATRACE_CALL();
     mProjectedNodes.clear();
 
     // 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];
-        childOp->mRenderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
+    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
 }
 
 void RenderNode::computeOrderingImpl(
         DrawRenderNodeOp* opState,
         std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
         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
@@ -614,12 +663,12 @@
         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];
-            RenderNode* child = childOp->mRenderNode;
+        for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
+            DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+            RenderNode* child = childOp->renderNode;
 
             std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
             const mat4* projectionTransform = nullptr;
@@ -642,6 +691,7 @@
             child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
         }
     }
+#endif
 }
 
 class DeferOperationHandler {
@@ -699,13 +749,14 @@
     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];
-        RenderNode* child = childOp->mRenderNode;
+        DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+        RenderNode* child = childOp->renderNode;
         float childZ = child->properties().getZ();
 
         if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
@@ -719,6 +770,7 @@
 
     // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
     std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
+#endif
 }
 
 template <class T>
@@ -824,7 +876,7 @@
     while (shadowIndex < endIndex || drawIndex < endIndex) {
         if (shadowIndex < endIndex) {
             DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
-            RenderNode* caster = casterOp->mRenderNode;
+            RenderNode* caster = casterOp->renderNode;
             const float casterZ = zTranslatedNodes[shadowIndex].key;
             // attempt to render the shadow if the caster about to be drawn is its caster,
             // OR if its caster's Z value is similar to the previous potential caster
@@ -867,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->mRenderNode->properties();
+    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);
 
@@ -904,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;
@@ -948,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) {
@@ -959,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);
@@ -969,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
@@ -988,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 88fc608..57e41c6 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -29,8 +29,8 @@
 
 #include "AnimatorManager.h"
 #include "Debug.h"
-#include "Matrix.h"
 #include "DisplayList.h"
+#include "Matrix.h"
 #include "RenderProperties.h"
 
 #include <vector>
@@ -43,6 +43,7 @@
 namespace android {
 namespace uirenderer {
 
+class CanvasState;
 class DisplayListOp;
 class DisplayListCanvas;
 class OpenGLRenderer;
@@ -65,15 +66,16 @@
  * 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 {
+friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
 public:
     enum DirtyPropertyMask {
         GENERIC         = 1 << 1,
@@ -102,7 +104,7 @@
 
     void debugDumpLayers(const char* prefix);
 
-    ANDROID_API void setStagingDisplayList(DisplayListData* newData);
+    ANDROID_API void setStagingDisplayList(DisplayList* newData);
 
     void computeOrdering();
 
@@ -114,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 {
@@ -176,8 +178,25 @@
 
     AnimatorManager& animators() { return mAnimatorManager; }
 
+    // Returns false if the properties dictate the subtree contained in this RenderNode won't render
+    bool applyViewProperties(CanvasState& canvasState) const;
+
     void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
 
+    bool nothingToDraw() const {
+        const Outline& outline = properties().getOutline();
+        return mDisplayList == nullptr
+                || properties().getAlpha() <= 0
+                || (outline.getShouldClip() && outline.isEmpty())
+                || properties().getScaleX() == 0
+                || properties().getScaleY() == 0;
+    }
+
+    // Only call if RenderNode has DisplayList...
+    const DisplayList& getDisplayList() const {
+        return *mDisplayList;
+    }
+
 private:
     typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
 
@@ -200,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>
@@ -235,14 +254,18 @@
         const char* mText;
     };
 
+
+    void syncProperties();
+    void syncDisplayList();
+
     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++; }
@@ -254,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;
@@ -278,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 71589c8..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 {
@@ -549,7 +549,7 @@
         if (flags & CLIP_TO_BOUNDS) {
             outRect->set(0, 0, getWidth(), getHeight());
             if (flags & CLIP_TO_CLIP_BOUNDS) {
-                outRect->intersect(mPrimitiveFields.mClipBounds);
+                outRect->doIntersect(mPrimitiveFields.mClipBounds);
             }
         } else {
             outRect->set(mPrimitiveFields.mClipBounds);
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 4d60b8d..0a58f4b 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -44,7 +44,7 @@
  * Copies the specified snapshot/ The specified snapshot is stored as
  * the previous snapshot.
  */
-Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
+Snapshot::Snapshot(Snapshot* s, int saveFlags)
         : flags(0)
         , previous(s)
         , layer(s->layer)
@@ -148,7 +148,7 @@
     const Snapshot* current = this;
     do {
         snapshotList.push(current);
-        current = current->previous.get();
+        current = current->previous;
     } while (current);
 
     // traverse the list, adding in each transform that contributes to the total transform
@@ -240,7 +240,7 @@
 
 void Snapshot::dump() const {
     ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
-            this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
+            this, flags, previous, getViewportHeight(), isIgnored(), !mClipArea->isSimple());
     const Rect& clipRect(mClipArea->getClipRect());
     ALOGD("  ClipRect %.1f %.1f %.1f %.1f, clip simple %d",
             clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, mClipArea->isSimple());
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index cf8f11c..4789b33 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -83,11 +83,11 @@
  * Each snapshot has a link to a previous snapshot, indicating the previous
  * state of the renderer.
  */
-class Snapshot: public LightRefBase<Snapshot> {
+class Snapshot {
 public:
 
     Snapshot();
-    Snapshot(const sp<Snapshot>& s, int saveFlags);
+    Snapshot(Snapshot* s, int saveFlags);
 
     /**
      * Various flags set on ::flags.
@@ -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; }
@@ -229,7 +228,7 @@
     /**
      * Previous snapshot.
      */
-    sp<Snapshot> previous;
+    Snapshot* previous;
 
     /**
      * A pointer to the currently active layer.
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
new file mode 100644
index 0000000..7a62037
--- /dev/null
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "DisplayList.h"
+#if HWUI_NEW_OPS
+#include "RecordingCanvas.h"
+#else
+#include "DisplayListCanvas.h"
+#endif
+#include "microbench/MicroBench.h"
+#include "unit_tests/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+BENCHMARK_NO_ARG(BM_DisplayList_alloc);
+void BM_DisplayList_alloc::Run(int iters) {
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        auto displayList = new DisplayList();
+        MicroBench::DoNotOptimize(displayList);
+        delete displayList;
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_DisplayList_alloc_theoretical);
+void BM_DisplayList_alloc_theoretical::Run(int iters) {
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        auto displayList = new char[sizeof(DisplayList)];
+        MicroBench::DoNotOptimize(displayList);
+        delete[] displayList;
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty);
+void BM_DisplayListCanvas_record_empty::Run(int iters) {
+    TestCanvas canvas(100, 100);
+    delete canvas.finishRecording();
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        canvas.reset(100, 100);
+        MicroBench::DoNotOptimize(&canvas);
+        delete canvas.finishRecording();
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore);
+void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
+    TestCanvas canvas(100, 100);
+    delete canvas.finishRecording();
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        canvas.reset(100, 100);
+        canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+        canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+        MicroBench::DoNotOptimize(&canvas);
+        canvas.restore();
+        canvas.restore();
+        delete canvas.finishRecording();
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate);
+void BM_DisplayListCanvas_record_translate::Run(int iters) {
+    TestCanvas canvas(100, 100);
+    delete canvas.finishRecording();
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        canvas.reset(100, 100);
+        canvas.scale(10, 10);
+        MicroBench::DoNotOptimize(&canvas);
+        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();
+}
+
+class NullClient: public CanvasStateClient {
+    void onViewportInitialized() override {}
+    void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+    GLuint getTargetFbo() const override { return 0; }
+};
+
+BENCHMARK_NO_ARG(BM_CanvasState_saverestore);
+void BM_CanvasState_saverestore::Run(int iters) {
+    NullClient client;
+    CanvasState state(client);
+    state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        state.save(SkCanvas::kMatrixClip_SaveFlag);
+        state.save(SkCanvas::kMatrixClip_SaveFlag);
+        MicroBench::DoNotOptimize(&state);
+        state.restore();
+        state.restore();
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_CanvasState_init);
+void BM_CanvasState_init::Run(int iters) {
+    NullClient client;
+    CanvasState state(client);
+    state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+        MicroBench::DoNotOptimize(&state);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_CanvasState_translate);
+void BM_CanvasState_translate::Run(int iters) {
+    NullClient client;
+    CanvasState state(client);
+    state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; ++i) {
+        state.translate(5, 5, 0);
+        MicroBench::DoNotOptimize(&state);
+        state.translate(-5, -5, 0);
+    }
+    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/MicroBench.h b/libs/hwui/microbench/MicroBench.h
new file mode 100644
index 0000000..f05e92c
--- /dev/null
+++ b/libs/hwui/microbench/MicroBench.h
@@ -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.
+ */
+#ifndef MICROBENCH_MICROBENCH_H
+#define MICROBENCH_MICROBENCH_H
+
+namespace android {
+namespace uirenderer {
+
+#define NO_INLINE __attribute__ ((noinline))
+
+class MicroBench {
+public:
+    template <class Tp>
+    static inline void DoNotOptimize(Tp const& value) {
+        asm volatile("" : "+rm" (const_cast<Tp&>(value)));
+    }
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* MICROBENCH_MICROBENCH_H */
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
new file mode 100644
index 0000000..382c0bd
--- /dev/null
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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(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(200, 200, *sReorderingDisplayList);
+            MicroBench::DoNotOptimize(&reorderer);
+
+            BakedOpRenderer::Info info(caches, renderState, 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..1b0f5ea
--- /dev/null
+++ b/libs/hwui/microbench/ShadowBench.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.addRoundRect(SkRect::MakeWH(100, 100), 5, 5);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        VertexBuffer ambient;
+        VertexBuffer spot;
+        tessellateShadows(shadowData, true, path, &ambient, &spot);
+        MicroBench::DoNotOptimize(&ambient);
+        MicroBench::DoNotOptimize(&spot);
+    }
+    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/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index dfa70ac..9637117 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -125,6 +125,21 @@
     }
 }
 
+GLuint RenderState::genFramebuffer() {
+    GLuint ret;
+    glGenFramebuffers(1, &ret);
+    return ret;
+}
+
+void RenderState::deleteFramebuffer(GLuint fbo) {
+    if (mFramebuffer == fbo) {
+        // GL defines that deleting the currently bound FBO rebinds FBO 0.
+        // Reflect this in our cached value.
+        mFramebuffer = 0;
+    }
+    glDeleteFramebuffers(1, &fbo);
+}
+
 void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
     if (mode == DrawGlInfo::kModeProcessNoContext) {
         // If there's no context we don't need to interrupt as there's
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9ae0845..87a7996 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -57,7 +57,10 @@
     void getViewport(GLsizei* outWidth, GLsizei* outHeight);
 
     void bindFramebuffer(GLuint fbo);
-    GLint getFramebuffer() { return mFramebuffer; }
+    GLuint getFramebuffer() { return mFramebuffer; }
+    GLuint genFramebuffer();
+    void deleteFramebuffer(GLuint fbo);
+
 
     void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ddfd621..7c0f0b6 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -28,6 +28,11 @@
 #include "renderstate/Stencil.h"
 #include "protos/hwui.pb.h"
 
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#include "OpReorderer.h"
+#endif
+
 #include <cutils/properties.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <private/hwui/DrawGlInfo.h>
@@ -121,9 +126,11 @@
 
 bool CanvasContext::initialize(ANativeWindow* window) {
     setSurface(window);
+#if !HWUI_NEW_OPS
     if (mCanvas) return false;
     mCanvas = new OpenGLRenderer(mRenderThread.renderState());
     mCanvas->initProperties();
+#endif
     return true;
 }
 
@@ -162,11 +169,13 @@
 }
 
 void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
+#if !HWUI_NEW_OPS
     bool success = layerUpdater->apply();
     LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
     if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
         mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
     }
+#endif
 }
 
 static bool wasSkipped(FrameInfo* info) {
@@ -239,8 +248,10 @@
 }
 
 void CanvasContext::draw() {
+#if !HWUI_NEW_OPS
     LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
             "drawRenderNode called on a context with no canvas or surface!");
+#endif
 
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
@@ -254,10 +265,12 @@
     mCurrentFrameInfo->markIssueDrawCommandsStart();
 
     Frame frame = mEglManager.beginFrame(mEglSurface);
-    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();
@@ -303,6 +316,16 @@
     mDamageHistory.next() = screenDirty;
 
     mEglManager.damageFrame(frame, dirty);
+
+#if HWUI_NEW_OPS
+    OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+    BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), 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);
 
@@ -339,7 +362,7 @@
             // Remember the intersection of the target bounds and the intersection bounds against
             // which we have to crop the content.
             backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
-            backdropBounds.intersect(targetBounds);
+            backdropBounds.doIntersect(targetBounds);
             // Check if we have to draw something on the left side ...
             if (targetBounds.left < contentBounds.left) {
                 mCanvas->save(SkCanvas::kClip_SaveFlag);
@@ -413,7 +436,7 @@
     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();
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..485759b 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,24 +324,16 @@
     }
 #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)) {
         return true;
     }
-    if (err == EGL_BAD_SURFACE) {
+    if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) {
         // For some reason our surface was destroyed out from under us
         // This really shouldn't happen, but if it does we can recover easily
         // by just not trying to use the surface anymore
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/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h
new file mode 100644
index 0000000..e16310e
--- /dev/null
+++ b/libs/hwui/tests/Benchmark.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TESTS_BENCHMARK_H
+#define TESTS_BENCHMARK_H
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+struct BenchmarkOptions {
+    int count;
+};
+
+typedef void (*BenchmarkFunctor)(const BenchmarkOptions&);
+
+struct BenchmarkInfo {
+    std::string name;
+    std::string description;
+    BenchmarkFunctor functor;
+};
+
+class Benchmark {
+public:
+    Benchmark(const BenchmarkInfo& info) {
+        registerBenchmark(info);
+    }
+
+private:
+    Benchmark() = delete;
+    Benchmark(const Benchmark&) = delete;
+    Benchmark& operator=(const Benchmark&) = delete;
+
+    static void registerBenchmark(const BenchmarkInfo& info);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TESTS_BENCHMARK_H */
diff --git a/libs/hwui/tests/TestContext.cpp b/libs/hwui/tests/TestContext.cpp
index cebe765..ba763a8 100644
--- a/libs/hwui/tests/TestContext.cpp
+++ b/libs/hwui/tests/TestContext.cpp
@@ -22,16 +22,35 @@
 
 static const int IDENT_DISPLAYEVENT = 1;
 
-static DisplayInfo getBuiltInDisplay() {
+static android::DisplayInfo DUMMY_DISPLAY {
+    1080, //w
+    1920, //h
+    320.0, // xdpi
+    320.0, // ydpi
+    60.0, // fps
+    2.0, // density
+    0, // orientation
+    false, // secure?
+    0, // appVsyncOffset
+    0, // presentationDeadline
+    0, // colorTransform
+};
+
+DisplayInfo getBuiltInDisplay() {
+#if !HWUI_NULL_GPU
     DisplayInfo display;
     sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
             ISurfaceComposer::eDisplayIdMain));
     status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display);
     LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
     return display;
+#else
+    return DUMMY_DISPLAY;
+#endif
 }
 
-android::DisplayInfo gDisplay = getBuiltInDisplay();
+// Initialize to a dummy default
+android::DisplayInfo gDisplay = DUMMY_DISPLAY;
 
 TestContext::TestContext() {
     mLooper = new Looper(true);
diff --git a/libs/hwui/tests/TestContext.h b/libs/hwui/tests/TestContext.h
index 7b30fc1..2bbe5df 100644
--- a/libs/hwui/tests/TestContext.h
+++ b/libs/hwui/tests/TestContext.h
@@ -32,6 +32,8 @@
 extern DisplayInfo gDisplay;
 #define dp(x) ((x) * android::uirenderer::test::gDisplay.density)
 
+DisplayInfo getBuiltInDisplay();
+
 class TestContext {
 public:
     TestContext();
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
new file mode 100644
index 0000000..2eefd37
--- /dev/null
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -0,0 +1,403 @@
+/*
+ * 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 <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+#include <AnimationContext.h>
+#include <DisplayListCanvas.h>
+#include <RecordingCanvas.h>
+#include <RenderNode.h>
+#include <renderthread/RenderProxy.h>
+#include <renderthread/RenderTask.h>
+
+#include "Benchmark.h"
+#include "TestContext.h"
+
+#include "protos/hwui.pb.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+        return new AnimationContext(clock);
+    }
+};
+
+static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
+    TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
+    contentCallback(canvas);
+    node.setStagingDisplayList(canvas.finishRecording());
+}
+
+class TreeContentAnimation {
+public:
+    virtual ~TreeContentAnimation() {}
+    int frameCount = 150;
+    virtual int getFrameCount() { return frameCount; }
+    virtual void setFrameCount(int fc) {
+        if (fc > 0) {
+            frameCount = fc;
+        }
+    }
+    virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
+    virtual void doFrame(int frameNr) = 0;
+
+    template <class T>
+    static void run(const BenchmarkOptions& opts) {
+        // Switch to the real display
+        gDisplay = getBuiltInDisplay();
+
+        T animation;
+        animation.setFrameCount(opts.count);
+
+        TestContext testContext;
+
+        // create the native surface
+        const int width = gDisplay.w;
+        const int height = gDisplay.h;
+        sp<Surface> surface = testContext.surface();
+
+        RenderNode* rootNode = new RenderNode();
+        rootNode->incStrong(nullptr);
+        rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
+        rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        rootNode->mutateStagingProperties().setClipToBounds(false);
+        rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+
+        ContextFactory factory;
+        std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
+        proxy->loadSystemProperties();
+        proxy->initialize(surface);
+        float lightX = width / 2.0;
+        proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
+        proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
+
+        recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
+            animation.createContent(width, height, &canvas); //TODO: no&
+        });
+
+        // Do a few cold runs then reset the stats so that the caches are all hot
+        for (int i = 0; i < 3; i++) {
+            testContext.waitForVsync();
+            proxy->syncAndDrawFrame();
+        }
+        proxy->resetProfileInfo();
+
+        for (int i = 0; i < animation.getFrameCount(); i++) {
+            testContext.waitForVsync();
+
+            ATRACE_NAME("UI-Draw Frame");
+            nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+            UiFrameInfoBuilder(proxy->frameInfo())
+                    .setVsync(vsync, vsync);
+            animation.doFrame(i);
+            proxy->syncAndDrawFrame();
+        }
+
+        proxy->dumpProfileInfo(STDOUT_FILENO, 0);
+        rootNode->decStrong(nullptr);
+    }
+};
+
+class ShadowGridAnimation : public TreeContentAnimation {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+                canvas->drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas->insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+        node->mutateStagingProperties().setElevation(dp(16));
+        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
+
+        recordNode(*node, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+        return node;
+    }
+};
+static Benchmark _ShadowGrid(BenchmarkInfo{
+    "shadowgrid",
+    "A grid of rounded rects that cast a shadow. Simplified scenario of an "
+    "Android TV-style launcher interface. High CPU/GPU load.",
+    TreeContentAnimation::run<ShadowGridAnimation>
+});
+
+class ShadowGrid2Animation : public TreeContentAnimation {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
+
+        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
+            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
+                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
+                canvas->drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas->insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+        node->mutateStagingProperties().setElevation(dp(16));
+        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
+
+        recordNode(*node, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+        return node;
+    }
+};
+static Benchmark _ShadowGrid2(BenchmarkInfo{
+    "shadowgrid2",
+    "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
+    "variant of shadowgrid. Very high CPU load, high GPU load.",
+    TreeContentAnimation::run<ShadowGrid2Animation>
+});
+
+class RectGridAnimation : public TreeContentAnimation {
+public:
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
+
+        card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+            SkRegion region;
+            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+                }
+            }
+
+            SkPaint paint;
+            paint.setColor(0xff00ffff);
+            canvas.drawRegion(region, paint);
+        });
+        canvas->drawRenderNode(card.get());
+
+        canvas->insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
+static Benchmark _RectGrid(BenchmarkInfo{
+    "rectgrid",
+    "A dense grid of 1x1 rects that should visually look like a single rect. "
+    "Low CPU/GPU load.",
+    TreeContentAnimation::run<RectGridAnimation>
+});
+
+class OvalAnimation : public TreeContentAnimation {
+public:
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas->insertReorderBarrier(true);
+
+        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setColor(0xFF000000);
+            canvas.drawOval(0, 0, 200, 200, paint);
+        });
+        canvas->drawRenderNode(card.get());
+
+        canvas->insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
+static Benchmark _Oval(BenchmarkInfo{
+    "oval",
+    "Draws 1 oval.",
+    TreeContentAnimation::run<OvalAnimation>
+});
+
+class PartialDamageTest : public TreeContentAnimation {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
+                        COLORS[static_cast<int>((y / dp(116))) % 4]);
+                canvas->drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+            canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
+                    SkXfermode::kSrcOver_Mode);
+        });
+    }
+
+    static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
+        int startA = (start >> 24) & 0xff;
+        int startR = (start >> 16) & 0xff;
+        int startG = (start >> 8) & 0xff;
+        int startB = start & 0xff;
+
+        int endA = (end >> 24) & 0xff;
+        int endR = (end >> 16) & 0xff;
+        int endG = (end >> 8) & 0xff;
+        int endB = end & 0xff;
+
+        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                (int)((startB + (int)(fraction * (endB - startB))));
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        recordNode(*node, [color](TestCanvas& canvas) {
+            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+        });
+        return node;
+    }
+};
+static Benchmark _PartialDamage(BenchmarkInfo{
+    "partialdamage",
+    "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
+    "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
+    "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
+    TreeContentAnimation::run<PartialDamageTest>
+});
+
+
+class SaveLayerAnimation : public TreeContentAnimation {
+public:
+    sp<RenderNode> card = new RenderNode();
+    void createContent(int width, int height, TestCanvas* canvas) override {
+        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+
+        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        recordNode(*card, [](TestCanvas& canvas) {
+            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+            canvas.restore();
+            canvas.restore();
+        });
+
+        canvas->drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
+static Benchmark _SaveLayer(BenchmarkInfo{
+    "savelayer",
+    "A nested pair of clipped saveLayer operations. "
+    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+    TreeContentAnimation::run<SaveLayerAnimation>
+});
diff --git a/libs/hwui/tests/how_to_run.txt b/libs/hwui/tests/how_to_run.txt
index 85900ef..b051768f 100644
--- a/libs/hwui/tests/how_to_run.txt
+++ b/libs/hwui/tests/how_to_run.txt
@@ -2,16 +2,4 @@
     adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest &&
     adb shell /data/local/tmp/hwuitest
 
-
-Command arguments:
-hwuitest [testname]
-
-Default test is 'shadowgrid'
-
-List of tests:
-
-shadowgrid: creates a grid of rounded rects that cast shadows, high CPU & GPU load
-
-rectgrid: creates a grid of 1x1 rects
-
-oval: draws 1 oval
+Pass --help to get help
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 0bbf08c..aee84de 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -14,385 +14,181 @@
  * limitations under the License.
  */
 
-#include <cutils/log.h>
-#include <gui/Surface.h>
-#include <ui/PixelFormat.h>
-
-#include <AnimationContext.h>
-#include <DisplayListCanvas.h>
-#include <RenderNode.h>
-#include <renderthread/RenderProxy.h>
-#include <renderthread/RenderTask.h>
-
-#include "TestContext.h"
+#include "Benchmark.h"
 
 #include "protos/hwui.pb.h"
 
+#include <getopt.h>
 #include <stdio.h>
+#include <string>
 #include <unistd.h>
+#include <unordered_map>
+#include <vector>
 
 using namespace android;
 using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-using namespace android::uirenderer::test;
 
-class ContextFactory : public IContextFactory {
-public:
-    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
-        return new AnimationContext(clock);
-    }
-};
-
-static DisplayListCanvas* startRecording(RenderNode* node) {
-    DisplayListCanvas* renderer = new DisplayListCanvas(
-            node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
-    return renderer;
+// Not a static global because we need to force the map to be constructed
+// before we try to add things to it.
+std::unordered_map<std::string, BenchmarkInfo>& testMap() {
+    static std::unordered_map<std::string, BenchmarkInfo> testMap;
+    return testMap;
 }
 
-static void endRecording(DisplayListCanvas* renderer, RenderNode* node) {
-    node->setStagingDisplayList(renderer->finishRecording());
-    delete renderer;
+void Benchmark::registerBenchmark(const BenchmarkInfo& info) {
+    testMap()[info.name] = info;
 }
 
-class TreeContentAnimation {
-public:
-    virtual ~TreeContentAnimation() {}
-    int frameCount = 150;
-    virtual int getFrameCount() { return frameCount; }
-    virtual void setFrameCount(int fc) {
-        if (fc > 0) {
-            frameCount = fc;
-        }
-    }
-    virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0;
-    virtual void doFrame(int frameNr) = 0;
+static int gFrameCount = 150;
+static int gRepeatCount = 1;
+static std::vector<BenchmarkInfo> gRunTests;
 
-    template <class T>
-    static void run(int frameCount) {
-        T animation;
-        animation.setFrameCount(frameCount);
+static void printHelp() {
+    printf("\
+USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
+\n\
+OPTIONS:\n\
+  -c, --count=NUM      NUM loops a test should run (example, number of frames)\n\
+  -r, --runs=NUM       Repeat the test(s) NUM times\n\
+  -h, --help           Display this help\n\
+  --list               List all tests\n\
+\n");
+}
 
-        TestContext testContext;
-
-        // create the native surface
-        const int width = gDisplay.w;
-        const int height = gDisplay.h;
-        sp<Surface> surface = testContext.surface();
-
-        RenderNode* rootNode = new RenderNode();
-        rootNode->incStrong(nullptr);
-        rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
-        rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        rootNode->mutateStagingProperties().setClipToBounds(false);
-        rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
-
-        ContextFactory factory;
-        std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
-        proxy->loadSystemProperties();
-        proxy->initialize(surface);
-        float lightX = width / 2.0;
-        proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
-        proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
-
-        android::uirenderer::Rect DUMMY;
-
-        DisplayListCanvas* renderer = startRecording(rootNode);
-        animation.createContent(width, height, renderer);
-        endRecording(renderer, rootNode);
-
-        // Do a few cold runs then reset the stats so that the caches are all hot
-        for (int i = 0; i < 3; i++) {
-            testContext.waitForVsync();
-            proxy->syncAndDrawFrame();
-        }
-        proxy->resetProfileInfo();
-
-        for (int i = 0; i < animation.getFrameCount(); i++) {
-            testContext.waitForVsync();
-
-            ATRACE_NAME("UI-Draw Frame");
-            nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
-            UiFrameInfoBuilder(proxy->frameInfo())
-                    .setVsync(vsync, vsync);
-            animation.doFrame(i);
-            proxy->syncAndDrawFrame();
-        }
-
-        proxy->dumpProfileInfo(STDOUT_FILENO, 0);
-        rootNode->decStrong(nullptr);
-    }
-};
-
-class ShadowGridAnimation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, DisplayListCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
-                renderer->drawRenderNode(card.get());
-                cards.push_back(card);
+static void listTests() {
+    printf("Tests: \n");
+    for (auto&& test : testMap()) {
+        auto&& info = test.second;
+        const char* col1 = info.name.c_str();
+        int dlen = info.description.length();
+        const char* col2 = info.description.c_str();
+        // World's best line breaking algorithm.
+        do {
+            int toPrint = dlen;
+            if (toPrint > 50) {
+                char* found = (char*) memrchr(col2, ' ', 50);
+                if (found) {
+                    toPrint = found - col2;
+                } else {
+                    toPrint = 50;
+                }
             }
-        }
-
-        renderer->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        DisplayListCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
-        return node;
-    }
-};
-
-class ShadowGrid2Animation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, DisplayListCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
-
-        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
-            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
-                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
-                renderer->drawRenderNode(card.get());
-                cards.push_back(card);
+            printf("%-20s %.*s\n", col1, toPrint, col2);
+            col1 = "";
+            col2 += toPrint;
+            dlen -= toPrint;
+            while (*col2 == ' ') {
+                col2++; dlen--;
             }
-        }
+        } while (dlen > 0);
+        printf("\n");
+    }
+}
 
-        renderer->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        DisplayListCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
-        return node;
-    }
+static const struct option LONG_OPTIONS[] = {
+    { "frames", required_argument, nullptr, 'f' },
+    { "repeat", required_argument, nullptr, 'r' },
+    { "help", no_argument, nullptr, 'h' },
+    { "list", no_argument, nullptr, 'l' },
+    { 0, 0, 0, 0 }
 };
 
-class RectGridAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card;
-    void createContent(int width, int height, DisplayListCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
+static const char* SHORT_OPTIONS = "c:r:h";
 
-        card = createCard(40, 40, 200, 200);
-        renderer->drawRenderNode(card.get());
+void parseOptions(int argc, char* argv[]) {
+    int c;
+    // temporary variable
+    int count;
+    bool error = false;
+    opterr = 0;
 
-        renderer->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    while (true) {
 
-        DisplayListCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+        /* getopt_long stores the option index here. */
+        int option_index = 0;
 
-        SkRegion region;
-        for (int xOffset = 0; xOffset < width; xOffset+=2) {
-            for (int yOffset = 0; yOffset < height; yOffset+=2) {
-                region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+        c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index);
+
+        if (c == -1)
+            break;
+
+        switch (c) {
+        case 0:
+            // Option set a flag, don't need to do anything
+            // (although none of the current LONG_OPTIONS do this...)
+            break;
+
+        case 'l':
+            listTests();
+            exit(EXIT_SUCCESS);
+            break;
+
+        case 'c':
+            count = atoi(optarg);
+            if (!count) {
+                fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
+                error = true;
+            } else {
+                gFrameCount = (count > 0 ? count : INT_MAX);
             }
-        }
+            break;
 
-        SkPaint paint;
-        paint.setColor(0xff00ffff);
-        renderer->drawRegion(region, paint);
-
-        endRecording(renderer, node.get());
-        return node;
-    }
-};
-
-class OvalAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card;
-    void createContent(int width, int height, DisplayListCanvas* renderer) override {
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        renderer->insertReorderBarrier(true);
-
-        card = createCard(40, 40, 400, 400);
-        renderer->drawRenderNode(card.get());
-
-        renderer->insertReorderBarrier(false);
-    }
-
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        DisplayListCanvas* renderer = startRecording(node.get());
-
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(0xFF000000);
-        renderer->drawOval(0, 0, width, height, paint);
-
-        endRecording(renderer, node.get());
-        return node;
-    }
-};
-
-class PartialInvalTest : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, DisplayListCanvas* renderer) override {
-        static SkColor COLORS[] = {
-                0xFFF44336,
-                0xFF9C27B0,
-                0xFF2196F3,
-                0xFF4CAF50,
-        };
-
-        renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
-                        COLORS[static_cast<int>((y / dp(116))) % 4]);
-                renderer->drawRenderNode(card.get());
-                cards.push_back(card);
+        case 'r':
+            count = atoi(optarg);
+            if (!count) {
+                fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
+                error = true;
+            } else {
+                gRepeatCount = (count > 0 ? count : INT_MAX);
             }
+            break;
+
+        case 'h':
+            printHelp();
+            exit(EXIT_SUCCESS);
+            break;
+
+        case '?':
+            fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
+            // fall-through
+        default:
+            error = true;
+            break;
         }
     }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
-        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
-        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
 
-        DisplayListCanvas* renderer = startRecording(cards[0].get());
-        renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
-                SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, cards[0].get());
+    if (error) {
+        fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
+        exit(EXIT_FAILURE);
     }
 
-    static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
-        int startA = (start >> 24) & 0xff;
-        int startR = (start >> 16) & 0xff;
-        int startG = (start >> 8) & 0xff;
-        int startB = start & 0xff;
-
-        int endA = (end >> 24) & 0xff;
-        int endR = (end >> 16) & 0xff;
-        int endG = (end >> 8) & 0xff;
-        int endB = end & 0xff;
-
-        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
-                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
-                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
-                (int)((startB + (int)(fraction * (endB - startB))));
+    /* Print any remaining command line arguments (not options). */
+    if (optind < argc) {
+        do {
+            const char* test = argv[optind++];
+            auto pos = testMap().find(test);
+            if (pos == testMap().end()) {
+                fprintf(stderr, "Unknown test '%s'\n", test);
+                exit(EXIT_FAILURE);
+            } else {
+                gRunTests.push_back(pos->second);
+            }
+        } while (optind < argc);
+    } else {
+        gRunTests.push_back(testMap()["shadowgrid"]);
     }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        DisplayListCanvas* renderer = startRecording(node.get());
-        renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
-        endRecording(renderer, node.get());
-        return node;
-    }
-};
-
-struct cstr_cmp {
-    bool operator()(const char *a, const char *b) const {
-        return std::strcmp(a, b) < 0;
-    }
-};
-
-typedef void (*testProc)(int);
-
-std::map<const char*, testProc, cstr_cmp> gTestMap {
-    {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>},
-    {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>},
-    {"rectgrid", TreeContentAnimation::run<RectGridAnimation> },
-    {"oval", TreeContentAnimation::run<OvalAnimation> },
-    {"partialinval", TreeContentAnimation::run<PartialInvalTest> },
-};
+}
 
 int main(int argc, char* argv[]) {
-    const char* testName = argc > 1 ? argv[1] : "shadowgrid";
-    testProc proc = gTestMap[testName];
-    if(!proc) {
-        printf("Error: couldn't find test %s\n", testName);
-        return 1;
-    }
-    int loopCount = 1;
-    if (argc > 2) {
-        loopCount = atoi(argv[2]);
-        if (!loopCount) {
-            printf("Invalid loop count!\n");
-            return 1;
+    parseOptions(argc, argv);
+
+    BenchmarkOptions opts;
+    opts.count = gFrameCount;
+    for (int i = 0; i < gRepeatCount; i++) {
+        for (auto&& test : gRunTests) {
+            test.functor(opts);
         }
     }
-    int frameCount = 150;
-    if (argc > 3) {
-        frameCount = atoi(argv[3]);
-        if (frameCount < 1) {
-            printf("Invalid frame count!\n");
-            return 1;
-        }
-    }
-    if (loopCount < 0) {
-        loopCount = INT_MAX;
-    }
-    for (int i = 0; i < loopCount; i++) {
-        proc(frameCount);
-    }
     printf("Success!\n");
     return 0;
 }
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
new file mode 100644
index 0000000..bc1b69f
--- /dev/null
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <BakedOpState.h>
+#include <RecordedOp.h>
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(ResolvedRenderState, resolution) {
+    Matrix4 identity;
+    identity.loadIdentity();
+
+    Matrix4 translate10x20;
+    translate10x20.loadTranslate(10, 20, 0);
+
+    SkPaint paint;
+    RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint);
+    {
+        // recorded with transform, no parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp);
+        EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
+        EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200));
+        EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped
+    }
+    {
+        // recorded with transform and parent transform
+        auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(0, 0, 100, 200));
+        ResolvedRenderState state(*parentSnapshot, recordedOp);
+
+        Matrix4 expectedTranslate;
+        expectedTranslate.loadTranslate(20, 40, 0);
+        EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate);
+
+        // intersection of parent & transformed child clip
+        EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200));
+
+        // translated and also clipped
+        EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200));
+    }
+}
+
+TEST(BakedOpState, constructAndReject) {
+    LinearAllocator allocator;
+
+    Matrix4 identity;
+    identity.loadIdentity();
+
+    Matrix4 translate100x0;
+    translate100x0.loadTranslate(100, 0, 0);
+
+    SkPaint paint;
+    {
+        RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
+
+        EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
+        EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
+    }
+    {
+        RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
+
+        EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
+        EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
+    }
+}
+
+}
+}
diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp
index 0c5e5e7..d6192df 100644
--- a/libs/hwui/unit_tests/ClipAreaTests.cpp
+++ b/libs/hwui/unit_tests/ClipAreaTests.cpp
@@ -101,10 +101,9 @@
     EXPECT_FALSE(area.isEmpty());
     EXPECT_FALSE(area.isSimple());
     EXPECT_FALSE(area.isRectangleList());
+
     Rect clipRect(area.getClipRect());
-    clipRect.dump("clipRect");
     Rect expected(0, 0, r * 2, r * 2);
-    expected.dump("expected");
     EXPECT_EQ(expected, clipRect);
     SkRegion clipRegion(area.getClipRegion());
     auto skRect(clipRegion.getBounds());
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
new file mode 100644
index 0000000..af9c3f2
--- /dev/null
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -0,0 +1,373 @@
+/*
+ * 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 <BakedOpState.h>
+#include <OpReorderer.h>
+#include <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <unit_tests/TestUtils.h>
+
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * 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
+ * startLayer fails by default - tests should override if expected
+ * 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&) { ADD_FAILURE(); }
+    class Client {
+    public:
+        virtual ~Client() {};
+        MAP_OPS(CLIENT_METHOD)
+
+        virtual Layer* startLayer(Arg& info, uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
+        virtual void endLayer(Arg& info) { ADD_FAILURE(); }
+        virtual void startFrame(Arg& info, uint32_t width, uint32_t height) {}
+        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 Layer* startLayer(Arg& info, uint32_t width, uint32_t height) {
+        CustomClient client;
+        return client.startLayer(info, width, height);
+    }
+    static void endLayer(Arg& info) {
+        CustomClient client;
+        client.endLayer(info);
+    }
+    static void startFrame(Arg& info, uint32_t width, uint32_t height) {
+        CustomClient client;
+        client.startFrame(info, width, height);
+    }
+    static void endFrame(Arg& info) {
+        CustomClient client;
+        client.endFrame(info);
+    }
+};
+
+class Info {
+public:
+    int index = 0;
+};
+
+// 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:
+    void startFrame(Info& info, uint32_t width, uint32_t height) override {
+        EXPECT_EQ(0, info.index++);
+        EXPECT_EQ(100u, width);
+        EXPECT_EQ(200u, height);
+    }
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, info.index++);
+    }
+    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 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(100, 200, *dl);
+
+    Info 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(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+}
+
+
+static int SIMPLE_BATCHING_LOOPS = 5;
+class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
+public:
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        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);
+    }
+};
+TEST(OpReorderer, simpleBatching) {
+    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.
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
+            canvas.translate(0, 10);
+            canvas.drawRect(0, 0, 10, 10, SkPaint());
+            canvas.drawBitmap(bitmap, 5, 0, nullptr);
+        }
+        canvas.restore();
+    });
+
+    OpReorderer reorderer(200, 200, *dl);
+
+    Info 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 : public TestReceiver<RenderNodeReceiver, Info>::Client {
+public:
+    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());
+            break;
+        case 1:
+            EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
+            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+            break;
+        default:
+            ADD_FAILURE();
+        }
+    }
+};
+TEST(OpReorderer, renderNode) {
+    sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    });
+
+    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);
+
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(40, 40);
+        canvas.drawRenderNode(childPtr);
+        canvas.restore();
+    });
+
+    TestUtils::syncNodePropertiesAndDisplayList(child);
+    TestUtils::syncNodePropertiesAndDisplayList(parent);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(parent.get());
+
+    OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+    Info 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(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:
+    Layer* startLayer(Info& info, uint32_t width, uint32_t height) override {
+        EXPECT_EQ(0, info.index++);
+        EXPECT_EQ(180u, width);
+        EXPECT_EQ(180u, height);
+        return nullptr;
+    }
+    void endLayer(Info& info) override {
+        EXPECT_EQ(2, info.index++);
+    }
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, 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(3, 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(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
+    EXPECT_EQ(4, info.index);
+}
+
+
+/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
+ * - startLayer2, rect2 endLayer2
+ * - startLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
+class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+public:
+    Layer* startLayer(Info& info, uint32_t width, uint32_t height) override {
+        const int index = info.index++;
+        if (index == 0) {
+            EXPECT_EQ(400u, width);
+            EXPECT_EQ(400u, height);
+            return (Layer*) 0x400;
+        } else if (index == 3) {
+            EXPECT_EQ(800u, width);
+            EXPECT_EQ(800u, height);
+            return (Layer*) 0x800;
+        } else { ADD_FAILURE(); }
+        return (Layer*) nullptr;
+    }
+    void endLayer(Info& info) override {
+        int index = info.index++;
+        EXPECT_TRUE(index == 2 || index == 6);
+    }
+    void startFrame(Info& info, uint32_t width, uint32_t height) override {
+        EXPECT_EQ(7, info.index++);
+    }
+    void endFrame(Info& info) override {
+        EXPECT_EQ(9, info.index++);
+    }
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 1) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+        } else if (index == 4) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+        } else { ADD_FAILURE(); }
+    }
+    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 5) {
+            EXPECT_EQ((Layer*)0x400, *op.layerHandle);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+        } else if (index == 8) {
+            EXPECT_EQ((Layer*)0x800, *op.layerHandle);
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+        } else { ADD_FAILURE(); }
+    }
+};
+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(800, 800, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
+    EXPECT_EQ(10, 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(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
new file mode 100644
index 0000000..e8cdf46
--- /dev/null
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -0,0 +1,231 @@
+/*
+ * 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 <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+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++) {
+            RecordedOp* op = displayList.getOps()[opIndex];
+            opReceiver(*op);
+        }
+    }
+}
+
+TEST(RecordingCanvas, emptyPlayback) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.restore();
+    });
+    playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
+}
+
+TEST(RecordingCanvas, testSimpleRectRecord) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        canvas.drawRect(10, 20, 90, 180, SkPaint());
+    });
+
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        count++;
+        ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+        ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+        ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+    });
+    ASSERT_EQ(1, count); // only one observed
+}
+
+TEST(RecordingCanvas, backgroundAndImage) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkBitmap bitmap;
+        bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
+        SkPaint paint;
+        paint.setColor(SK_ColorBLUE);
+
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        {
+            // a background!
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.drawRect(0, 0, 100, 200, paint);
+            canvas.restore();
+        }
+        {
+            // an image!
+            canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+            canvas.translate(25, 25);
+            canvas.scale(2, 2);
+            canvas.drawBitmap(bitmap, 0, 0, nullptr);
+            canvas.restore();
+        }
+        canvas.restore();
+    });
+
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count == 0) {
+            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+            ASSERT_NE(nullptr, op.paint);
+            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+
+            Matrix4 expectedMatrix;
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        } else {
+            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
+            EXPECT_EQ(nullptr, op.paint);
+            EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds);
+            EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+
+            Matrix4 expectedMatrix;
+            expectedMatrix.loadTranslate(25, 25, 0);
+            expectedMatrix.scale(2, 2, 1);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+        count++;
+    });
+    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:
+            ADD_FAILURE();
+        }
+    });
+    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
new file mode 100644
index 0000000..99ecc9b
--- /dev/null
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -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.
+ */
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include <Matrix.h>
+#include <Rect.h>
+#include <RenderNode.h>
+#include <renderstate/RenderState.h>
+#include <renderthread/RenderThread.h>
+#include <Snapshot.h>
+
+#include <memory>
+
+namespace android {
+namespace uirenderer {
+
+#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) {
+        for (int i = 0; i < 16; i++) {
+            if (!MathUtils::areEqual(a[i], b[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) {
+        std::unique_ptr<Snapshot> snapshot(new Snapshot());
+        snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op);
+        *(snapshot->transform) = transform;
+        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<DisplayList> createDisplayList(int width, int height,
+            std::function<void(CanvasType& canvas)> canvasCallback) {
+        CanvasType canvas(width, height);
+        canvasCallback(canvas);
+        return std::unique_ptr<DisplayList>(canvas.finishRecording());
+    }
+
+    template<class CanvasType>
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            std::function<void(CanvasType& canvas)> canvasCallback) {
+        sp<RenderNode> node = new RenderNode();
+        node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
+        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        CanvasType canvas(
+                node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
+        canvasCallback(canvas);
+        node->setStagingDisplayList(canvas.finishRecording());
+        return node;
+    }
+
+    static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
+        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 */
+} /* namespace android */
+
+#endif /* TEST_UTILS_H */
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/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 019e5d3..db53713 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -19,6 +19,8 @@
 #include <utils/Blur.h>
 
 #include <SkColorFilter.h>
+#include <SkDrawLooper.h>
+#include <SkShader.h>
 #include <SkXfermode.h>
 
 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/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index 2f4d136..1fee587 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -52,7 +52,7 @@
         "/system/media/audio/ui/camera_click.ogg",
         "/system/media/audio/ui/camera_focus.ogg",
         "/system/media/audio/ui/VideoRecord.ogg",
-        "/system/media/audio/ui/VideoRecord.ogg"
+        "/system/media/audio/ui/VideoStop.ogg"
     };
 
     private static final String TAG = "MediaActionSound";
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index bc1aeda..1c2c940 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -479,11 +479,6 @@
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
  *         the object state. </p></td></tr>
- * <tr><td>setPlaybackRate</p></td>
- *     <td>any </p></td>
- *     <td>{} </p></td>
- *     <td>This method can be called in any state and calling it does not change
- *         the object state. </p></td></tr>
  * <tr><td>setPlaybackParams</p></td>
  *     <td>any </p></td>
  *     <td>{} </p></td>
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index ed2c4cbd..8ac86b0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -807,6 +807,32 @@
     public native void stop() throws IllegalStateException;
 
     /**
+     * Pauses recording. Call this after start(). You may resume recording
+     * with resume() without reconfiguration, as opposed to stop(). It does
+     * nothing if the recording is already paused.
+     *
+     * When the recording is paused and resumed, the resulting output would
+     * be as if nothing happend during paused period, immediately switching
+     * to the resumed scene.
+     *
+     * @throws IllegalStateException if it is called before start() or after
+     * stop()
+     * {@hide}
+     */
+    public native void pause() throws IllegalStateException;
+
+    /**
+     * Resumes recording. Call this after start(). It does nothing if the
+     * recording is not paused.
+     *
+     * @throws IllegalStateException if it is called before start() or after
+     * stop()
+     * @see android.media.MediaRecorder#pause
+     * {@hide}
+     */
+    public native void resume() throws IllegalStateException;
+
+    /**
      * Restarts the MediaRecorder to its idle state. After calling
      * this method, you will have to configure it again as if it had just been
      * constructed.
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45fb579..93dbf3f 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -180,7 +180,8 @@
             Log.e(TAG, "could not open device " + info);
         } else {
             ...
-        }, new Handler(Looper.getMainLooper())
+        }
+    }, new Handler(Looper.getMainLooper())
     );
 </pre>
 
diff --git a/media/java/android/mtp/MtpEvent.java b/media/java/android/mtp/MtpEvent.java
index 0fa7d93..4c8a742 100644
--- a/media/java/android/mtp/MtpEvent.java
+++ b/media/java/android/mtp/MtpEvent.java
@@ -18,13 +18,33 @@
 
 /**
  * This class encapsulates information about a MTP event.
+ * Event constants are defined by the USB-IF MTP specification.
  */
 public class MtpEvent {
-    private int mEventCode;
+    public static final int EVENT_UNDEFINED = 0x4000;
+    public static final int EVENT_CANCEL_TRANSACTION = 0x4001;
+    public static final int EVENT_OBJECT_ADDED = 0x4002;
+    public static final int EVENT_OBJECT_REMOVED = 0x4003;
+    public static final int EVENT_STORE_ADDED = 0x4004;
+    public static final int EVENT_STORE_REMOVED = 0x4005;
+    public static final int EVENT_DEVICE_PROP_CHANGED = 0x4006;
+    public static final int EVENT_OBJECT_INFO_CHANGED = 0x4007;
+    public static final int EVENT_DEVICE_INFO_CHANGED = 0x4008;
+    public static final int EVENT_REQUEST_OBJECT_TRANSFER = 0x4009;
+    public static final int EVENT_STORE_FULL = 0x400A;
+    public static final int EVENT_DEVICE_RESET = 0x400B;
+    public static final int EVENT_STORAGE_INFO_CHANGED = 0x400C;
+    public static final int EVENT_CAPTURE_COMPLETE = 0x400D;
+    public static final int EVENT_UNREPORTED_STATUS = 0x400E;
+    public static final int EVENT_OBJECT_PROP_CHANGED = 0xC801;
+    public static final int EVENT_OBJECT_PROP_DESC_CHANGED = 0xC802;
+    public static final int EVENT_OBJECT_REFERENCES_CHANGED = 0xC803;
+
+    private int mEventCode = EVENT_UNDEFINED;
 
     /**
      * Returns event code of MTP event.
-     *
+     * See the USB-IF MTP specification for the details of event constants.
      * @return event code
      */
     public int getEventCode() { return mEventCode; }
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index e05b348..701f7ac 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -399,6 +399,22 @@
 }
 
 static void
+android_media_MediaRecorder_pause(JNIEnv *env, jobject thiz)
+{
+    ALOGV("pause");
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    process_media_recorder_call(env, mr->pause(), "java/lang/RuntimeException", "pause failed.");
+}
+
+static void
+android_media_MediaRecorder_resume(JNIEnv *env, jobject thiz)
+{
+    ALOGV("resume");
+    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+    process_media_recorder_call(env, mr->resume(), "java/lang/RuntimeException", "resume failed.");
+}
+
+static void
 android_media_MediaRecorder_native_reset(JNIEnv *env, jobject thiz)
 {
     ALOGV("native_reset");
@@ -528,6 +544,8 @@
     {"getMaxAmplitude",      "()I",                             (void *)android_media_MediaRecorder_native_getMaxAmplitude},
     {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
     {"stop",                 "()V",                             (void *)android_media_MediaRecorder_stop},
+    {"pause",                "()V",                             (void *)android_media_MediaRecorder_pause},
+    {"resume",               "()V",                             (void *)android_media_MediaRecorder_resume},
     {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
     {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
     {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
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/BackupRestoreConfirmation/res/values-ro/strings.xml b/packages/BackupRestoreConfirmation/res/values-ro/strings.xml
index 6a7dbbd..b762059 100644
--- a/packages/BackupRestoreConfirmation/res/values-ro/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-ro/strings.xml
@@ -35,5 +35,5 @@
     <string name="toast_backup_ended" msgid="3818080769548726424">"Copierea de rezervă a fost finalizată"</string>
     <string name="toast_restore_started" msgid="7881679218971277385">"Se porneşte restabilirea..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"Restabilirea s-a încheiat"</string>
-    <string name="toast_timeout" msgid="5276598587087626877">"Operaţia a expirat"</string>
+    <string name="toast_timeout" msgid="5276598587087626877">"Operația a expirat"</string>
 </resources>
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 295cb80..f9e8027 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -3,7 +3,7 @@
 
     <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
     <uses-permission android:name="android.permission.REMOVE_TASKS" />
-    <uses-permission android:name="android.permission.REORDER_TASKS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application
         android:name=".DocumentsApplication"
diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
index 6dcbb38..ab414a9 100644
--- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
@@ -16,6 +16,10 @@
 
 <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" />
@@ -25,4 +29,4 @@
         android:alpha="0.5" />
     <item
         android:color="@android:color/transparent" />
-</selector>
\ No newline at end of file
+</selector>
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/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 45cb34d..4019d02 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -68,7 +68,7 @@
         android:layout_height="match_parent">
 
         <android.support.v7.widget.RecyclerView
-            android:id="@+id/recyclerView"
+            android:id="@+id/list"
             android:scrollbars="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index c576669..eba00a6 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.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"
@@ -131,4 +137,4 @@
 
     </LinearLayout>
 
-</FrameLayout>
+</com.android.documentsui.ListItem>
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-af/strings.xml b/packages/DocumentsUI/res/values-af/strings.xml
index c2b6dbc..3daecc7 100644
--- a/packages/DocumentsUI/res/values-af/strings.xml
+++ b/packages/DocumentsUI/res/values-af/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Lêers"</string>
     <string name="title_open" msgid="4353228937663917801">"Maak oop vanuit"</string>
     <string name="title_save" msgid="2433679664882857999">"Stoor na"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Skep vouer"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nuwe vouer"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Roosteraansig"</string>
     <string name="menu_list" msgid="7279285939892417279">"Lysaansig"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sorteer volgens"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Kies alles"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopieer na …"</string>
     <string name="menu_move" msgid="1828090633118079817">"Skuif na …"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nuwe venster"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopieer"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Plak"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Wys interne berging"</string>
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-am/strings.xml b/packages/DocumentsUI/res/values-am/strings.xml
index b82fa93..43db786 100644
--- a/packages/DocumentsUI/res/values-am/strings.xml
+++ b/packages/DocumentsUI/res/values-am/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ፋይሎች"</string>
     <string name="title_open" msgid="4353228937663917801">"ክፈት ከ"</string>
     <string name="title_save" msgid="2433679664882857999">"አስቀምጥ ወደ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"አቃፊ ፍጠር"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"አዲስ አቃፊ"</string>
     <string name="menu_grid" msgid="6878021334497835259">"የፍርግርግ እይታ"</string>
     <string name="menu_list" msgid="7279285939892417279">"የዝርዝር እይታ"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ደርድር በ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ሁሉንም ምረጥ"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ቅዳ ወደ…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ይውሰዱ ወደ..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"አዲሰ መስኮት"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ቅዳ"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ለጥፍ"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"ውስጣዊ ማከማቻ አሳይ"</string>
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-ar/strings.xml b/packages/DocumentsUI/res/values-ar/strings.xml
index c7c9ef2..88c42d03 100644
--- a/packages/DocumentsUI/res/values-ar/strings.xml
+++ b/packages/DocumentsUI/res/values-ar/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"الملفات"</string>
     <string name="title_open" msgid="4353228937663917801">"فتح من"</string>
     <string name="title_save" msgid="2433679664882857999">"حفظ في"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"إنشاء مجلد"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"مجلد جديد"</string>
     <string name="menu_grid" msgid="6878021334497835259">"عرض الشبكة"</string>
     <string name="menu_list" msgid="7279285939892417279">"عرض القائمة"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ترتيب بحسب"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"تحديد الكل"</string>
     <string name="menu_copy" msgid="3612326052677229148">"نسخ إلى…"</string>
     <string name="menu_move" msgid="1828090633118079817">"نقل إلى..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"نافذة جديدة"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"نسخ"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"لصق"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"إظهار وحدة التخزين الداخلية"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"نسخ"</string>
     <string name="button_move" msgid="2202666023104202232">"نقل"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"إزالة"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"إعادة المحاولة"</string>
     <string name="sort_name" msgid="9183560467917256779">"بحسب الاسم"</string>
     <string name="sort_date" msgid="586080032956151448">"بحسب تاريخ التعديل"</string>
     <string name="sort_size" msgid="3350681319735474741">"بحسب الحجم"</string>
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-az-rAZ/strings.xml b/packages/DocumentsUI/res/values-az-rAZ/strings.xml
index 8c79ceb..f91f2ee 100644
--- a/packages/DocumentsUI/res/values-az-rAZ/strings.xml
+++ b/packages/DocumentsUI/res/values-az-rAZ/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fayllar"</string>
     <string name="title_open" msgid="4353228937663917801">"Vasitəsilə açın"</string>
     <string name="title_save" msgid="2433679664882857999">"buraya saxlayın"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Qovluq yaradın"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Yeni qovluq"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Torlu görünüş"</string>
     <string name="menu_list" msgid="7279285939892417279">"Siyahı görünüşü"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Bunlardan biri üzrə sırala"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Hamısını seçin"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Buraya kopyalayın:"</string>
     <string name="menu_move" msgid="1828090633118079817">"Köçürün…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Yeni pəncərə"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopyalayın"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Yerləşdirin"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Daxili yaddaşı göstərin"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopyala"</string>
     <string name="button_move" msgid="2202666023104202232">"Köçürün"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Rədd edin"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Yenidən cəhd edin"</string>
     <string name="sort_name" msgid="9183560467917256779">"Ad üzrə"</string>
     <string name="sort_date" msgid="586080032956151448">"Tarix üzrə dəyişmiş"</string>
     <string name="sort_size" msgid="3350681319735474741">"Ölçü üzrə"</string>
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-bg/strings.xml b/packages/DocumentsUI/res/values-bg/strings.xml
index fdf57d0..67a1c6a 100644
--- a/packages/DocumentsUI/res/values-bg/strings.xml
+++ b/packages/DocumentsUI/res/values-bg/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файлове"</string>
     <string name="title_open" msgid="4353228937663917801">"Отваряне от"</string>
     <string name="title_save" msgid="2433679664882857999">"Запазване във:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Създаване на папка"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Нова папка"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Изглед в мрежа"</string>
     <string name="menu_list" msgid="7279285939892417279">"Списъчен изглед"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Сортиране по"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Избиране на всичко"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Копиране във…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Преместване във…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Нов прозорец"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Копиране"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Поставяне"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Вътр. хранилище: Показв."</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Копиране"</string>
     <string name="button_move" msgid="2202666023104202232">"Преместване"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Отхвърляне"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Нов опит"</string>
     <string name="sort_name" msgid="9183560467917256779">"По име"</string>
     <string name="sort_date" msgid="586080032956151448">"По дата на промяната"</string>
     <string name="sort_size" msgid="3350681319735474741">"По размер"</string>
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-bn-rBD/strings.xml b/packages/DocumentsUI/res/values-bn-rBD/strings.xml
index 7caf57f..b01cc84e 100644
--- a/packages/DocumentsUI/res/values-bn-rBD/strings.xml
+++ b/packages/DocumentsUI/res/values-bn-rBD/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ফাইলগুলি"</string>
     <string name="title_open" msgid="4353228937663917801">"এখান থেকে খুলুন"</string>
     <string name="title_save" msgid="2433679664882857999">"এতে সংরক্ষণ করুন"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ফোল্ডার তৈরি করুন"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"নতুন ফোল্ডার"</string>
     <string name="menu_grid" msgid="6878021334497835259">"গ্রিড দৃশ্য"</string>
     <string name="menu_list" msgid="7279285939892417279">"তালিকা দৃশ্য"</string>
     <string name="menu_sort" msgid="7677740407158414452">"এই অনুসারে বাছুন"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"সবগুলি নির্বাচন করুন"</string>
     <string name="menu_copy" msgid="3612326052677229148">"এতে অনুলিপি করুন…"</string>
     <string name="menu_move" msgid="1828090633118079817">"এতে সরান..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"নতুন উইন্ডো"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"প্রতিলিপি করুন"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"আটকান"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"অভ্যন্তরীণ সঞ্চয়স্থান দেখান"</string>
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-ca/strings.xml b/packages/DocumentsUI/res/values-ca/strings.xml
index ab365ce..663608b 100644
--- a/packages/DocumentsUI/res/values-ca/strings.xml
+++ b/packages/DocumentsUI/res/values-ca/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fitxers"</string>
     <string name="title_open" msgid="4353228937663917801">"Obre des de"</string>
     <string name="title_save" msgid="2433679664882857999">"Desa a"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Crea una carpeta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Carpeta nova"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Visualització de quadrícula"</string>
     <string name="menu_list" msgid="7279285939892417279">"Visualització de llista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordena per"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Selecciona-ho tot"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copia a…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mou a..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Finestra nova"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copia"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Enganxa"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostra emmagatz. intern"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Copia"</string>
     <string name="button_move" msgid="2202666023104202232">"Desplaça"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Ignora"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Torna-ho a provar"</string>
     <string name="sort_name" msgid="9183560467917256779">"Per nom"</string>
     <string name="sort_date" msgid="586080032956151448">"Per data de modificació"</string>
     <string name="sort_size" msgid="3350681319735474741">"Per mida"</string>
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-cs/strings.xml b/packages/DocumentsUI/res/values-cs/strings.xml
index 62b313c..81d8dc8 100644
--- a/packages/DocumentsUI/res/values-cs/strings.xml
+++ b/packages/DocumentsUI/res/values-cs/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Soubory"</string>
     <string name="title_open" msgid="4353228937663917801">"Otevřít"</string>
     <string name="title_save" msgid="2433679664882857999">"Uložit do"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Vytvořit složku"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nová složka"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Mřížkové zobrazení"</string>
     <string name="menu_list" msgid="7279285939892417279">"Zobrazení seznamu"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Řadit podle"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Vybrat vše"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopírovat do…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Přesunout do…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nové okno"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopírovat"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Vložit"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Zobrazit inter. úložiště"</string>
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-da/strings.xml b/packages/DocumentsUI/res/values-da/strings.xml
index c3d7cdd..e109048 100644
--- a/packages/DocumentsUI/res/values-da/strings.xml
+++ b/packages/DocumentsUI/res/values-da/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Filer"</string>
     <string name="title_open" msgid="4353228937663917801">"Åbn fra"</string>
     <string name="title_save" msgid="2433679664882857999">"Gem i"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Opret mappe"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Ny mappe"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Gittervisning"</string>
     <string name="menu_list" msgid="7279285939892417279">"Listevisning"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortér efter"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Markér alle"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiér til…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Flyt til…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nyt vindue"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiér"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Indsæt"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Vis intern lagerplads"</string>
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-de/strings.xml b/packages/DocumentsUI/res/values-de/strings.xml
index f88b5c4..7c467ef 100644
--- a/packages/DocumentsUI/res/values-de/strings.xml
+++ b/packages/DocumentsUI/res/values-de/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Dateien"</string>
     <string name="title_open" msgid="4353228937663917801">"Öffnen von"</string>
     <string name="title_save" msgid="2433679664882857999">"Speichern unter"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Ordner erstellen"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Neuer Ordner"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Rasteransicht"</string>
     <string name="menu_list" msgid="7279285939892417279">"Listenansicht"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortieren nach"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Alle auswählen"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopieren nach..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Verschieben nach…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Neues Fenster"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopieren"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Einfügen"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Int. Speicher anzeigen"</string>
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-el/strings.xml b/packages/DocumentsUI/res/values-el/strings.xml
index dcca46c..a350002 100644
--- a/packages/DocumentsUI/res/values-el/strings.xml
+++ b/packages/DocumentsUI/res/values-el/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Αρχεία"</string>
     <string name="title_open" msgid="4353228937663917801">"Άνοιγμα από"</string>
     <string name="title_save" msgid="2433679664882857999">"Αποθήκευση σε"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Δημιουργία φακέλου"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Νέος φάκελος"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Προβολή πλέγματος"</string>
     <string name="menu_list" msgid="7279285939892417279">"Προβολή λίστας"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ταξινόμηση κατά"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Επιλογή όλων"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Αντιγραφή σε…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Μετακίνηση σε..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Νέο παράθυρο"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Αντιγραφή"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Επικόλληση"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Εμφ.εσωτ.χώρου αποθήκ."</string>
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-rAU/strings.xml b/packages/DocumentsUI/res/values-en-rAU/strings.xml
index 26fd30f..e9ba34b 100644
--- a/packages/DocumentsUI/res/values-en-rAU/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rAU/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Files"</string>
     <string name="title_open" msgid="4353228937663917801">"Open from"</string>
     <string name="title_save" msgid="2433679664882857999">"Save to"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Create folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"New folder"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Grid view"</string>
     <string name="menu_list" msgid="7279285939892417279">"List view"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sort by"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Select all"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copy to…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Move to…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"New window"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copy"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Paste"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Show internal storage"</string>
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-rGB/strings.xml b/packages/DocumentsUI/res/values-en-rGB/strings.xml
index 26fd30f..e9ba34b 100644
--- a/packages/DocumentsUI/res/values-en-rGB/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rGB/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Files"</string>
     <string name="title_open" msgid="4353228937663917801">"Open from"</string>
     <string name="title_save" msgid="2433679664882857999">"Save to"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Create folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"New folder"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Grid view"</string>
     <string name="menu_list" msgid="7279285939892417279">"List view"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sort by"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Select all"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copy to…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Move to…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"New window"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copy"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Paste"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Show internal storage"</string>
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-en-rIN/strings.xml b/packages/DocumentsUI/res/values-en-rIN/strings.xml
index 26fd30f..e9ba34b 100644
--- a/packages/DocumentsUI/res/values-en-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-en-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Files"</string>
     <string name="title_open" msgid="4353228937663917801">"Open from"</string>
     <string name="title_save" msgid="2433679664882857999">"Save to"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Create folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"New folder"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Grid view"</string>
     <string name="menu_list" msgid="7279285939892417279">"List view"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sort by"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Select all"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copy to…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Move to…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"New window"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copy"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Paste"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Show internal storage"</string>
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-rUS/strings.xml b/packages/DocumentsUI/res/values-es-rUS/strings.xml
index 6e805b9..8e0a18cc 100644
--- a/packages/DocumentsUI/res/values-es-rUS/strings.xml
+++ b/packages/DocumentsUI/res/values-es-rUS/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Archivos"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir desde"</string>
     <string name="title_save" msgid="2433679664882857999">"Guardar en"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Crear carpeta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Carpeta nueva"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Vista de cuadrícula"</string>
     <string name="menu_list" msgid="7279285939892417279">"Vista de lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordenar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Seleccionar todo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar a…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover a…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Ventana nueva"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Pegar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar almacen. interno"</string>
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-es/strings.xml b/packages/DocumentsUI/res/values-es/strings.xml
index 8fa1d2e..cf0b607 100644
--- a/packages/DocumentsUI/res/values-es/strings.xml
+++ b/packages/DocumentsUI/res/values-es/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Archivos"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir desde"</string>
     <string name="title_save" msgid="2433679664882857999">"Guardar en"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Crear carpeta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nueva carpeta"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Vista de cuadrícula"</string>
     <string name="menu_list" msgid="7279285939892417279">"Vista de lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordenar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Seleccionar todo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar en…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover a…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nueva ventana"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Pegar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar almac. interno"</string>
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-et-rEE/strings.xml b/packages/DocumentsUI/res/values-et-rEE/strings.xml
index 7008885..76d1f48 100644
--- a/packages/DocumentsUI/res/values-et-rEE/strings.xml
+++ b/packages/DocumentsUI/res/values-et-rEE/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Failid"</string>
     <string name="title_open" msgid="4353228937663917801">"Ava:"</string>
     <string name="title_save" msgid="2433679664882857999">"Salvesta:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Loo kaust"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Uus kaust"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Ruudustikkuva"</string>
     <string name="menu_list" msgid="7279285939892417279">"Loendikuva"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortimisalus:"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Vali kõik"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopeeri asukohta ..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Teisaldamine kohta ..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Uus aken"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopeeri"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Kleebi"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Kuva sis. salvestusruum"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopeeri"</string>
     <string name="button_move" msgid="2202666023104202232">"Teisalda"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Loobu"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Proovi uuesti"</string>
     <string name="sort_name" msgid="9183560467917256779">"Nime järgi"</string>
     <string name="sort_date" msgid="586080032956151448">"Muutmiskuupäeva järgi"</string>
     <string name="sort_size" msgid="3350681319735474741">"Suuruse järgi"</string>
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-eu-rES/strings.xml b/packages/DocumentsUI/res/values-eu-rES/strings.xml
index 17d7c66..f24cf60 100644
--- a/packages/DocumentsUI/res/values-eu-rES/strings.xml
+++ b/packages/DocumentsUI/res/values-eu-rES/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fitxategiak"</string>
     <string name="title_open" msgid="4353228937663917801">"Ireki hemendik"</string>
     <string name="title_save" msgid="2433679664882857999">"Gorde hemen"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Sortu karpeta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Karpeta berria"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Sareta-ikuspegia"</string>
     <string name="menu_list" msgid="7279285939892417279">"Zerrenda-ikuspegia"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordenatzeko irizpidea"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Hautatu guztiak"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiatu hemen…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Eraman hona…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Leiho berria"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiatu"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Itsatsi"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Erakutsi barneko memoria"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopiatu"</string>
     <string name="button_move" msgid="2202666023104202232">"Mugitu"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Baztertu"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Saiatu berriro"</string>
     <string name="sort_name" msgid="9183560467917256779">"Izenaren arabera"</string>
     <string name="sort_date" msgid="586080032956151448">"Aldatze-dataren arabera"</string>
     <string name="sort_size" msgid="3350681319735474741">"Tamainaren arabera"</string>
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-fa/strings.xml b/packages/DocumentsUI/res/values-fa/strings.xml
index c4f6d58..b504db5 100644
--- a/packages/DocumentsUI/res/values-fa/strings.xml
+++ b/packages/DocumentsUI/res/values-fa/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"فایل‌ها"</string>
     <string name="title_open" msgid="4353228937663917801">"باز کردن از"</string>
     <string name="title_save" msgid="2433679664882857999">"ذخیره در"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ایجاد پوشه"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"پوشه جدید"</string>
     <string name="menu_grid" msgid="6878021334497835259">"نمای جدولی"</string>
     <string name="menu_list" msgid="7279285939892417279">"نمای فهرستی"</string>
     <string name="menu_sort" msgid="7677740407158414452">"مرتب‌سازی براساس"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"انتخاب همه"</string>
     <string name="menu_copy" msgid="3612326052677229148">"کپی در..."</string>
     <string name="menu_move" msgid="1828090633118079817">"انتقال به…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"پنجره جدید"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"کپی"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"جای‌گذاری"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"نمایش فضای ذخیره‌سازی داخلی"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"کپی"</string>
     <string name="button_move" msgid="2202666023104202232">"انتقال"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"نپذیرفتن"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"دوباره امتحان کنید"</string>
     <string name="sort_name" msgid="9183560467917256779">"براساس نام"</string>
     <string name="sort_date" msgid="586080032956151448">"براساس تاریخ اصلاح"</string>
     <string name="sort_size" msgid="3350681319735474741">"براساس اندازه"</string>
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-fi/strings.xml b/packages/DocumentsUI/res/values-fi/strings.xml
index 3e87300..33f2e3d 100644
--- a/packages/DocumentsUI/res/values-fi/strings.xml
+++ b/packages/DocumentsUI/res/values-fi/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Tiedostot"</string>
     <string name="title_open" msgid="4353228937663917801">"Avaa sijainnista"</string>
     <string name="title_save" msgid="2433679664882857999">"Tallenna kohteeseen"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Luo kansio"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Uusi kansio"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Ruudukkonäkymä"</string>
     <string name="menu_list" msgid="7279285939892417279">"Luettelonäkymä"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Lajitteluperuste"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Valitse kaikki"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopioi kohteeseen…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Siirrä kohteeseen…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Uusi ikkuna"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopioi"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Liitä"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Näytä sis. tallennustila"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopioi"</string>
     <string name="button_move" msgid="2202666023104202232">"Siirrä"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Hylkää"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Yritä uudelleen"</string>
     <string name="sort_name" msgid="9183560467917256779">"Nimen mukaan"</string>
     <string name="sort_date" msgid="586080032956151448">"Muokkauspäivän mukaan"</string>
     <string name="sort_size" msgid="3350681319735474741">"Koon mukaan"</string>
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-rCA/strings.xml b/packages/DocumentsUI/res/values-fr-rCA/strings.xml
index 3e82f34..228dc7a 100644
--- a/packages/DocumentsUI/res/values-fr-rCA/strings.xml
+++ b/packages/DocumentsUI/res/values-fr-rCA/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fichiers"</string>
     <string name="title_open" msgid="4353228937663917801">"Ouvrir à partir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Enregistrer dans"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Créer un dossier"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nouveau dossier"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Grille"</string>
     <string name="menu_list" msgid="7279285939892417279">"Liste"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Trier par"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Tout sélectionner"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copier vers..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Déplacer dans…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nouvelle fenêtre"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copier"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Coller"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Aff. mém. stock. interne"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Copier"</string>
     <string name="button_move" msgid="2202666023104202232">"Déplacer"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Faire disparaître"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Réessayer"</string>
     <string name="sort_name" msgid="9183560467917256779">"Par nom"</string>
     <string name="sort_date" msgid="586080032956151448">"Par date de modification"</string>
     <string name="sort_size" msgid="3350681319735474741">"Par taille"</string>
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-fr/strings.xml b/packages/DocumentsUI/res/values-fr/strings.xml
index 0512ab1..eb9f84a 100644
--- a/packages/DocumentsUI/res/values-fr/strings.xml
+++ b/packages/DocumentsUI/res/values-fr/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fichiers"</string>
     <string name="title_open" msgid="4353228937663917801">"Ouvrir à partir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Enregistrer sous"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Créer un dossier"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nouveau dossier"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Grille"</string>
     <string name="menu_list" msgid="7279285939892417279">"Liste"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Trier par"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Tout sélectionner"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copier vers…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Placer dans…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nouvelle fenêtre"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copier"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Coller"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Aff. mém. stock. interne"</string>
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-gl-rES/strings.xml b/packages/DocumentsUI/res/values-gl-rES/strings.xml
index a1cd614..07c5d39 100644
--- a/packages/DocumentsUI/res/values-gl-rES/strings.xml
+++ b/packages/DocumentsUI/res/values-gl-rES/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Ficheiros"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir desde"</string>
     <string name="title_save" msgid="2433679664882857999">"Gardar en"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Crear cartafol"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Novo cartafol"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Vista de grade"</string>
     <string name="menu_list" msgid="7279285939892417279">"Vista de lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordenar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Seleccionar todo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar en…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover a…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nova ventá"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Pegar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar almacen. interno"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Copiar"</string>
     <string name="button_move" msgid="2202666023104202232">"Mover"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Ignorar"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Tentar de novo"</string>
     <string name="sort_name" msgid="9183560467917256779">"Por nome"</string>
     <string name="sort_date" msgid="586080032956151448">"Por data de modificación"</string>
     <string name="sort_size" msgid="3350681319735474741">"Por tamaño"</string>
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-gu-rIN/strings.xml b/packages/DocumentsUI/res/values-gu-rIN/strings.xml
index 059fb2c..dade7df 100644
--- a/packages/DocumentsUI/res/values-gu-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-gu-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ફાઇલો"</string>
     <string name="title_open" msgid="4353228937663917801">"અહીંથી ખોલો"</string>
     <string name="title_save" msgid="2433679664882857999">"આમાં સાચવો"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ફોલ્ડર બનાવો"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"નવું ફોલ્ડર"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ગ્રિડ દૃશ્ય"</string>
     <string name="menu_list" msgid="7279285939892417279">"સૂચિ દૃશ્ય"</string>
     <string name="menu_sort" msgid="7677740407158414452">"આ પ્રમાણે સૉર્ટ કરો"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"બધા પસંદ કરો"</string>
     <string name="menu_copy" msgid="3612326052677229148">"આના પર કૉપિ કરો…"</string>
     <string name="menu_move" msgid="1828090633118079817">"આમાં ખસેડો…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"નવી વિંડો"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"કૉપિ કરો"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"પેસ્ટ કરો"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"આંતરિક સ્ટોરેજ બતાવો"</string>
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-hi/strings.xml b/packages/DocumentsUI/res/values-hi/strings.xml
index 8e33a00..0a5de16 100644
--- a/packages/DocumentsUI/res/values-hi/strings.xml
+++ b/packages/DocumentsUI/res/values-hi/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"फ़ाइलें"</string>
     <string name="title_open" msgid="4353228937663917801">"यहां से खोलें"</string>
     <string name="title_save" msgid="2433679664882857999">"यहां सहेजें"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"फ़ोल्डर बनाएं"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"नया फ़ोल्डर"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ग्रिड दृश्य"</string>
     <string name="menu_list" msgid="7279285939892417279">"सूची दृश्य"</string>
     <string name="menu_sort" msgid="7677740407158414452">"इससे क्रमित करें"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"सभी चुनें"</string>
     <string name="menu_copy" msgid="3612326052677229148">"इनकी कॉपी बनाएं..."</string>
     <string name="menu_move" msgid="1828090633118079817">"इसमें ले जाएं…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"नई विंडो"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"कॉपी करें"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"चिपकाएं"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"आंतरिक मेमोरी दिखाएं"</string>
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-hr/strings.xml b/packages/DocumentsUI/res/values-hr/strings.xml
index 4774753..78c1485 100644
--- a/packages/DocumentsUI/res/values-hr/strings.xml
+++ b/packages/DocumentsUI/res/values-hr/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Datoteke"</string>
     <string name="title_open" msgid="4353228937663917801">"Otvori iz"</string>
     <string name="title_save" msgid="2433679664882857999">"Spremi u"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Izradi mapu"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nova mapa"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Prikaz rešetke"</string>
     <string name="menu_list" msgid="7279285939892417279">"Prikaz popisa"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Poredano po"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Odaberi sve"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiraj u…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Premjesti u…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Novi prozor"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiraj"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Zalijepi"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Pokaži internu pohranu"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopiraj"</string>
     <string name="button_move" msgid="2202666023104202232">"Premjesti"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Odbaci"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Pokušaj ponovo"</string>
     <string name="sort_name" msgid="9183560467917256779">"Po nazivu"</string>
     <string name="sort_date" msgid="586080032956151448">"Po datumu izmjene"</string>
     <string name="sort_size" msgid="3350681319735474741">"Po veličini"</string>
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-hu/strings.xml b/packages/DocumentsUI/res/values-hu/strings.xml
index 7a521ec..4948116 100644
--- a/packages/DocumentsUI/res/values-hu/strings.xml
+++ b/packages/DocumentsUI/res/values-hu/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fájlok"</string>
     <string name="title_open" msgid="4353228937663917801">"Megnyitás innen"</string>
     <string name="title_save" msgid="2433679664882857999">"Mentés ide"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Mappa létrehozása"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Új mappa"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Rács"</string>
     <string name="menu_list" msgid="7279285939892417279">"Lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Rendezés"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Összes kijelölése"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Másolás ide…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Áthelyezés…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Új ablak"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Másolás"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Beillesztés"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Belső tárhely"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Másolás"</string>
     <string name="button_move" msgid="2202666023104202232">"Áthelyezés"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Elvetés"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Újrapróbálkozás"</string>
     <string name="sort_name" msgid="9183560467917256779">"Név szerint"</string>
     <string name="sort_date" msgid="586080032956151448">"Módosítás dátuma szerint"</string>
     <string name="sort_size" msgid="3350681319735474741">"Méret szerint"</string>
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-hy-rAM/strings.xml b/packages/DocumentsUI/res/values-hy-rAM/strings.xml
index 95d73f0..6dc66b0 100644
--- a/packages/DocumentsUI/res/values-hy-rAM/strings.xml
+++ b/packages/DocumentsUI/res/values-hy-rAM/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Ֆայլեր"</string>
     <string name="title_open" msgid="4353228937663917801">"Բացել այստեղից"</string>
     <string name="title_save" msgid="2433679664882857999">"Պահել այստեղ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Ստեղծել պանակ"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Նոր պանակ"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Ցանցի տեսքով"</string>
     <string name="menu_list" msgid="7279285939892417279">"Ցուցակի տեսք"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Դասավորել ըստ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Ընտրել բոլորը"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Պատճենել…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Տեղափոխում դեպի…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Նոր պատուհան"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Պատճենել"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Տեղադրել"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Ցույց տալ ներքին պահոցը"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Պատճենել"</string>
     <string name="button_move" msgid="2202666023104202232">"Տեղափոխել"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Փակել"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Փորձել նորից"</string>
     <string name="sort_name" msgid="9183560467917256779">"Ըստ անվան"</string>
     <string name="sort_date" msgid="586080032956151448">"Ըստ փոփոխման ամսաթվի"</string>
     <string name="sort_size" msgid="3350681319735474741">"Ըստ չափի"</string>
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-in/strings.xml b/packages/DocumentsUI/res/values-in/strings.xml
index 63d415c..2412488 100644
--- a/packages/DocumentsUI/res/values-in/strings.xml
+++ b/packages/DocumentsUI/res/values-in/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"File"</string>
     <string name="title_open" msgid="4353228937663917801">"Buka dari"</string>
     <string name="title_save" msgid="2433679664882857999">"Simpan ke"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Buat folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Folder baru"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Tampilan kisi"</string>
     <string name="menu_list" msgid="7279285939892417279">"Tampilan daftar"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Urutkan menurut"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Pilih semua"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Salin ke…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Pindahkan ke..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Jendela baru"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Salin"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Tempel"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Tampilkan simpanan internal"</string>
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-is-rIS/strings.xml b/packages/DocumentsUI/res/values-is-rIS/strings.xml
index 0c0e47f..d09a44b 100644
--- a/packages/DocumentsUI/res/values-is-rIS/strings.xml
+++ b/packages/DocumentsUI/res/values-is-rIS/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Skrár"</string>
     <string name="title_open" msgid="4353228937663917801">"Opna frá"</string>
     <string name="title_save" msgid="2433679664882857999">"Vista í"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Búa til möppu"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Ný mappa"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Töfluyfirlit"</string>
     <string name="menu_list" msgid="7279285939892417279">"Listayfirlit"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Raða eftir"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Velja allt"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Afrita í ..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Færa í…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nýr gluggi"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Afrita"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Líma"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Sýna innbyggða geymslu"</string>
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-it/strings.xml b/packages/DocumentsUI/res/values-it/strings.xml
index de129a7..4824205 100644
--- a/packages/DocumentsUI/res/values-it/strings.xml
+++ b/packages/DocumentsUI/res/values-it/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"File"</string>
     <string name="title_open" msgid="4353228937663917801">"Apri da"</string>
     <string name="title_save" msgid="2433679664882857999">"Salva in"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Crea cartella"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nuova cartella"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Visualizzazione griglia"</string>
     <string name="menu_list" msgid="7279285939892417279">"Visualizzazione elenco"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordina per"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Seleziona tutto"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copia in…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Sposta in..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nuova finestra"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copia"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Incolla"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostra memoria interna"</string>
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-iw/strings.xml b/packages/DocumentsUI/res/values-iw/strings.xml
index dbe4630..1a19510 100644
--- a/packages/DocumentsUI/res/values-iw/strings.xml
+++ b/packages/DocumentsUI/res/values-iw/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"קבצים"</string>
     <string name="title_open" msgid="4353228937663917801">"פתח מ-"</string>
     <string name="title_save" msgid="2433679664882857999">"שמור ב-"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"צור תיקיה"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"תיקייה חדשה"</string>
     <string name="menu_grid" msgid="6878021334497835259">"תצוגת רשת"</string>
     <string name="menu_list" msgid="7279285939892417279">"תצוגת רשימה"</string>
     <string name="menu_sort" msgid="7677740407158414452">"מיין לפי"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"בחר הכל"</string>
     <string name="menu_copy" msgid="3612326052677229148">"העתק אל…"</string>
     <string name="menu_move" msgid="1828090633118079817">"העברה אל…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"חלון חדש"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"העתק"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"הדבק"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"הצג אחסון פנימי"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"העתק"</string>
     <string name="button_move" msgid="2202666023104202232">"העבר"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"הסר"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"נסה שוב"</string>
     <string name="sort_name" msgid="9183560467917256779">"לפי שם"</string>
     <string name="sort_date" msgid="586080032956151448">"לפי תאריך שינוי"</string>
     <string name="sort_size" msgid="3350681319735474741">"לפי גודל"</string>
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-ja/strings.xml b/packages/DocumentsUI/res/values-ja/strings.xml
index 45f0ea1..7e356af 100644
--- a/packages/DocumentsUI/res/values-ja/strings.xml
+++ b/packages/DocumentsUI/res/values-ja/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ファイル"</string>
     <string name="title_open" msgid="4353228937663917801">"次から開く:"</string>
     <string name="title_save" msgid="2433679664882857999">"次に保存:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"フォルダを作成"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"新しいフォルダ"</string>
     <string name="menu_grid" msgid="6878021334497835259">"グリッド表示"</string>
     <string name="menu_list" msgid="7279285939892417279">"リスト表示"</string>
     <string name="menu_sort" msgid="7677740407158414452">"並べ替え"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"すべて選択"</string>
     <string name="menu_copy" msgid="3612326052677229148">"コピー…"</string>
     <string name="menu_move" msgid="1828090633118079817">"移動..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"新しいウィンドウ"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"コピー"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"貼り付け"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"内部ストレージを表示"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"コピー"</string>
     <string name="button_move" msgid="2202666023104202232">"移動"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"表示しない"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"再試行"</string>
     <string name="sort_name" msgid="9183560467917256779">"名前順"</string>
     <string name="sort_date" msgid="586080032956151448">"更新日順"</string>
     <string name="sort_size" msgid="3350681319735474741">"サイズ順"</string>
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-ka-rGE/strings.xml b/packages/DocumentsUI/res/values-ka-rGE/strings.xml
index 114e73c..6e880aa 100644
--- a/packages/DocumentsUI/res/values-ka-rGE/strings.xml
+++ b/packages/DocumentsUI/res/values-ka-rGE/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ფაილები"</string>
     <string name="title_open" msgid="4353228937663917801">"გახსნა აქედან:"</string>
     <string name="title_save" msgid="2433679664882857999">"შენახვა აქ:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"საქაღალდის შექმნა"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ახალი საქაღალდე"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ბადის ხედი"</string>
     <string name="menu_list" msgid="7279285939892417279">"სიის ხედი"</string>
     <string name="menu_sort" msgid="7677740407158414452">"სორტირება:"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ყველას არჩევა"</string>
     <string name="menu_copy" msgid="3612326052677229148">"კოპირება…"</string>
     <string name="menu_move" msgid="1828090633118079817">"გადაადგილება..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"ახალი ფანჯარა"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"კოპირება"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ჩასმა"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"შიდა საცავის ჩვენება"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"კოპირება"</string>
     <string name="button_move" msgid="2202666023104202232">"გადაადგილება"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"დახურვა"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"ისევ ცდა"</string>
     <string name="sort_name" msgid="9183560467917256779">"სახელით"</string>
     <string name="sort_date" msgid="586080032956151448">"ცვლილების თარიღით"</string>
     <string name="sort_size" msgid="3350681319735474741">"ზომით"</string>
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-kk-rKZ/strings.xml b/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
index af123c6..0d553e0 100644
--- a/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
+++ b/packages/DocumentsUI/res/values-kk-rKZ/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файлдар"</string>
     <string name="title_open" msgid="4353228937663917801">"Мынадан ашу:"</string>
     <string name="title_save" msgid="2433679664882857999">"Сақталатын орны"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Қалта жасақтау"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Жаңа қалта"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Торлы көрініс"</string>
     <string name="menu_list" msgid="7279285939892417279">"Тізім көрінісі"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Белгіге қарай сұрыптау"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Барлығын таңдау"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Көшіру орны…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Орнын ауыстыру…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Жаңа терезе"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Көшіру"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Қою"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Ішкі жадты көрсету"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Көшіру"</string>
     <string name="button_move" msgid="2202666023104202232">"Жылжыту"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Өшіру"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Әрекетті қайталау"</string>
     <string name="sort_name" msgid="9183560467917256779">"Атауы бойынша"</string>
     <string name="sort_date" msgid="586080032956151448">"Өзгертілген мерзімі бойынша"</string>
     <string name="sort_size" msgid="3350681319735474741">"Өлшемі бойынша"</string>
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-km-rKH/strings.xml b/packages/DocumentsUI/res/values-km-rKH/strings.xml
index 8f3feae..4680879 100644
--- a/packages/DocumentsUI/res/values-km-rKH/strings.xml
+++ b/packages/DocumentsUI/res/values-km-rKH/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ឯកសារ"</string>
     <string name="title_open" msgid="4353228937663917801">"បើក​ពី"</string>
     <string name="title_save" msgid="2433679664882857999">"រក្សា​ទុក​ទៅ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"បង្កើត​ថត"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ថត​ថ្មី"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ទិដ្ឋភាព​ក្រឡា​"</string>
     <string name="menu_list" msgid="7279285939892417279">"ទិដ្ឋភាព​បញ្ជី"</string>
     <string name="menu_sort" msgid="7677740407158414452">"តម្រៀប​តាម"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ជ្រើសរើសទាំងអស់"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ថតចម្លងទៅ…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ផ្លាស់ទីទៅ៖"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"បង្អួចថ្មី"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ចម្លង"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"បិទភ្ជាប់"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"បង្ហាញឧបករណ៍ផ្ទុកខាងក្នុង"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"ចម្លង"</string>
     <string name="button_move" msgid="2202666023104202232">"ផ្លាស់ទី"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"បដិសេធ"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"ព្យាយាមម្ដងទៀត"</string>
     <string name="sort_name" msgid="9183560467917256779">"តាម​ឈ្មោះ"</string>
     <string name="sort_date" msgid="586080032956151448">"តាម​កាលបរិច្ឆេទ​បាន​កែប្រែ"</string>
     <string name="sort_size" msgid="3350681319735474741">"តាម​​ទំហំ"</string>
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-kn-rIN/strings.xml b/packages/DocumentsUI/res/values-kn-rIN/strings.xml
index 826cf1e..7d5770a 100644
--- a/packages/DocumentsUI/res/values-kn-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-kn-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ಫೈಲ್‌ಗಳು"</string>
     <string name="title_open" msgid="4353228937663917801">"ಇದರ ಮೂಲಕ ತೆರೆಯಿರಿ"</string>
     <string name="title_save" msgid="2433679664882857999">"ಇವುಗಳಲ್ಲಿ ಉಳಿಸಿ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ಫೋಲ್ಡರ್ ರಚಿಸು"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ಹೊಸ ಫೋಲ್ಡರ್"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ಗ್ರಿಡ್ ವೀಕ್ಷಣೆ"</string>
     <string name="menu_list" msgid="7279285939892417279">"ಪಟ್ಟಿ ವೀಕ್ಷಣೆ"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ಈ ಪ್ರಕಾರ ವಿಂಗಡಿಸು"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ಎಲ್ಲವನ್ನೂ ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ಇದಕ್ಕೆ ನಕಲಿಸಿ…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ಇದಕ್ಕೆ ಸರಿಸು…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"ಹೊಸ ವಿಂಡೋ"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ನಕಲಿಸು"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ಅಂಟಿಸು"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"ಆಂತರಿಕ ಸಂಗ್ರಹಣೆಯನ್ನು ತೋರಿಸು"</string>
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-ko/strings.xml b/packages/DocumentsUI/res/values-ko/strings.xml
index 322b43b..3032eed 100644
--- a/packages/DocumentsUI/res/values-ko/strings.xml
+++ b/packages/DocumentsUI/res/values-ko/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"파일"</string>
     <string name="title_open" msgid="4353228937663917801">"열기:"</string>
     <string name="title_save" msgid="2433679664882857999">"저장 위치:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"폴더 만들기"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"새 폴더"</string>
     <string name="menu_grid" msgid="6878021334497835259">"바둑판식 보기"</string>
     <string name="menu_list" msgid="7279285939892417279">"목록 보기"</string>
     <string name="menu_sort" msgid="7677740407158414452">"정렬 기준"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"모두 선택"</string>
     <string name="menu_copy" msgid="3612326052677229148">"복사…"</string>
     <string name="menu_move" msgid="1828090633118079817">"이동…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"새 창"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"복사"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"붙여넣기"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"내부 저장소 표시"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"복사"</string>
     <string name="button_move" msgid="2202666023104202232">"이동"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"닫기"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"다시 시도"</string>
     <string name="sort_name" msgid="9183560467917256779">"이름순"</string>
     <string name="sort_date" msgid="586080032956151448">"수정된 날짜순"</string>
     <string name="sort_size" msgid="3350681319735474741">"크기순"</string>
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-ky-rKG/strings.xml b/packages/DocumentsUI/res/values-ky-rKG/strings.xml
index c8aa69e..14a25bc 100644
--- a/packages/DocumentsUI/res/values-ky-rKG/strings.xml
+++ b/packages/DocumentsUI/res/values-ky-rKG/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файлдар"</string>
     <string name="title_open" msgid="4353228937663917801">"Кийинкиден ачуу:"</string>
     <string name="title_save" msgid="2433679664882857999">"Кийинкиге сактоо:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Папка түзүү"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Жаңы куржун"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Тор көрүнүшү"</string>
     <string name="menu_list" msgid="7279285939892417279">"Тизмек көрүнүшү"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ылгоо"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Бардыгын тандоо"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Төмөнкүгө көчүрүү…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Төмөнкүгө жылдыруу..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Жаңы терезе"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Көчүрүү"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Чаптоо"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Ички сактагычты көрсөтүү"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Көчүрүү"</string>
     <string name="button_move" msgid="2202666023104202232">"Жылдыруу"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Этибарга албоо"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Дагы аракет кылыңыз"</string>
     <string name="sort_name" msgid="9183560467917256779">"Аты боюнча"</string>
     <string name="sort_date" msgid="586080032956151448">"Өзгөртүлгөн күнү боюнча"</string>
     <string name="sort_size" msgid="3350681319735474741">"Өлчөмү боюнча"</string>
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-lo-rLA/strings.xml b/packages/DocumentsUI/res/values-lo-rLA/strings.xml
index dedfa05..2a680bf 100644
--- a/packages/DocumentsUI/res/values-lo-rLA/strings.xml
+++ b/packages/DocumentsUI/res/values-lo-rLA/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"​ໄຟລ໌"</string>
     <string name="title_open" msgid="4353228937663917801">"ເປີດ​ຈາກ"</string>
     <string name="title_save" msgid="2433679664882857999">"ບັນທຶກໄປທີ່"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ສ້າງໂຟນເດີ"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ໂຟ​ລ​ເດີໃໝ່"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ມຸມມອງແບບຊ່ອງ"</string>
     <string name="menu_list" msgid="7279285939892417279">"ມຸມມອງແບບລາຍຊື່"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ຮຽງລຳດັບຕາມ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ເລືອກທັງຫມົດ"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ອັດ​ສຳ​ເນົາ​ໃສ່…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ຍ້າຍໄປໃສ່..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"ໜ້າຈໍໃໝ່"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ສຳເນົາ"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"​ວາງໃສ່"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"ສະແດງ​ໂຕເກັບ​ຂໍ້ມູນພາຍໃນ"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"ສຳເນົາ"</string>
     <string name="button_move" msgid="2202666023104202232">"ຍ້າຍ"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"ປິດໄວ້"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"​ລອງ​ໃໝ່​ອີກ​"</string>
     <string name="sort_name" msgid="9183560467917256779">"ຕາມຊື່"</string>
     <string name="sort_date" msgid="586080032956151448">"ຕາມວັນທີທີ່ແກ້ໄຂ"</string>
     <string name="sort_size" msgid="3350681319735474741">"ຕາມຂະໜາດ"</string>
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-lt/strings.xml b/packages/DocumentsUI/res/values-lt/strings.xml
index 0c8b443..8105846 100644
--- a/packages/DocumentsUI/res/values-lt/strings.xml
+++ b/packages/DocumentsUI/res/values-lt/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Failai"</string>
     <string name="title_open" msgid="4353228937663917801">"Atidaryti iš"</string>
     <string name="title_save" msgid="2433679664882857999">"Išsaugoti į"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Kurti aplanką"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Naujas aplankas"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Tinklelio rodinys"</string>
     <string name="menu_list" msgid="7279285939892417279">"Sąrašo rodinys"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Rūšiuoti pagal"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Pasirinkti viską"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopijuoti į..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Perkelti į…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Naujas langas"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopijuoti"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Įklijuoti"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Rodyti vidinę atmintį"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopijuoti"</string>
     <string name="button_move" msgid="2202666023104202232">"Perkelti"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Atsisakyti"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Bandyti dar kartą"</string>
     <string name="sort_name" msgid="9183560467917256779">"Pagal pavadinimą"</string>
     <string name="sort_date" msgid="586080032956151448">"Pagal keitimo datą"</string>
     <string name="sort_size" msgid="3350681319735474741">"Pagal dydį"</string>
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-lv/strings.xml b/packages/DocumentsUI/res/values-lv/strings.xml
index 5c79554..48715e6 100644
--- a/packages/DocumentsUI/res/values-lv/strings.xml
+++ b/packages/DocumentsUI/res/values-lv/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Faili"</string>
     <string name="title_open" msgid="4353228937663917801">"Atvēršana no:"</string>
     <string name="title_save" msgid="2433679664882857999">"Saglabāšana:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Izveidot mapi"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Jauna mape"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Režģis"</string>
     <string name="menu_list" msgid="7279285939892417279">"Saraksts"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Kārtot pēc"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Atlasīt visus"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopēt…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Pārvietot uz…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Jauns logs"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopēt"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Ielīmēt"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Rādīt iekšējo atmiņu"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopēt"</string>
     <string name="button_move" msgid="2202666023104202232">"Pārvietot"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Noraidīt"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Mēģināt vēlreiz"</string>
     <string name="sort_name" msgid="9183560467917256779">"Pēc nosaukuma"</string>
     <string name="sort_date" msgid="586080032956151448">"Pēc pārveidošanas datuma"</string>
     <string name="sort_size" msgid="3350681319735474741">"Pēc lieluma"</string>
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-mk-rMK/strings.xml b/packages/DocumentsUI/res/values-mk-rMK/strings.xml
index b2cb9f1..950a9b1 100644
--- a/packages/DocumentsUI/res/values-mk-rMK/strings.xml
+++ b/packages/DocumentsUI/res/values-mk-rMK/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Датотеки"</string>
     <string name="title_open" msgid="4353228937663917801">"Отвори од"</string>
     <string name="title_save" msgid="2433679664882857999">"Зачувај во"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Создади папка"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Нова папка"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Приказ на мрежа"</string>
     <string name="menu_list" msgid="7279285939892417279">"Приказ на список"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Подреди по"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Избери ги сите"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Копирај во…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Премести во..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Нов прозорец"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Копирај"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Залепи"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Прикажи внатрешна мемор."</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Копирај"</string>
     <string name="button_move" msgid="2202666023104202232">"Премести"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Отфрли"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Обиди се пак"</string>
     <string name="sort_name" msgid="9183560467917256779">"По име"</string>
     <string name="sort_date" msgid="586080032956151448">"Изменети по датум"</string>
     <string name="sort_size" msgid="3350681319735474741">"По големина"</string>
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-ml-rIN/strings.xml b/packages/DocumentsUI/res/values-ml-rIN/strings.xml
index 07efa66..32411b2 100644
--- a/packages/DocumentsUI/res/values-ml-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-ml-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ഫയലുകൾ"</string>
     <string name="title_open" msgid="4353228937663917801">"ഇതിൽ നിന്നും തുറക്കുക"</string>
     <string name="title_save" msgid="2433679664882857999">"ഇതില്‍‌ സംരക്ഷിക്കുക"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ഫോൾഡർ സൃഷ്‌ടിക്കുക"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"പുതിയ ഫോൾഡർ"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ഗ്രിഡ് കാഴ്‌ച"</string>
     <string name="menu_list" msgid="7279285939892417279">"ലിസ്റ്റ് കാഴ്‌ച"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ഇപ്രകാരം അടുക്കുക"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"എല്ലാം തിരഞ്ഞെടുക്കുക"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ഇതിൽ പകർത്തുക…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ഇതിലേക്ക് നീക്കുക..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"പുതിയ വിന്‍‍ഡോ"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"പകര്‍ത്തുക"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ഒട്ടിക്കുക"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"ആന്തരിക സ്റ്റോറേജ്  കാണിക്കുക"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"പകര്‍ത്തുക"</string>
     <string name="button_move" msgid="2202666023104202232">"നീക്കുക"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"ഡിസ്മിസ് ചെയ്യുക"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="sort_name" msgid="9183560467917256779">"പേര് പ്രകാരം"</string>
     <string name="sort_date" msgid="586080032956151448">"പരിഷ്‌ക്കരിച്ച തീയതി പ്രകാരം"</string>
     <string name="sort_size" msgid="3350681319735474741">"വലുപ്പം പ്രകാരം"</string>
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-mn-rMN/strings.xml b/packages/DocumentsUI/res/values-mn-rMN/strings.xml
index d45778c..0c5c72d 100644
--- a/packages/DocumentsUI/res/values-mn-rMN/strings.xml
+++ b/packages/DocumentsUI/res/values-mn-rMN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файл"</string>
     <string name="title_open" msgid="4353228937663917801">"Нээх"</string>
     <string name="title_save" msgid="2433679664882857999">"Хадгалах"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Фолдер үүсгэх"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Шинэ фолдер"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Эгнүүлж харах"</string>
     <string name="menu_list" msgid="7279285939892417279">"Жагсааж харах"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Эрэмбэлэх"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Бүгдийг сонгох"</string>
     <string name="menu_copy" msgid="3612326052677229148">"...руу хуулах"</string>
     <string name="menu_move" msgid="1828090633118079817">"Байршуулах газар"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Шинэ цонх"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Хуулах"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Буулгах"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Дотоод санг харуулах"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Хуулах"</string>
     <string name="button_move" msgid="2202666023104202232">"Зөөх"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Алгасах"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Дахин оролдоно уу"</string>
     <string name="sort_name" msgid="9183560467917256779">"Нэрээр"</string>
     <string name="sort_date" msgid="586080032956151448">"Өөрчлөгдсөн огноогоор"</string>
     <string name="sort_size" msgid="3350681319735474741">"Хэмжээгээр"</string>
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-mr-rIN/strings.xml b/packages/DocumentsUI/res/values-mr-rIN/strings.xml
index 8d70143..15c1291 100644
--- a/packages/DocumentsUI/res/values-mr-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-mr-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"फायली"</string>
     <string name="title_open" msgid="4353228937663917801">"वरून उघडा"</string>
     <string name="title_save" msgid="2433679664882857999">"येथे जतन करा"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"फोल्डर तयार करा"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"नवीन फोल्डर"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ग्रिड दृश्य"</string>
     <string name="menu_list" msgid="7279285939892417279">"सूची"</string>
     <string name="menu_sort" msgid="7677740407158414452">"नुसार क्रमवारी लावा"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"सर्व निवडा"</string>
     <string name="menu_copy" msgid="3612326052677229148">"यावर कॉपी करा…"</string>
     <string name="menu_move" msgid="1828090633118079817">"यावर हलवा…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"नवीन विंडो"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"कॉपी करा"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"पेस्ट करा"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"अंतर्गत संचयन दर्शवा"</string>
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-ms-rMY/strings.xml b/packages/DocumentsUI/res/values-ms-rMY/strings.xml
index 2250109..12cf2e3 100644
--- a/packages/DocumentsUI/res/values-ms-rMY/strings.xml
+++ b/packages/DocumentsUI/res/values-ms-rMY/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fail"</string>
     <string name="title_open" msgid="4353228937663917801">"Buka dari"</string>
     <string name="title_save" msgid="2433679664882857999">"Simpan ke"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Buat folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Folder baharu"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Paparan grid"</string>
     <string name="menu_list" msgid="7279285939892417279">"Paparan senarai"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Isih mengikut"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Pilih semua"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Salin ke..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Alihkan ke…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Tetingkap baharu"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Salin"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Tampal"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Papar storan dalaman"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Salin"</string>
     <string name="button_move" msgid="2202666023104202232">"Alihkan"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Tolak"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Cuba Lagi"</string>
     <string name="sort_name" msgid="9183560467917256779">"Mengikut nama"</string>
     <string name="sort_date" msgid="586080032956151448">"Mengikut tarikh diubah"</string>
     <string name="sort_size" msgid="3350681319735474741">"Mengikut saiz"</string>
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-my-rMM/strings.xml b/packages/DocumentsUI/res/values-my-rMM/strings.xml
index 480c27b..a1ab012 100644
--- a/packages/DocumentsUI/res/values-my-rMM/strings.xml
+++ b/packages/DocumentsUI/res/values-my-rMM/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ဖိုင်များ"</string>
     <string name="title_open" msgid="4353228937663917801">"မှ ဖွင့်ပါ"</string>
     <string name="title_save" msgid="2433679664882857999">"သို့ သိမ်းပါ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"အကန့် တည်ဆောက်ရန်"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ဖိုလ်ဒါ အသစ်"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ဖယားကွက်မြင်ကွင်း"</string>
     <string name="menu_list" msgid="7279285939892417279">"အစဉ်လိုက်မြင်ကွင်း"</string>
     <string name="menu_sort" msgid="7677740407158414452">"အစဉ်အလိုက် စီခြင်း"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"အားလုံးကို ရွေးရန်"</string>
     <string name="menu_copy" msgid="3612326052677229148">"သို့ကူးယူရန်…"</string>
     <string name="menu_move" msgid="1828090633118079817">"...သို့ ရွှေ့ရန်"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"ဝင်းဒိုးသစ်"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ကူးယူရန်"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ကပ်ရန်"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"စက်ရှိစတိုရုံ ပြပါ"</string>
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-nb/strings.xml b/packages/DocumentsUI/res/values-nb/strings.xml
index 48355aa..edeaa6d 100644
--- a/packages/DocumentsUI/res/values-nb/strings.xml
+++ b/packages/DocumentsUI/res/values-nb/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Filer"</string>
     <string name="title_open" msgid="4353228937663917801">"Åpne fra"</string>
     <string name="title_save" msgid="2433679664882857999">"Lagre i"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Opprett en mappe"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Ny mappe"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Rutenettvisning"</string>
     <string name="menu_list" msgid="7279285939892417279">"Listevisning"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortér etter"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Markér alt"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiér til …"</string>
     <string name="menu_move" msgid="1828090633118079817">"Flytt til"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nytt vindu"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiér"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Lim inn"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Vis den interne lagringen"</string>
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-ne-rNP/strings.xml b/packages/DocumentsUI/res/values-ne-rNP/strings.xml
index 53942c1..bd54211 100644
--- a/packages/DocumentsUI/res/values-ne-rNP/strings.xml
+++ b/packages/DocumentsUI/res/values-ne-rNP/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"फाइलहरू"</string>
     <string name="title_open" msgid="4353228937663917801">"यसबाट खोल्नुहोस्"</string>
     <string name="title_save" msgid="2433679664882857999">"यसमा सुरक्षित गर्नुहोस्"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"फोल्डर सिर्जना गर्नुहोस्"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"नयाँ फोल्डर"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ग्रिड दृश्य"</string>
     <string name="menu_list" msgid="7279285939892417279">"सूची दृश्य"</string>
     <string name="menu_sort" msgid="7677740407158414452">"यसद्वारा क्रमवद्घ गर्नुहोस्"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"सबै चयन गर्नुहोस्"</string>
     <string name="menu_copy" msgid="3612326052677229148">"यसमा प्रतिलिपि गर्नुहोस् ..."</string>
     <string name="menu_move" msgid="1828090633118079817">"…मा सार्नुहोस्"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"नयाँ विन्डो"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"प्रतिलिपि बनाउनुहोस्"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"टाँस्नुहोस्"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"आन्तरिक भण्डारण देखाउनुहोस्"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"प्रतिलिपि बनाउनुहोस्"</string>
     <string name="button_move" msgid="2202666023104202232">"सार्नुहोस्"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"खारेज गर्नुहोस्"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"पुन: प्रयास गर्नुहोस्"</string>
     <string name="sort_name" msgid="9183560467917256779">"नाम अनुसार"</string>
     <string name="sort_date" msgid="586080032956151448">"परिमार्जित मिति अनुसार"</string>
     <string name="sort_size" msgid="3350681319735474741">"आकार अनुसार"</string>
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-nl/strings.xml b/packages/DocumentsUI/res/values-nl/strings.xml
index d3e6bbe..f2eda72 100644
--- a/packages/DocumentsUI/res/values-nl/strings.xml
+++ b/packages/DocumentsUI/res/values-nl/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Bestanden"</string>
     <string name="title_open" msgid="4353228937663917801">"Openen vanuit"</string>
     <string name="title_save" msgid="2433679664882857999">"Opslaan in"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Map maken"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nieuwe map"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Rasterweergave"</string>
     <string name="menu_list" msgid="7279285939892417279">"Lijstweergave"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sorteren op"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Alles selecteren"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiëren naar…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Verplaatsen naar…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nieuw venster"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiëren"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Plakken"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Interne opslag weergeven"</string>
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-pa-rIN/strings.xml b/packages/DocumentsUI/res/values-pa-rIN/strings.xml
index 3b14396..e139dfd 100644
--- a/packages/DocumentsUI/res/values-pa-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-pa-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ਫਾਈਲਾਂ"</string>
     <string name="title_open" msgid="4353228937663917801">"ਤੋਂ ਖੋਲ੍ਹੋ"</string>
     <string name="title_save" msgid="2433679664882857999">"ਇਸ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕਰੋ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ਫੋਲਡਰ ਬਣਾਓ"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"ਨਵਾਂ ਫੋਲਡਰ"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ਗ੍ਰਿਡ ਵਿਊ"</string>
     <string name="menu_list" msgid="7279285939892417279">"ਸੂਚੀ ਦ੍ਰਿਸ਼"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ਇਸ ਅਨੁਸਾਰ ਛਾਂਟੋ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"ਸਾਰੇ ਚੁਣੋ"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ਇਸ ਵਿੱਚ ਕਾਪੀ ਕਰੋ…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ਇਸ ਵਿੱਚ ਮੂਵ ਕਰੋ..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"ਨਵੀਂ ਵਿੰਡੋ"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"ਕਾਪੀ ਕਰੋ"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ਪੇਸਟ ਕਰੋ"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਦਿਖਾਓ"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"ਕਾਪੀ ਕਰੋ"</string>
     <string name="button_move" msgid="2202666023104202232">"ਮੂਵ ਕਰੋ"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"ਬਰਖਾਸਤ ਕਰੋ"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="sort_name" msgid="9183560467917256779">"ਨਾਮ ਮੁਤਾਬਕ"</string>
     <string name="sort_date" msgid="586080032956151448">"ਤਾਰੀਖ ਮੁਤਾਬਕ ਸੰਸ਼ੋਧਿਤ"</string>
     <string name="sort_size" msgid="3350681319735474741">"ਆਕਾਰ ਮੁਤਾਬਕ"</string>
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-pl/strings.xml b/packages/DocumentsUI/res/values-pl/strings.xml
index 3e4ef68..bb81377 100644
--- a/packages/DocumentsUI/res/values-pl/strings.xml
+++ b/packages/DocumentsUI/res/values-pl/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Pliki"</string>
     <string name="title_open" msgid="4353228937663917801">"Otwórz z"</string>
     <string name="title_save" msgid="2433679664882857999">"Zapisz w"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Utwórz folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nowy folder"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Widok siatki"</string>
     <string name="menu_list" msgid="7279285939892417279">"Widok listy"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortuj według"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Zaznacz wszystko"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiuj do…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Przenieś do…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nowe okno"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiuj"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Wklej"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Pokaż pamięć wewnętrzną"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopiuj"</string>
     <string name="button_move" msgid="2202666023104202232">"Przenieś"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Zamknij"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Spróbuj ponownie"</string>
     <string name="sort_name" msgid="9183560467917256779">"Według nazwy"</string>
     <string name="sort_date" msgid="586080032956151448">"Według daty edycji"</string>
     <string name="sort_size" msgid="3350681319735474741">"Według rozmiaru"</string>
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-rBR/strings.xml b/packages/DocumentsUI/res/values-pt-rBR/strings.xml
index ca984cf..d72f0c3 100644
--- a/packages/DocumentsUI/res/values-pt-rBR/strings.xml
+++ b/packages/DocumentsUI/res/values-pt-rBR/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Arquivos"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Salvar em"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Criar pasta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nova pasta"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Visualização em grade"</string>
     <string name="menu_list" msgid="7279285939892417279">"Visualização em lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Classificar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Selecionar tudo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar para..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover para..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nova janela"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Colar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar armaz. interno"</string>
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-rPT/strings.xml b/packages/DocumentsUI/res/values-pt-rPT/strings.xml
index ab67358..43265a5 100644
--- a/packages/DocumentsUI/res/values-pt-rPT/strings.xml
+++ b/packages/DocumentsUI/res/values-pt-rPT/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Ficheiros"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Guardar em"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Criar pasta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nova pasta"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Vista de grelha"</string>
     <string name="menu_list" msgid="7279285939892417279">"Vista de lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Ordenar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Selecionar tudo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar para…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover para..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nova janela"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Colar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar mem. armaz. int."</string>
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-pt/strings.xml b/packages/DocumentsUI/res/values-pt/strings.xml
index ca984cf..d72f0c3 100644
--- a/packages/DocumentsUI/res/values-pt/strings.xml
+++ b/packages/DocumentsUI/res/values-pt/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Arquivos"</string>
     <string name="title_open" msgid="4353228937663917801">"Abrir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Salvar em"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Criar pasta"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nova pasta"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Visualização em grade"</string>
     <string name="menu_list" msgid="7279285939892417279">"Visualização em lista"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Classificar por"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Selecionar tudo"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiar para..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Mover para..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nova janela"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiar"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Colar"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Mostrar armaz. interno"</string>
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-ro/strings.xml b/packages/DocumentsUI/res/values-ro/strings.xml
index e927b78..e880bbd 100644
--- a/packages/DocumentsUI/res/values-ro/strings.xml
+++ b/packages/DocumentsUI/res/values-ro/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fișiere"</string>
     <string name="title_open" msgid="4353228937663917801">"Deschideți din"</string>
     <string name="title_save" msgid="2433679664882857999">"Salvați în"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Creați un dosar"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Dosar nou"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Afișare tip grilă"</string>
     <string name="menu_list" msgid="7279285939892417279">"Afișare tip listă"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortați după"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Selectați tot"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Copiați în…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Mutați în…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Fereastră nouă"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Copiați"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Inserați"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Afișați stocarea internă"</string>
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-ru/strings.xml b/packages/DocumentsUI/res/values-ru/strings.xml
index cdf20ca..e475591 100644
--- a/packages/DocumentsUI/res/values-ru/strings.xml
+++ b/packages/DocumentsUI/res/values-ru/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файлы"</string>
     <string name="title_open" msgid="4353228937663917801">"Открыть"</string>
     <string name="title_save" msgid="2433679664882857999">"Сохранить"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Новая папка"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Создать папку"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Сетка"</string>
     <string name="menu_list" msgid="7279285939892417279">"Список"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Сортировать"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Выбрать все"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Копировать в…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Переместить"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Новое окно"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Копировать"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Вставить"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Внутренняя память"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Копировать"</string>
     <string name="button_move" msgid="2202666023104202232">"Переместить"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Скрыть"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Повторить"</string>
     <string name="sort_name" msgid="9183560467917256779">"По названию"</string>
     <string name="sort_date" msgid="586080032956151448">"По дате изменения"</string>
     <string name="sort_size" msgid="3350681319735474741">"По размеру"</string>
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-si-rLK/strings.xml b/packages/DocumentsUI/res/values-si-rLK/strings.xml
index 138f8a6..e380b0a 100644
--- a/packages/DocumentsUI/res/values-si-rLK/strings.xml
+++ b/packages/DocumentsUI/res/values-si-rLK/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ගොනු"</string>
     <string name="title_open" msgid="4353228937663917801">"විවෘත වන්නේ"</string>
     <string name="title_save" msgid="2433679664882857999">"සුරකින්නේ"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ෆෝල්ඩරයක් සාදන්න"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"නව ෆෝල්ඩරය"</string>
     <string name="menu_grid" msgid="6878021334497835259">"ජාල පෙනුම"</string>
     <string name="menu_list" msgid="7279285939892417279">"ලැයිස්තු පෙනුම"</string>
     <string name="menu_sort" msgid="7677740407158414452">"අනුපිළිවෙලට සකසා ඇත්තේ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"සියල්ල තෝරන්න"</string>
     <string name="menu_copy" msgid="3612326052677229148">"වෙත පිටපත් කරන්න..."</string>
     <string name="menu_move" msgid="1828090633118079817">"වෙත ගෙනයන්න..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"නව කවුළුව"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"පිටපත් කරන්න"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"අලවන්න"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"අභ්‍යන්තර ආචයනය පෙන්වන්න"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"පිටපත් කිරීම"</string>
     <string name="button_move" msgid="2202666023104202232">"ගෙන යන්න"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"ඉවතලන්න"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"නැවත උත්සාහ කරන්න"</string>
     <string name="sort_name" msgid="9183560467917256779">"නමින්"</string>
     <string name="sort_date" msgid="586080032956151448">"වෙනස් කරන ලද දිනයෙන්"</string>
     <string name="sort_size" msgid="3350681319735474741">"ප්‍රමාණය මගින්"</string>
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-sk/strings.xml b/packages/DocumentsUI/res/values-sk/strings.xml
index 4310819..6614dca 100644
--- a/packages/DocumentsUI/res/values-sk/strings.xml
+++ b/packages/DocumentsUI/res/values-sk/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Súbory"</string>
     <string name="title_open" msgid="4353228937663917801">"Otvoriť z"</string>
     <string name="title_save" msgid="2433679664882857999">"Uložiť do"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Vytvoriť priečinok"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nový priečinok"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Zobrazenie mriežky"</string>
     <string name="menu_list" msgid="7279285939892417279">"Zobrazenie zoznamu"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Zoradiť podľa"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Vybrať všetko"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopírovať do…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Presunúť do…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nové okno"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopírovať"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Prilepiť"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Zobraziť interné úložisko"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopírovať"</string>
     <string name="button_move" msgid="2202666023104202232">"Presunúť"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Odmietnuť"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Skúsiť znova"</string>
     <string name="sort_name" msgid="9183560467917256779">"Podľa názvu"</string>
     <string name="sort_date" msgid="586080032956151448">"Podľa dátumu zmeny"</string>
     <string name="sort_size" msgid="3350681319735474741">"Podľa veľkosti"</string>
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-sl/strings.xml b/packages/DocumentsUI/res/values-sl/strings.xml
index c9982a7..58a4cc0 100644
--- a/packages/DocumentsUI/res/values-sl/strings.xml
+++ b/packages/DocumentsUI/res/values-sl/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Datoteke"</string>
     <string name="title_open" msgid="4353228937663917801">"Odpri iz mape"</string>
     <string name="title_save" msgid="2433679664882857999">"Shrani v"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Ustvarjanje mape"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Nova mapa"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Mrežni pogled"</string>
     <string name="menu_list" msgid="7279285939892417279">"Pogled seznama"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Razvrsti glede na"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Izberi vse"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiraj v …"</string>
     <string name="menu_move" msgid="1828090633118079817">"Premakni v ..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Novo okno"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiraj"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Prilepi"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Pokaži notranjo shrambo"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopiraj"</string>
     <string name="button_move" msgid="2202666023104202232">"Premik"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Opusti"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Poskusite znova"</string>
     <string name="sort_name" msgid="9183560467917256779">"Po imenu"</string>
     <string name="sort_date" msgid="586080032956151448">"Po datumu spremembe"</string>
     <string name="sort_size" msgid="3350681319735474741">"Po velikosti"</string>
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-sq-rAL/strings.xml b/packages/DocumentsUI/res/values-sq-rAL/strings.xml
index f1ee1bc..7eba92f 100644
--- a/packages/DocumentsUI/res/values-sq-rAL/strings.xml
+++ b/packages/DocumentsUI/res/values-sq-rAL/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Skedarët"</string>
     <string name="title_open" msgid="4353228937663917801">"Hap nga"</string>
     <string name="title_save" msgid="2433679664882857999">"Ruaje te"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Krijo dosje"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Dosje e re"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Pamje rrjete"</string>
     <string name="menu_list" msgid="7279285939892417279">"Pamje liste"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Rendit sipas"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Zgjidhi të gjitha"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopjo te..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Zhvendos te..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Dritare e re"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopjo"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Ngjit"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Trego hapësirën e brendshme ruajtëse"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopjo"</string>
     <string name="button_move" msgid="2202666023104202232">"Zhvendos"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Largoje"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Provo sërish"</string>
     <string name="sort_name" msgid="9183560467917256779">"Sipas emrit"</string>
     <string name="sort_date" msgid="586080032956151448">"Sipas datës së modifikimit"</string>
     <string name="sort_size" msgid="3350681319735474741">"Sipas madhësisë"</string>
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-sr/strings.xml b/packages/DocumentsUI/res/values-sr/strings.xml
index c5116c2..0bce687 100644
--- a/packages/DocumentsUI/res/values-sr/strings.xml
+++ b/packages/DocumentsUI/res/values-sr/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Датотеке"</string>
     <string name="title_open" msgid="4353228937663917801">"Отвори са"</string>
     <string name="title_save" msgid="2433679664882857999">"Сачувај у"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Направи директоријум"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Нови директоријум"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Приказ мреже"</string>
     <string name="menu_list" msgid="7279285939892417279">"Приказ листе"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Сортирај према"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Изабери све"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Копирај на..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Премести у..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Нови прозор"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Копирај"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Налепи"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Прикажи интерну меморију"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Копирај"</string>
     <string name="button_move" msgid="2202666023104202232">"Премести"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Одбаци"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Покушај поново"</string>
     <string name="sort_name" msgid="9183560467917256779">"Према имену"</string>
     <string name="sort_date" msgid="586080032956151448">"Према датуму измене"</string>
     <string name="sort_size" msgid="3350681319735474741">"Према величини"</string>
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-sv/strings.xml b/packages/DocumentsUI/res/values-sv/strings.xml
index 06e6514..a4a119c 100644
--- a/packages/DocumentsUI/res/values-sv/strings.xml
+++ b/packages/DocumentsUI/res/values-sv/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Filer"</string>
     <string name="title_open" msgid="4353228937663917801">"Öppna från"</string>
     <string name="title_save" msgid="2433679664882857999">"Spara till"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Skapa mapp"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Ny mapp"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Rutnätsvy"</string>
     <string name="menu_list" msgid="7279285939892417279">"Listvy"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sortera efter"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Markera allt"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopiera till …"</string>
     <string name="menu_move" msgid="1828090633118079817">"Flytta till ..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Nytt fönster"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopiera"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Klistra in"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Visa internminne"</string>
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-sw/strings.xml b/packages/DocumentsUI/res/values-sw/strings.xml
index 79bae1f..91151fb 100644
--- a/packages/DocumentsUI/res/values-sw/strings.xml
+++ b/packages/DocumentsUI/res/values-sw/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Faili"</string>
     <string name="title_open" msgid="4353228937663917801">"Fungua kutoka"</string>
     <string name="title_save" msgid="2433679664882857999">"Hifadhi kwenye"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Unda folda"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Folda mpya"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Mwonekano gridi"</string>
     <string name="menu_list" msgid="7279285939892417279">"Mwonekano orodha"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Panga kwa"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Chagua zote"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Nakili kwenda..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Hamisha hadi..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Dirisha jipya"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Nakili"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Bandika"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Onyesha hifadhi ya ndani"</string>
diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml
index f4bc88e..d415a84 100644
--- a/packages/DocumentsUI/res/values-sw720dp/styles.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/styles.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="DocumentsBaseTheme" parent="@*android:style/Theme.Material.DayNight.Dialog">
+    <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Dialog">
         <!-- We do not specify width of window here because the max size of
              floating window specified by windowFixedWidthis is limited. -->
         <item name="*android:windowFixedHeightMajor">80%</item>
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-ta-rIN/strings.xml b/packages/DocumentsUI/res/values-ta-rIN/strings.xml
index 117aabc..9a667b8 100644
--- a/packages/DocumentsUI/res/values-ta-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-ta-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"கோப்புகள்"</string>
     <string name="title_open" msgid="4353228937663917801">"இதில் திற"</string>
     <string name="title_save" msgid="2433679664882857999">"இதில் சேமி"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"கோப்புறையை உருவாக்கு"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"புதிய கோப்புறை"</string>
     <string name="menu_grid" msgid="6878021334497835259">"கட்டக் காட்சி"</string>
     <string name="menu_list" msgid="7279285939892417279">"பட்டியல்"</string>
     <string name="menu_sort" msgid="7677740407158414452">"இதன்படி வரிசைப்படுத்து"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"எல்லாவற்றையும் தேர்ந்தெடு"</string>
     <string name="menu_copy" msgid="3612326052677229148">"இங்கு நகலெடு…"</string>
     <string name="menu_move" msgid="1828090633118079817">"இதற்கு நகர்த்து…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"புதிய சாளரம்"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"நகலெடு"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"ஒட்டு"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"அகச் சேமிப்பகத்தைக் காட்டு"</string>
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-te-rIN/strings.xml b/packages/DocumentsUI/res/values-te-rIN/strings.xml
index 21b7f58..91d436a 100644
--- a/packages/DocumentsUI/res/values-te-rIN/strings.xml
+++ b/packages/DocumentsUI/res/values-te-rIN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ఫైల్‌లు"</string>
     <string name="title_open" msgid="4353228937663917801">"ఇక్కడి నుండి తెరువు"</string>
     <string name="title_save" msgid="2433679664882857999">"ఇందులో సేవ్ చేయి"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"ఫోల్డర్‌ను సృష్టించు"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"కొత్త ఫోల్డర్"</string>
     <string name="menu_grid" msgid="6878021334497835259">"గ్రిడ్ వీక్షణ"</string>
     <string name="menu_list" msgid="7279285939892417279">"జాబితా వీక్షణ"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ఇలా క్రమబద్ధీకరించు"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"అన్నీ ఎంచుకోండి"</string>
     <string name="menu_copy" msgid="3612326052677229148">"ఇక్కడికి కాపీ చేయి…"</string>
     <string name="menu_move" msgid="1828090633118079817">"దీనికి తరలించు..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"కొత్త విండో"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"కాపీ చేయి"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"అతికించు"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"అంతర్గత నిల్వను చూపు"</string>
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-th/strings.xml b/packages/DocumentsUI/res/values-th/strings.xml
index 8a49708..9d77561 100644
--- a/packages/DocumentsUI/res/values-th/strings.xml
+++ b/packages/DocumentsUI/res/values-th/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"ไฟล์"</string>
     <string name="title_open" msgid="4353228937663917801">"เปิดจาก"</string>
     <string name="title_save" msgid="2433679664882857999">"บันทึกไปยัง"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"สร้างโฟลเดอร์"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"โฟลเดอร์ใหม่"</string>
     <string name="menu_grid" msgid="6878021334497835259">"มุมมองตาราง"</string>
     <string name="menu_list" msgid="7279285939892417279">"มุมมองรายการ"</string>
     <string name="menu_sort" msgid="7677740407158414452">"จัดเรียงตาม"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"เลือกทั้งหมด"</string>
     <string name="menu_copy" msgid="3612326052677229148">"คัดลอกไปยัง…"</string>
     <string name="menu_move" msgid="1828090633118079817">"ย้ายไปที่…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"หน้าต่างใหม่"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"คัดลอก"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"วาง"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"แสดงที่จัดเก็บภายใน"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"คัดลอก"</string>
     <string name="button_move" msgid="2202666023104202232">"ย้าย"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"ปิด"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"ลองอีกครั้ง"</string>
     <string name="sort_name" msgid="9183560467917256779">"ตามชื่อ"</string>
     <string name="sort_date" msgid="586080032956151448">"ตามวันที่ที่ปรับเปลี่ยน"</string>
     <string name="sort_size" msgid="3350681319735474741">"ตามขนาด"</string>
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-tl/strings.xml b/packages/DocumentsUI/res/values-tl/strings.xml
index 5c0dc12..9ae30aa 100644
--- a/packages/DocumentsUI/res/values-tl/strings.xml
+++ b/packages/DocumentsUI/res/values-tl/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Mga File"</string>
     <string name="title_open" msgid="4353228937663917801">"Buksan mula sa"</string>
     <string name="title_save" msgid="2433679664882857999">"I-save sa"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Gumawa ng folder"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Bagong folder"</string>
     <string name="menu_grid" msgid="6878021334497835259">"View na grid"</string>
     <string name="menu_list" msgid="7279285939892417279">"View na listahan"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Uriin ayon sa"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Piliin lahat"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopyahin sa..."</string>
     <string name="menu_move" msgid="1828090633118079817">"Ilipat sa…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Bagong window"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopyahin"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"I-paste"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Ipakita internal storage"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopyahin"</string>
     <string name="button_move" msgid="2202666023104202232">"Ilipat"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"I-dismiss"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Subukang Muli"</string>
     <string name="sort_name" msgid="9183560467917256779">"Ayon sa pangalan"</string>
     <string name="sort_date" msgid="586080032956151448">"Ayon sa petsa ng pagbago"</string>
     <string name="sort_size" msgid="3350681319735474741">"Ayon sa laki"</string>
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-tr/strings.xml b/packages/DocumentsUI/res/values-tr/strings.xml
index 092b38e..3c2e39c9 100644
--- a/packages/DocumentsUI/res/values-tr/strings.xml
+++ b/packages/DocumentsUI/res/values-tr/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Dosyalar"</string>
     <string name="title_open" msgid="4353228937663917801">"Şuradan aç:"</string>
     <string name="title_save" msgid="2433679664882857999">"Şuraya kaydet:"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Klasör oluştur"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Yeni klasör"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Tablo görünümü"</string>
     <string name="menu_list" msgid="7279285939892417279">"Liste görünümü"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sıralama ölçütü"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Tümünü seç"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopyala…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Taşı..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Yeni pencere"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopyala"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Yapıştır"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Dahili depolamayı göster"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Kopyala"</string>
     <string name="button_move" msgid="2202666023104202232">"Taşı"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Kapat"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Tekrar Dene"</string>
     <string name="sort_name" msgid="9183560467917256779">"Ada göre"</string>
     <string name="sort_date" msgid="586080032956151448">"Değişiklik tarihine göre"</string>
     <string name="sort_size" msgid="3350681319735474741">"Boyuta göre"</string>
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-uk/strings.xml b/packages/DocumentsUI/res/values-uk/strings.xml
index dff37c3..407f1aa 100644
--- a/packages/DocumentsUI/res/values-uk/strings.xml
+++ b/packages/DocumentsUI/res/values-uk/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Файли"</string>
     <string name="title_open" msgid="4353228937663917801">"Відкрити"</string>
     <string name="title_save" msgid="2433679664882857999">"Зберегти в"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Створити папку"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Нова папка"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Режим таблиці"</string>
     <string name="menu_list" msgid="7279285939892417279">"Режим списку"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Параметри сортування"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Вибрати все"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Копіювати в…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Перемістити в…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Нове вікно"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Копіювати"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Вставити"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Показати внутр. пам’ять"</string>
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-ur-rPK/strings.xml b/packages/DocumentsUI/res/values-ur-rPK/strings.xml
index 82822ec..845d2cb 100644
--- a/packages/DocumentsUI/res/values-ur-rPK/strings.xml
+++ b/packages/DocumentsUI/res/values-ur-rPK/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"فائلیں"</string>
     <string name="title_open" msgid="4353228937663917801">"کھولیں از"</string>
     <string name="title_save" msgid="2433679664882857999">"اس میں محفوظ کریں"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"فولڈر بنائیں"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"نیا فولڈر"</string>
     <string name="menu_grid" msgid="6878021334497835259">"گرڈ منظر"</string>
     <string name="menu_list" msgid="7279285939892417279">"فہرست منظر"</string>
     <string name="menu_sort" msgid="7677740407158414452">"ترتیب دیں بلحاظ"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"سبھی کو منتخب کریں"</string>
     <string name="menu_copy" msgid="3612326052677229148">"اس میں کاپی کریں…"</string>
     <string name="menu_move" msgid="1828090633118079817">"اس میں منتقل کریں…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"نئی ونڈو"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"کاپی کریں"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"پیسٹ کریں"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"داخلی اسٹوریج دکھائیں"</string>
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-uz-rUZ/strings.xml b/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
index d0cfacc..f3514db 100644
--- a/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
+++ b/packages/DocumentsUI/res/values-uz-rUZ/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Fayllar"</string>
     <string name="title_open" msgid="4353228937663917801">"Ochish"</string>
     <string name="title_save" msgid="2433679664882857999">"Saqlash"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Jild yaratish"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Yangi jild"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Katak ko‘rinishida"</string>
     <string name="menu_list" msgid="7279285939892417279">"Ro‘yxat ko‘rinishida"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Saralash"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Barchasini belgilash"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Nusxalash…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Ko‘chirib o‘tkazish…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Yangi oyna"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Nusxalash"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Joylash"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Ichki xotirani ko‘rsatish"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Nusxalash"</string>
     <string name="button_move" msgid="2202666023104202232">"Ko‘chirib o‘tkazish"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"O‘chirish"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Qayta urinish"</string>
     <string name="sort_name" msgid="9183560467917256779">"Nomi bo‘yicha"</string>
     <string name="sort_date" msgid="586080032956151448">"Tahrir sanasi bo‘yicha"</string>
     <string name="sort_size" msgid="3350681319735474741">"Hajmi bo‘yicha"</string>
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-vi/strings.xml b/packages/DocumentsUI/res/values-vi/strings.xml
index 4583362..52a4e82 100644
--- a/packages/DocumentsUI/res/values-vi/strings.xml
+++ b/packages/DocumentsUI/res/values-vi/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Tệp"</string>
     <string name="title_open" msgid="4353228937663917801">"Mở từ"</string>
     <string name="title_save" msgid="2433679664882857999">"Lưu vào"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Tạo thư mục"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Thư mục mới"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Chế độ xem lưới"</string>
     <string name="menu_list" msgid="7279285939892417279">"Chế độ xem danh sách"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Sắp xếp theo"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Chọn tất cả"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Sao chép vào…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Chuyển tới..."</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Cửa sổ mới"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Sao chép"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Dán"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Hiển thị bộ nhớ trong"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"Sao chép"</string>
     <string name="button_move" msgid="2202666023104202232">"Di chuyển"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"Loại bỏ"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"Thử lại"</string>
     <string name="sort_name" msgid="9183560467917256779">"Theo tên"</string>
     <string name="sort_date" msgid="586080032956151448">"Theo ngày sửa đổi"</string>
     <string name="sort_size" msgid="3350681319735474741">"Theo kích thước"</string>
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-rCN/strings.xml b/packages/DocumentsUI/res/values-zh-rCN/strings.xml
index d577e14..8ee90cc 100644
--- a/packages/DocumentsUI/res/values-zh-rCN/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rCN/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"文件"</string>
     <string name="title_open" msgid="4353228937663917801">"打开文件"</string>
     <string name="title_save" msgid="2433679664882857999">"保存文件"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"新建文件夹"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"新建文件夹"</string>
     <string name="menu_grid" msgid="6878021334497835259">"网格视图"</string>
     <string name="menu_list" msgid="7279285939892417279">"列表视图"</string>
     <string name="menu_sort" msgid="7677740407158414452">"排序依据"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"全选"</string>
     <string name="menu_copy" msgid="3612326052677229148">"复制到…"</string>
     <string name="menu_move" msgid="1828090633118079817">"移动到…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"新建窗口"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"复制"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"粘贴"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"显示内部存储设备"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"复制"</string>
     <string name="button_move" msgid="2202666023104202232">"移动"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"关闭"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"重试"</string>
     <string name="sort_name" msgid="9183560467917256779">"按名称"</string>
     <string name="sort_date" msgid="586080032956151448">"按修改日期"</string>
     <string name="sort_size" msgid="3350681319735474741">"按大小"</string>
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-rHK/strings.xml b/packages/DocumentsUI/res/values-zh-rHK/strings.xml
index 3d44047..dadc029 100644
--- a/packages/DocumentsUI/res/values-zh-rHK/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rHK/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"檔案"</string>
     <string name="title_open" msgid="4353228937663917801">"開啟檔案"</string>
     <string name="title_save" msgid="2433679664882857999">"儲存至"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"建立資料夾"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"新增資料夾"</string>
     <string name="menu_grid" msgid="6878021334497835259">"格狀檢視"</string>
     <string name="menu_list" msgid="7279285939892417279">"清單檢視"</string>
     <string name="menu_sort" msgid="7677740407158414452">"排序方式"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"全部選取"</string>
     <string name="menu_copy" msgid="3612326052677229148">"複製到…"</string>
     <string name="menu_move" msgid="1828090633118079817">"移至…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"新視窗"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"複製"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"貼上"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"顯示內部儲存空間"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"複製"</string>
     <string name="button_move" msgid="2202666023104202232">"移動"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"關閉"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"再試一次"</string>
     <string name="sort_name" msgid="9183560467917256779">"按名稱"</string>
     <string name="sort_date" msgid="586080032956151448">"按修改日期"</string>
     <string name="sort_size" msgid="3350681319735474741">"按大小"</string>
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-zh-rTW/strings.xml b/packages/DocumentsUI/res/values-zh-rTW/strings.xml
index aaad98c..cdd1288 100644
--- a/packages/DocumentsUI/res/values-zh-rTW/strings.xml
+++ b/packages/DocumentsUI/res/values-zh-rTW/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"檔案"</string>
     <string name="title_open" msgid="4353228937663917801">"開啟檔案"</string>
     <string name="title_save" msgid="2433679664882857999">"儲存至"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"建立資料夾"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"新增資料夾"</string>
     <string name="menu_grid" msgid="6878021334497835259">"格狀檢視"</string>
     <string name="menu_list" msgid="7279285939892417279">"清單檢視"</string>
     <string name="menu_sort" msgid="7677740407158414452">"排序依據"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"全選"</string>
     <string name="menu_copy" msgid="3612326052677229148">"複製到…"</string>
     <string name="menu_move" msgid="1828090633118079817">"移至…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"新視窗"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"複製"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"貼上"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"顯示內部儲存空間"</string>
@@ -45,8 +46,7 @@
     <string name="button_copy" msgid="8706475544635021302">"複製"</string>
     <string name="button_move" msgid="2202666023104202232">"移動"</string>
     <string name="button_dismiss" msgid="3714065566893946085">"關閉"</string>
-    <!-- no translation found for button_retry (4392027584153752797) -->
-    <skip />
+    <string name="button_retry" msgid="4392027584153752797">"再試一次"</string>
     <string name="sort_name" msgid="9183560467917256779">"依名稱"</string>
     <string name="sort_date" msgid="586080032956151448">"依修改日期"</string>
     <string name="sort_size" msgid="3350681319735474741">"依大小"</string>
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-zu/strings.xml b/packages/DocumentsUI/res/values-zu/strings.xml
index 8f20cc4..d6bb2b4 100644
--- a/packages/DocumentsUI/res/values-zu/strings.xml
+++ b/packages/DocumentsUI/res/values-zu/strings.xml
@@ -20,7 +20,7 @@
     <string name="files_label" msgid="6051402950202690279">"Amafayela"</string>
     <string name="title_open" msgid="4353228937663917801">"Vula kusuka ku-"</string>
     <string name="title_save" msgid="2433679664882857999">"Londoloza ku-"</string>
-    <string name="menu_create_dir" msgid="5947289605844398389">"Dala ifolda"</string>
+    <string name="menu_create_dir" msgid="2547620241173881754">"Ifolda entsha"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Ukubuka kwegridi"</string>
     <string name="menu_list" msgid="7279285939892417279">"Ukubuka uhlu"</string>
     <string name="menu_sort" msgid="7677740407158414452">"Hlunga nge-"</string>
@@ -33,6 +33,7 @@
     <string name="menu_select_all" msgid="8323579667348729928">"Khetha konke"</string>
     <string name="menu_copy" msgid="3612326052677229148">"Kopishela ku…"</string>
     <string name="menu_move" msgid="1828090633118079817">"Hambisa ku…"</string>
+    <string name="menu_new_window" msgid="1226032889278727538">"Iwindi elisha"</string>
     <string name="menu_copy_to_clipboard" msgid="489311381979634291">"Kopisha"</string>
     <string name="menu_paste_from_clipboard" msgid="2071583031180257091">"Namathisela"</string>
     <string name="menu_advanced_show" product="nosdcard" msgid="4693652895715631401">"Bonisa isitoreji sangaphakathi"</string>
diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml
index ed7820b..ad419aa 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 caaa2b9..e3b1324 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -39,6 +39,7 @@
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 66f8acd..047949f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -34,6 +34,7 @@
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
@@ -72,6 +73,8 @@
     // TODO: Move it to a shared file when more operations are implemented.
     public static final int FAILURE_COPY = 1;
 
+    private PowerManager mPowerManager;
+
     private NotificationManager mNotificationManager;
     private Notification.Builder mProgressBuilder;
 
@@ -140,12 +143,16 @@
             return;
         }
 
+        final PowerManager.WakeLock wakeLock = mPowerManager
+                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
         final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
         // Copy by default.
         final int transferMode = intent.getIntExtra(EXTRA_TRANSFER_MODE, TRANSFER_MODE_COPY);
 
         try {
+            wakeLock.acquire();
+
             // Acquire content providers.
             mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
                     srcs.get(0).authority);
@@ -165,6 +172,8 @@
             ContentProviderClient.releaseQuietly(mSrcClient);
             ContentProviderClient.releaseQuietly(mDstClient);
 
+            wakeLock.release();
+
             // Dismiss the ongoing copy notification when the copy is done.
             mNotificationManager.cancel(mJobId, 0);
 
@@ -198,7 +207,8 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        mPowerManager = getSystemService(PowerManager.class);
+        mNotificationManager = getSystemService(NotificationManager.class);
     }
 
     /**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 0abbf4e..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
@@ -220,7 +230,7 @@
 
         mEmptyView = view.findViewById(android.R.id.empty);
 
-        mRecView = (RecyclerView) view.findViewById(R.id.recyclerView);
+        mRecView = (RecyclerView) view.findViewById(R.id.list);
         mRecView.setRecyclerListener(
                 new RecyclerListener() {
                     @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,13 +899,55 @@
     // 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;
+            // Setting this using android:focusable in the item layouts doesn't work for list items.
+            // 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);
         }
     }
 
@@ -913,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
@@ -950,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);
@@ -1282,7 +1365,7 @@
     }
 
     void copySelectedToClipboard() {
-        Selection sel = mModel.getSelection(new Selection());
+        Selection sel = mSelectionManager.getSelection(new Selection());
         copySelectionToClipboard(sel);
     }
 
@@ -1331,7 +1414,7 @@
     }
 
     void selectAllFiles() {
-        boolean changed = mModel.selectAll();
+        boolean changed = mSelectionManager.setItemsSelected(0, mModel.getItemCount(), true);
         if (changed) {
             updateDisplayState();
         }
@@ -1426,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;
             }
@@ -1648,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.");
 
@@ -1736,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) {
-                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() {
@@ -1775,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;
 
@@ -1802,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;
         }
 
         /**
@@ -1823,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.");
+                }
             }
         }
 
@@ -1842,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();
+            }
         }
 
         /**
@@ -1863,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();
+                }
+            }
         }
 
         /**
@@ -1922,7 +1999,7 @@
                 } else {
                     if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
                 }
-                mMarkedForDeletion.clear();
+                resetDeleteData();
 
                 mListener.onCompletion();
             }
@@ -1958,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/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 70ddf59..627ba75 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -89,11 +89,13 @@
         RootsFragment.show(getFragmentManager(), null);
 
         if (mState.restored) {
+            if (DEBUG) Log.d(TAG, "Restored instance for uri: " + getIntent().getData());
             onCurrentDirectoryChanged(ANIM_NONE);
         } else {
             Intent intent = getIntent();
             Uri uri = intent.getData();
 
+            if (DEBUG) Log.d(TAG, "Creating new instance for uri: " + uri);
             // If a non-empty stack is present in our state it was read (presumably)
             // from EXTRA_STACK intent extra. In this case, we'll skip other means of
             // loading or restoring the stack.
@@ -321,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/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
index 17a1161..7426af5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.Shared.TAG;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -65,7 +66,9 @@
             }
         }
 
-        Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
+        if (DEBUG && mCount != cursor.getCount()) {
+            Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
+        }
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/LauncherActivity.java b/packages/DocumentsUI/src/com/android/documentsui/LauncherActivity.java
index c29937d..b3d0cf3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/LauncherActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/LauncherActivity.java
@@ -16,15 +16,17 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.AppTask;
-import android.app.ActivityManager.RecentTaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import java.util.List;
 
@@ -39,39 +41,47 @@
  */
 public class LauncherActivity extends Activity {
 
-    public static final String LAUNCH_CONTROL_AUTHORITY = "com.android.documentsui.launchControl";
+    private static final String LAUNCH_CONTROL_AUTHORITY = "com.android.documentsui.launchControl";
+    private static final String TAG = "LauncherActivity";
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         ActivityManager activities = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-        List<AppTask> tasks = activities.getAppTasks();
 
-        AppTask raiseTask = null;
-        for (AppTask task : tasks) {
-            Uri taskUri = task.getTaskInfo().baseIntent.getData();
-            if (taskUri != null && isLaunchUri(taskUri)) {
-                raiseTask = task;
-            }
-        }
-
-        if (raiseTask == null) {
-            launchFilesTask();
+        Intent intent = findTask(activities);
+        if (intent != null) {
+            restoreTask(intent);
         } else {
-            raiseFilesTask(activities, raiseTask.getTaskInfo());
+            startTask();
         }
 
         finish();
     }
 
-    private void launchFilesTask() {
+    private @Nullable Intent findTask(ActivityManager activities) {
+        List<AppTask> tasks = activities.getAppTasks();
+        for (AppTask task : tasks) {
+            Intent intent = task.getTaskInfo().baseIntent;
+            Uri uri = intent.getData();
+            if (isLaunchUri(uri)) {
+                return intent;
+            }
+        }
+        return null;
+    }
+
+    private void startTask() {
         Intent intent = createLaunchIntent(this);
+        if (DEBUG) Log.d(TAG, "Starting new task > " + intent.getData());
         startActivity(intent);
     }
 
-    private void raiseFilesTask(ActivityManager activities, RecentTaskInfo task) {
-        activities.moveTaskToFront(task.id, 0);
+    private void restoreTask(Intent intent) {
+        if (DEBUG) Log.d(TAG, "Restoring existing task > " + intent.getData());
+        // TODO: This doesn't appear to restore a task once it has stopped running.
+        startActivity(intent);
     }
 
     static Intent createLaunchIntent(Context context) {
@@ -88,6 +98,7 @@
     }
 
     static boolean isLaunchUri(@Nullable Uri uri) {
-        return uri != null && LAUNCH_CONTROL_AUTHORITY.equals(uri.getAuthority());
+        boolean result = uri != null && LAUNCH_CONTROL_AUTHORITY.equals(uri.getAuthority());
+        return result;
     }
 }
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..605c530 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,39 @@
     @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;
+            } else {
+                Log.e(TAG, "Can't resolve trusted quick view package: " + trustedPkg);
+            }
+        }
+
+        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/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index cf682fa..bb6c3b5e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -83,7 +83,7 @@
 
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
-        mRecView = (RecyclerView) view.findViewById(R.id.recyclerView);
+        mRecView = (RecyclerView) view.findViewById(R.id.list);
         mRecView.setLayoutManager(new LinearLayoutManager(getContext()));
         mRecView.addOnItemTouchListener(mItemListener);
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index de35cef..4fc3788 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -102,6 +102,7 @@
         mRecentsRoot.authority = null;
         mRecentsRoot.rootId = null;
         mRecentsRoot.derivedIcon = R.drawable.ic_root_recent;
+        mRecentsRoot.derivedType = RootInfo.TYPE_RECENTS;
         mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
                 | Root.FLAG_SUPPORTS_IS_CHILD;
         mRecentsRoot.title = mContext.getString(R.string.root_recent);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index c98da47..0dcd02d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -286,51 +286,34 @@
         public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
             super(context, 0);
 
-            RootItem recents = null;
-            RootItem images = null;
-            RootItem videos = null;
-            RootItem audio = null;
-            RootItem downloads = null;
-
-            final List<RootInfo> clouds = new ArrayList<>();
-            final List<RootInfo> locals = new ArrayList<>();
+            final List<RootItem> libraries = new ArrayList<>();
+            final List<RootItem> clouds = new ArrayList<>();
+            final List<RootItem> locals = new ArrayList<>();
 
             for (RootInfo root : roots) {
-                if (root.isRecents()) {
-                    recents = new RootItem(root);
-                } else if (root.isExternalStorage()) {
-                    locals.add(root);
-                } else if (root.isDownloads()) {
-                    downloads = new RootItem(root);
-                } else if (root.isImages()) {
-                    images = new RootItem(root);
-                } else if (root.isVideos()) {
-                    videos = new RootItem(root);
-                } else if (root.isAudio()) {
-                    audio = new RootItem(root);
-                } else {
-                    clouds.add(root);
+                RootItem item = new RootItem(root);
+                switch (root.derivedType) {
+                    case RootInfo.TYPE_LOCAL:
+                        locals.add(item);
+                        break;
+                    case RootInfo.TYPE_CLOUD:
+                        clouds.add(item);
+                        break;
+                    default:
+                        libraries.add(item);
+                        break;
                 }
             }
 
             final RootComparator comp = new RootComparator();
-            Collections.sort(clouds, comp);
+            Collections.sort(libraries, comp);
             Collections.sort(locals, comp);
+            Collections.sort(clouds, comp);
 
-            if (recents != null) add(recents);
-
-            for (RootInfo cloud : clouds) {
-                add(new RootItem(cloud));
-            }
-
-            if (images != null) add(images);
-            if (videos != null) add(videos);
-            if (audio != null) add(audio);
-            if (downloads != null) add(downloads);
-
-            for (RootInfo local : locals) {
-                add(new RootItem(local));
-            }
+            addAll(libraries);
+            add(new SpacerItem());
+            addAll(locals);
+            addAll(clouds);
 
             if (includeApps != null) {
                 final PackageManager pm = context.getPackageManager();
@@ -348,9 +331,7 @@
 
                 if (apps.size() > 0) {
                     add(new SpacerItem());
-                    for (Item item : apps) {
-                        add(item);
-                    }
+                    addAll(apps);
                 }
             }
         }
@@ -387,15 +368,20 @@
         }
     }
 
-    public static class RootComparator implements Comparator<RootInfo> {
+    public static class RootComparator implements Comparator<RootItem> {
         @Override
-        public int compare(RootInfo lhs, RootInfo rhs) {
-            final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
+        public int compare(RootItem lhs, RootItem rhs) {
+            // Sort by root type, then title, then summary.
+            int score = lhs.root.derivedType - rhs.root.derivedType;
             if (score != 0) {
                 return score;
-            } else {
-                return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
             }
+            score = DocumentInfo.compareToIgnoreCaseNullable(lhs.root.title, rhs.root.title);
+            if (score != 0) {
+                return score;
+            }
+
+            return DocumentInfo.compareToIgnoreCaseNullable(lhs.root.summary, rhs.root.summary);
         }
     }
 }
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/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index ecf4d6c..17e6a80 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -44,6 +44,15 @@
     private static final int VERSION_INIT = 1;
     private static final int VERSION_DROP_TYPE = 2;
 
+    // The values of these constants determine the sort order of various roots in the RootsFragment.
+    public static final int TYPE_IMAGES = 1;
+    public static final int TYPE_VIDEO = 2;
+    public static final int TYPE_AUDIO = 3;
+    public static final int TYPE_RECENTS = 4;
+    public static final int TYPE_DOWNLOADS = 5;
+    public static final int TYPE_LOCAL = 6;
+    public static final int TYPE_CLOUD = 7;
+
     public String authority;
     public String rootId;
     public int flags;
@@ -57,6 +66,7 @@
     /** Derived fields that aren't persisted */
     public String[] derivedMimeTypes;
     public int derivedIcon;
+    public int derivedType;
 
     public RootInfo() {
         reset();
@@ -76,6 +86,7 @@
 
         derivedMimeTypes = null;
         derivedIcon = 0;
+        derivedType = 0;
     }
 
     @Override
@@ -158,14 +169,23 @@
         // TODO: remove these special case icons
         if (isExternalStorage()) {
             derivedIcon = R.drawable.ic_root_sdcard;
+            derivedType = TYPE_LOCAL;
         } else if (isDownloads()) {
             derivedIcon = R.drawable.ic_root_download;
+            derivedType = TYPE_DOWNLOADS;
         } else if (isImages()) {
             derivedIcon = R.drawable.ic_doc_image;
+            derivedType = TYPE_IMAGES;
         } else if (isVideos()) {
             derivedIcon = R.drawable.ic_doc_video;
+            derivedType = TYPE_VIDEO;
         } else if (isAudio()) {
             derivedIcon = R.drawable.ic_doc_audio;
+            derivedType = TYPE_AUDIO;
+        } else if (isRecents()) {
+            derivedType = TYPE_RECENTS;
+        } else {
+            derivedType = TYPE_CLOUD;
         }
     }
 
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
new file mode 100644
index 0000000..8baadba
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestDocumentsProvider
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := tests
+#LOCAL_SDK_VERSION := current
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..66988a1
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.documentsui.testing">
+    <application>
+        <provider android:name="TestDocumentsProvider"
+                android:authorities="com.android.documentsui.testing"
+                android:exported="true"
+                android:grantUriPermissions="true"
+                android:permission="android.permission.MANAGE_DOCUMENTS">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+</manifest>
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
new file mode 100644
index 0000000..63ff0de
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
@@ -0,0 +1,299 @@
+/*
+ * 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.testing;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.os.AsyncTask;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TestDocumentsProvider extends DocumentsProvider {
+    private static final String TAG = "TestDocumentsProvider";
+
+    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+            Root.COLUMN_ROOT_ID,
+            Root.COLUMN_FLAGS,
+            Root.COLUMN_ICON,
+            Root.COLUMN_TITLE,
+            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+    };
+
+    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID,
+            Document.COLUMN_MIME_TYPE,
+            Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_LAST_MODIFIED,
+            Document.COLUMN_FLAGS,
+            Document.COLUMN_SIZE,
+    };
+
+    private static String[] resolveRootProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+    }
+
+    private static String[] resolveDocumentProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+    }
+
+    @Override
+    public boolean onCreate() {
+        resetRoots();
+        return true;
+    }
+
+    @Override
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+
+        RowBuilder row = result.newRow();
+        row.add(Root.COLUMN_ROOT_ID, "local");
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
+        row.add(Root.COLUMN_TITLE, "TEST-Local");
+        row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary");
+        row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
+
+        row = result.newRow();
+        row.add(Root.COLUMN_ROOT_ID, "create");
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
+        row.add(Root.COLUMN_TITLE, "TEST-Create");
+        row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
+
+        return result;
+    }
+
+    private Map<String, Doc> mDocs = new HashMap<>();
+
+    private Doc mLocalRoot;
+    private Doc mCreateRoot;
+
+    private Doc buildDoc(String docId, String displayName, String mimeType) {
+        final Doc doc = new Doc();
+        doc.docId = docId;
+        doc.displayName = displayName;
+        doc.mimeType = mimeType;
+        mDocs.put(doc.docId, doc);
+        return doc;
+    }
+
+    public void resetRoots() {
+        Log.d(TAG, "resetRoots()");
+
+        mDocs.clear();
+
+        mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR);
+
+        mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR);
+        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+
+        {
+            Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1");
+            file1.contents = "fileone".getBytes();
+            file1.flags = Document.FLAG_SUPPORTS_WRITE;
+            mLocalRoot.children.add(file1);
+            mCreateRoot.children.add(file1);
+        }
+
+        {
+            Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2");
+            file2.contents = "filetwo".getBytes();
+            file2.flags = Document.FLAG_SUPPORTS_WRITE;
+            mLocalRoot.children.add(file2);
+            mCreateRoot.children.add(file2);
+        }
+
+        Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR);
+        mLocalRoot.children.add(dir1);
+
+        {
+            Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3");
+            file3.contents = "filethree".getBytes();
+            file3.flags = Document.FLAG_SUPPORTS_WRITE;
+            dir1.children.add(file3);
+        }
+
+        Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR);
+        mCreateRoot.children.add(dir2);
+
+        {
+            Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4");
+            file4.contents = "filefour".getBytes();
+            file4.flags = Document.FLAG_SUPPORTS_WRITE;
+            dir2.children.add(file4);
+        }
+    }
+
+    private static class Doc {
+        public String docId;
+        public int flags;
+        public String displayName;
+        public long size;
+        public String mimeType;
+        public long lastModified;
+        public byte[] contents;
+        public List<Doc> children = new ArrayList<>();
+
+        public void include(MatrixCursor result) {
+            final RowBuilder row = result.newRow();
+            row.add(Document.COLUMN_DOCUMENT_ID, docId);
+            row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+            row.add(Document.COLUMN_SIZE, size);
+            row.add(Document.COLUMN_MIME_TYPE, mimeType);
+            row.add(Document.COLUMN_FLAGS, flags);
+            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
+        }
+    }
+
+    @Override
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        for (Doc doc : mDocs.get(parentDocumentId).children) {
+            if (doc.docId.equals(documentId)) {
+                return true;
+            }
+            if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
+                return isChildDocument(doc.docId, documentId);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String createDocument(String parentDocumentId, String mimeType, String displayName)
+            throws FileNotFoundException {
+        final String docId = "doc:" + System.currentTimeMillis();
+        final Doc doc = buildDoc(docId, displayName, mimeType);
+        doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
+        mDocs.get(parentDocumentId).children.add(doc);
+        return docId;
+    }
+
+    @Override
+    public String renameDocument(String documentId, String displayName)
+            throws FileNotFoundException {
+        mDocs.get(documentId).displayName = displayName;
+        return null;
+    }
+
+    @Override
+    public void deleteDocument(String documentId) throws FileNotFoundException {
+        mDocs.remove(documentId);
+        for (Doc doc : mDocs.values()) {
+            doc.children.remove(documentId);
+        }
+    }
+
+    @Override
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        mDocs.get(documentId).include(result);
+        return result;
+    }
+
+    @Override
+    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        for (Doc doc : mDocs.get(parentDocumentId).children) {
+            doc.include(result);
+        }
+        return result;
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(String documentId, String mode,
+            CancellationSignal signal) throws FileNotFoundException {
+        final Doc doc = mDocs.get(documentId);
+        if (doc == null) {
+            throw new FileNotFoundException();
+        }
+        final ParcelFileDescriptor[] pipe;
+        try {
+            pipe = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+        if (mode.contains("w")) {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (doc) {
+                        try {
+                            final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+                                    pipe[0]);
+                            doc.contents = readFullyNoClose(is);
+                            is.close();
+                            doc.notifyAll();
+                        } catch (IOException e) {
+                            Log.w(TAG, "Failed to stream", e);
+                        }
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+            return pipe[1];
+        } else {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (doc) {
+                        try {
+                            final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
+                                    pipe[1]);
+                            while (doc.contents == null) {
+                                doc.wait();
+                            }
+                            os.write(doc.contents);
+                            os.close();
+                        } catch (IOException e) {
+                            Log.w(TAG, "Failed to stream", e);
+                        } catch (InterruptedException e) {
+                            Log.w(TAG, "Interuppted", e);
+                        }
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+            return pipe[0];
+        }
+    }
+
+    private static byte[] readFullyNoClose(InputStream in) throws IOException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int count;
+        while ((count = in.read(buffer)) != -1) {
+            bytes.write(buffer, 0, count);
+        }
+        return bytes.toByteArray();
+    }
+}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index 3f191a9..cf486b1 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -3,11 +3,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
+#LOCAL_SDK_VERSION := current
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator
 
 LOCAL_PACKAGE_NAME := DocumentsUITests
 LOCAL_INSTRUMENTATION_FOR := DocumentsUI
@@ -15,3 +16,5 @@
 LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
+
+include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk
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/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
new file mode 100644
index 0000000..1f4b751
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.view.MotionEvent;
+
+import java.util.concurrent.TimeoutException;
+
+public class FilesActivityUiTest extends InstrumentationTestCase {
+
+    private static final String TAG = "FilesActivityUiTest";
+    private static final String TARGET_PKG = "com.android.documentsui";
+    private static final String LAUNCHER_PKG = "com.android.launcher";
+    private static final int ONE_SECOND = 1000;
+    private static final int FIVE_SECONDS = 5 * ONE_SECOND;
+
+    private ActionBar mBar;
+    private UiDevice mDevice;
+    private Context mContext;
+
+    public void setUp() throws TimeoutException {
+        // Initialize UiDevice instance.
+        mDevice = UiDevice.getInstance(getInstrumentation());
+
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+
+        // Start from the home screen.
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS);
+
+        // Launch app.
+        mContext = getInstrumentation().getContext();
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear.
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS);
+        mDevice.waitForIdle();
+
+        mBar = new ActionBar();
+    }
+
+    public void testSwitchMode() throws Exception {
+        UiObject2 mode = mBar.gridMode(100);
+        if (mode != null) {
+            mode.click();
+            assertNotNull(mBar.listMode(ONE_SECOND));
+        } else {
+            mBar.listMode(100).click();
+            assertNotNull(mBar.gridMode(ONE_SECOND));
+        }
+    }
+
+    private class ActionBar {
+
+        public UiObject2 gridMode(int timeout) {
+            // Note that we're using By.desc rather than By.res, because of b/25285770
+            BySelector selector = By.desc("Grid view");
+            if (timeout > 0) {
+                mDevice.wait(Until.findObject(selector), timeout);
+            }
+            return mDevice.findObject(selector);
+        }
+
+        public UiObject2 listMode(int timeout) {
+            // Note that we're using By.desc rather than By.res, because of b/25285770
+            BySelector selector = By.desc("List view");
+            if (timeout > 0) {
+                mDevice.wait(Until.findObject(selector), timeout);
+            }
+            return mDevice.findObject(selector);
+        }
+    }
+}
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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9b1f103..c44a7960 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -225,4 +225,10 @@
 
     <!-- [CHAR LIMIT=NONE] Label of a running process that represents another user -->
     <string name="running_process_item_user_label">User: <xliff:g id="user_name">%1$s</xliff:g></string>
+
+    <!-- Launch defaults preference summary with some set [CHAR LIMIT=40] -->
+    <string name="launch_defaults_some">Some defaults set</string>
+    <!-- Launch defaults preference summary with none set [CHAR LIMIT=40] -->
+    <string name="launch_defaults_none">No defaults set</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
new file mode 100644
index 0000000..344bf62
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -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
+ */
+
+package com.android.settingslib.applications;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.usb.IUsbManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppUtils {
+    private static final String TAG = "AppUtils";
+
+    public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry,
+            IUsbManager usbManager, PackageManager pm, Context context) {
+        String packageName = appEntry.info.packageName;
+        boolean hasPreferred = hasPreferredActivities(pm, packageName)
+                || hasUsbDefaults(usbManager, packageName);
+        int status = pm.getIntentVerificationStatus(packageName, UserHandle.myUserId());
+        // consider a visible current link-handling state to be any explicitly designated behavior
+        boolean hasDomainURLsPreference =
+                status != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        return context.getString(hasPreferred || hasDomainURLsPreference
+                ? R.string.launch_defaults_some
+                : R.string.launch_defaults_none);
+    }
+
+    public static boolean hasUsbDefaults(IUsbManager usbManager, String packageName) {
+        try {
+            if (usbManager != null) {
+                return usbManager.hasDefaults(packageName, UserHandle.myUserId());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "mUsbManager.hasDefaults", e);
+        }
+        return false;
+    }
+
+    public static boolean hasPreferredActivities(PackageManager pm, String packageName) {
+        // Get list of preferred activities
+        List<ComponentName> prefActList = new ArrayList<>();
+        // Intent list cannot be null. so pass empty list
+        List<IntentFilter> intentList = new ArrayList<>();
+        pm.getPreferredActivities(intentList, prefActList, packageName);
+        Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list");
+        return prefActList.size() > 0;
+    }
+
+}
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/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
new file mode 100644
index 0000000..e5cdc85
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.dream;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class DreamBackend {
+    private static final String TAG = "DreamBackend";
+    private static final boolean DEBUG = false;
+
+    public static class DreamInfo {
+        public CharSequence caption;
+        public Drawable icon;
+        public boolean isActive;
+        public ComponentName componentName;
+        public ComponentName settingsComponentName;
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
+            sb.append('[').append(caption);
+            if (isActive)
+                sb.append(",active");
+            sb.append(',').append(componentName);
+            if (settingsComponentName != null)
+                sb.append("settings=").append(settingsComponentName);
+            return sb.append(']').toString();
+        }
+    }
+
+    private final Context mContext;
+    private final IDreamManager mDreamManager;
+    private final DreamInfoComparator mComparator;
+    private final boolean mDreamsEnabledByDefault;
+    private final boolean mDreamsActivatedOnSleepByDefault;
+    private final boolean mDreamsActivatedOnDockByDefault;
+
+    public DreamBackend(Context context) {
+        mContext = context;
+        mDreamManager = IDreamManager.Stub.asInterface(
+                ServiceManager.getService(DreamService.DREAM_SERVICE));
+        mComparator = new DreamInfoComparator(getDefaultDream());
+        mDreamsEnabledByDefault = context.getResources()
+                .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault);
+        mDreamsActivatedOnSleepByDefault = context.getResources()
+                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+        mDreamsActivatedOnDockByDefault = context.getResources()
+                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+    }
+
+    public List<DreamInfo> getDreamInfos() {
+        logd("getDreamInfos()");
+        ComponentName activeDream = getActiveDream();
+        PackageManager pm = mContext.getPackageManager();
+        Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
+        List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
+                PackageManager.GET_META_DATA);
+        List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            if (resolveInfo.serviceInfo == null)
+                continue;
+            DreamInfo dreamInfo = new DreamInfo();
+            dreamInfo.caption = resolveInfo.loadLabel(pm);
+            dreamInfo.icon = resolveInfo.loadIcon(pm);
+            dreamInfo.componentName = getDreamComponentName(resolveInfo);
+            dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
+            dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
+            dreamInfos.add(dreamInfo);
+        }
+        Collections.sort(dreamInfos, mComparator);
+        return dreamInfos;
+    }
+
+    public ComponentName getDefaultDream() {
+        if (mDreamManager == null)
+            return null;
+        try {
+            return mDreamManager.getDefaultDreamComponent();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get default dream", e);
+            return null;
+        }
+    }
+
+    public CharSequence getActiveDreamName() {
+        ComponentName cn = getActiveDream();
+        if (cn != null) {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                ServiceInfo ri = pm.getServiceInfo(cn, 0);
+                if (ri != null) {
+                    return ri.loadLabel(pm);
+                }
+            } catch (PackageManager.NameNotFoundException exc) {
+                return null; // uninstalled?
+            }
+        }
+        return null;
+    }
+
+    public boolean isEnabled() {
+        return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
+    }
+
+    public void setEnabled(boolean value) {
+        logd("setEnabled(%s)", value);
+        setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
+    }
+
+    public boolean isActivatedOnDock() {
+        return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                mDreamsActivatedOnDockByDefault);
+    }
+
+    public void setActivatedOnDock(boolean value) {
+        logd("setActivatedOnDock(%s)", value);
+        setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value);
+    }
+
+    public boolean isActivatedOnSleep() {
+        return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                mDreamsActivatedOnSleepByDefault);
+    }
+
+    public void setActivatedOnSleep(boolean value) {
+        logd("setActivatedOnSleep(%s)", value);
+        setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
+    }
+
+    private boolean getBoolean(String key, boolean def) {
+        return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
+    }
+
+    private void setBoolean(String key, boolean value) {
+        Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
+    }
+
+    public void setActiveDream(ComponentName dream) {
+        logd("setActiveDream(%s)", dream);
+        if (mDreamManager == null)
+            return;
+        try {
+            ComponentName[] dreams = { dream };
+            mDreamManager.setDreamComponents(dream == null ? null : dreams);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to set active dream to " + dream, e);
+        }
+    }
+
+    public ComponentName getActiveDream() {
+        if (mDreamManager == null)
+            return null;
+        try {
+            ComponentName[] dreams = mDreamManager.getDreamComponents();
+            return dreams != null && dreams.length > 0 ? dreams[0] : null;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get active dream", e);
+            return null;
+        }
+    }
+
+    public void launchSettings(DreamInfo dreamInfo) {
+        logd("launchSettings(%s)", dreamInfo);
+        if (dreamInfo == null || dreamInfo.settingsComponentName == null)
+            return;
+        mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+    }
+
+    public void preview(DreamInfo dreamInfo) {
+        logd("preview(%s)", dreamInfo);
+        if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
+            return;
+        try {
+            mDreamManager.testDream(dreamInfo.componentName);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to preview " + dreamInfo, e);
+        }
+    }
+
+    public void startDreaming() {
+        logd("startDreaming()");
+        if (mDreamManager == null)
+            return;
+        try {
+            mDreamManager.dream();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to dream", e);
+        }
+    }
+
+    private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
+        if (resolveInfo == null || resolveInfo.serviceInfo == null)
+            return null;
+        return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+    }
+
+    private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) {
+        if (resolveInfo == null
+                || resolveInfo.serviceInfo == null
+                || resolveInfo.serviceInfo.metaData == null)
+            return null;
+        String cn = null;
+        XmlResourceParser parser = null;
+        Exception caughtException = null;
+        try {
+            parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA);
+            if (parser == null) {
+                Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
+                return null;
+            }
+            Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            String nodeName = parser.getName();
+            if (!"dream".equals(nodeName)) {
+                Log.w(TAG, "Meta-data does not start with dream tag");
+                return null;
+            }
+            TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
+            cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity);
+            sa.recycle();
+        } catch (PackageManager.NameNotFoundException|IOException|XmlPullParserException e) {
+            caughtException = e;
+        } finally {
+            if (parser != null) parser.close();
+        }
+        if (caughtException != null) {
+            Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+            return null;
+        }
+        if (cn != null && cn.indexOf('/') < 0) {
+            cn = resolveInfo.serviceInfo.packageName + "/" + cn;
+        }
+        return cn == null ? null : ComponentName.unflattenFromString(cn);
+    }
+
+    private static void logd(String msg, Object... args) {
+        if (DEBUG)
+            Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
+    }
+
+    private static class DreamInfoComparator implements Comparator<DreamInfo> {
+        private final ComponentName mDefaultDream;
+
+        public DreamInfoComparator(ComponentName defaultDream) {
+            mDefaultDream = defaultDream;
+        }
+
+        @Override
+        public int compare(DreamInfo lhs, DreamInfo rhs) {
+            return sortKey(lhs).compareTo(sortKey(rhs));
+        }
+
+        private String sortKey(DreamInfo di) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
+            sb.append(di.caption);
+            return sb.toString();
+        }
+    }
+}
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/drawable/qs_customizer_background.xml b/packages/SystemUI/res/drawable/qs_customizer_background.xml
new file mode 100644
index 0000000..6bb27cc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_customizer_background.xml
@@ -0,0 +1,19 @@
+<!--
+    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.
+-->
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/qs_detail_transition" />
+    <item android:drawable="?android:attr/windowBackground" />
+</transition>
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 dc928c7..f430fa5 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -19,13 +19,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:paddingBottom="@dimen/navigation_bar_size"
-    android:background="?android:attr/windowBackground">
+    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"
@@ -71,15 +71,15 @@
             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
-        android:layout_width="match_parent"
+        android:layout_width="@dimen/notification_panel_width"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:paddingTop="8dp"
+        android:paddingTop="12dp"
         android:paddingBottom="8dp"
         android:elevation="2dp">
 
@@ -87,7 +87,9 @@
             android:id="@+id/quick_settings_panel"
             android:background="#0000"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/notification_side_padding"
+            android:layout_marginRight="@dimen/notification_side_padding" />
 
     </com.android.systemui.tuner.AutoScrollView>
 
@@ -102,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
new file mode 100644
index 0000000..6187070
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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. 
+** 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.
+-->
+
+<!-- Extends RelativeLayout -->
+<com.android.systemui.statusbar.phone.QuickStatusBarHeader
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/header"
+    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"
+    android:clickable="true"
+    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="wrap_content"
+        android:layout_height="match_parent"
+        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"
+            android:layout_height="@dimen/status_bar_header_height"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+            <com.android.systemui.statusbar.phone.SettingsButton android:id="@+id/settings_button"
+                style="@android:style/Widget.Material.Button.Borderless"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/ripple_drawable"
+                android:src="@drawable/ic_settings"
+                android:contentDescription="@string/accessibility_desc_settings" />
+            <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/tuner_icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingStart="36dp"
+                android:tint="#4DFFFFFF"
+                android:tintMode="src_in"
+                android:visibility="invisible"
+                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"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        />
+
+    <com.android.systemui.statusbar.AlphaOptimizedImageView
+        android:id="@+id/qs_detail_header_progress"
+        android:src="@drawable/indeterminate_anim"
+        android:alpha="0"
+        android:background="@color/qs_detail_progress_track"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        systemui:hasOverlappingRendering="false"
+        />
+
+    <TextView
+        android:id="@+id/header_debug_info"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:fontFamily="sans-serif-condensed"
+        android:textSize="11dp"
+        android:textStyle="bold"
+        android:textColor="#00A040"
+        android:padding="2dp"
+        />
+
+</com.android.systemui.statusbar.phone.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f7bbce0..bfa13e2 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -98,7 +98,11 @@
             layout="@layout/keyguard_bottom_area"
             android:visibility="gone" />
 
-    <include layout="@layout/status_bar_expanded_header" />
+    <ViewStub
+        android:id="@+id/status_bar_header"
+        android:layout_width="@dimen/notification_panel_width"
+        android:layout_height="@dimen/status_bar_header_height"
+        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/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 c7c7f1a..41ed64c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Wys horlosiesekondes op die statusbalk. Sal batterylewe dalk beïnvloed."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Herrangskik Kitsinstellings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Wys helderheid in Kitsinstellings"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Gebruik verdeling 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 f49f5bc..673710d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"የሰዓት ሰከንዶችን በሁኔታ አሞሌ ውስጥ አሳይ። በባትሪ ዕድሜ ላይ ተጽዕኖ ሊኖርው ይችል ይሆናል።"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ፈጣን ቅንብሮችን ዳግም ያደራጁ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"በፈጣን ቅንብሮች ውስጥ ብሩህነትን አሳይ"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"በፈጣን ቅንብሮች ውስጥ ምልክት መጥሪያን ይጠቀሙ"</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 aaf20cc..c2c7e65 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -443,6 +443,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"عرض ثواني الساعة في شريط الحالة. قد يؤثر ذلك في عمر البطارية."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"إعادة ترتيب الإعدادات السريعة"</string>
     <string name="show_brightness" msgid="6613930842805942519">"عرض السطوع في الإعدادات السريعة"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"استخدام ترقيم الصفحات في الإعدادات السريعة"</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 0efc82b..a949dbb 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Saatın saniyəsini status panelində göstərin. Batareyaya təsir edə bilər."</string>
     <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="qs_paging" msgid="7020133150248666132">"Sürətli Ayarlarda səhifə nömrələməsindən istifadə edin"</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 6805779..a59f4c5 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Показване на секундите на часовника в лентата на състоянието. Може да се отрази на живота на батерията."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Пренареждане на бързите настройки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показване на яркостта от бързите настройки"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Използване на разделянето на страници от бързите настройки"</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 c0ffe2c..f4adc14 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"স্থিতি দন্ডে ঘড়ির সেকেন্ড দেখায়৷ ব্যাটারি লাইফকে প্রভাবিত করতে পারে৷"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"দ্রুত সেটিংসে পুনরায় সাজান"</string>
     <string name="show_brightness" msgid="6613930842805942519">"দ্রুত সেটিংসে উজ্জ্বলতা দেখান"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"দ্রুত সেটিংসে পেজিং ব্যবহার করুন"</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 c955f34..00f96a7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -170,7 +170,7 @@
     <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificació omesa."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Àrea de notificacions"</string>
-    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configuració ràpida."</string>
+    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configuració ràpida"</string>
     <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Pantalla de bloqueig"</string>
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"Configuració"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Visió general"</string>
@@ -337,7 +337,7 @@
     <string name="user_new_user_name" msgid="426540612051178753">"Usuari nou"</string>
     <string name="guest_nickname" msgid="8059989128963789678">"Convidat"</string>
     <string name="guest_new_guest" msgid="600537543078847803">"Afegeix un convidat"</string>
-    <string name="guest_exit_guest" msgid="7187359342030096885">"Suprimeix l\'usuari"</string>
+    <string name="guest_exit_guest" msgid="7187359342030096885">"Suprimeix el convidat"</string>
     <string name="guest_exit_guest_dialog_title" msgid="8480693520521766688">"Vols suprimir el convidat?"</string>
     <string name="guest_exit_guest_dialog_message" msgid="4155503224769676625">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
     <string name="guest_exit_guest_dialog_remove" msgid="7402231963862520531">"Suprimeix"</string>
@@ -439,8 +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_paging" msgid="7020133150248666132">"Utilitza la paginació 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 18fd994..e429a06 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -443,6 +443,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Na stavovém řádku se bude zobrazovat sekundová ručička. Může být ovlivněna výdrž baterie."</string>
     <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="qs_paging" msgid="7020133150248666132">"Použít v Rychlém nastavení stránkování"</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 5a2dd33..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>
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Brug sidelayout 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 a8143a4..b113083 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Uhrsekunden in der Statusleiste anzeigen. Kann sich auf die Akkulaufzeit auswirken."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Schnelleinstellungen neu anordnen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Helligkeit in den Schnelleinstellungen anzeigen"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Seitenlayout in den Schnelleinstellungen verwenden"</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 6031d3b..27a019c 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Εμφάνιση δευτερολέπτων ρολογιού στη γραμμή κατάστασης. Ενδέχεται να επηρεάσει τη διάρκεια ζωής της μπαταρίας."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Αναδιάταξη Γρήγορων ρυθμίσεων"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Εμφάνιση φωτεινότητας στις Γρήγορες ρυθμίσεις"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Χρήση τηλεειδοποίησης στις Γρήγορες ρυθμίσεις"</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 bef7661..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Use paging 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 bef7661..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Use paging 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 bef7661..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Use paging 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/donottranslate.xml b/packages/SystemUI/res/values-en/donottranslate.xml
deleted file mode 100644
index 9f04e1f..0000000
--- a/packages/SystemUI/res/values-en/donottranslate.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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>
-    <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
-    <item type="string" name="keyguard_indication_charging_time_fast_if_translated">@string/keyguard_indication_charging_time_fast</item>
-    <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
-    <item type="string" name="keyguard_indication_charging_time_slowly_if_translated">@string/keyguard_indication_charging_time_slowly</item>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 1590265..bca484a 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar la duración de la batería."</string>
     <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="qs_paging" msgid="7020133150248666132">"Usar la paginación 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 082e762..3c89db8 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar a la duración de la batería."</string>
     <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="qs_paging" msgid="7020133150248666132">"Utilizar paginación 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 4add147..01e5848 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Olekuribal kella sekundite kuvamine. See võib mõjutada aku kasutusaega."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Korralda kiirseaded ümber"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Kuva kiirseadetes heledus"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Kasuta kiirseadetes lehe paigutust"</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 4f5c9f2..613b44a 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Erakutsi erlojuko segundoak egoera-barran. Baliteke bateria gehiago erabiltzea."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Berrantolatu ezarpen bizkorrak"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Erakutsi distira Ezarpen bizkorretan"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Erabili orriak pasatzeko diseinu berria 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 6fa7e1c..9967225 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -67,12 +67,12 @@
     <string name="usb_debugging_secondary_user_message" msgid="8572228137833020196">"‏کاربری که درحال‌حاضر در این دستگاه وارد سیستم شده نمی‌تواند اشکال‌زدای USB را روشن کند. برای استفاده از این ویژگی، لطفاً به کاربر «سرپرست» تغییر حالت دهید."</string>
     <string name="compat_mode_on" msgid="6623839244840638213">"بزرگ‌نمایی برای پر کردن صفحه"</string>
     <string name="compat_mode_off" msgid="4434467572461327898">"گسترده کردن برای پر کردن صفحه"</string>
-    <string name="screenshot_saving_ticker" msgid="7403652894056693515">"در حال ذخیره تصویر صفحه..."</string>
-    <string name="screenshot_saving_title" msgid="8242282144535555697">"در حال ذخیره تصویر صفحه..."</string>
-    <string name="screenshot_saving_text" msgid="2419718443411738818">"تصویر صفحه ذخیره شد."</string>
-    <string name="screenshot_saved_title" msgid="6461865960961414961">"تصویر صفحه گرفته شد."</string>
+    <string name="screenshot_saving_ticker" msgid="7403652894056693515">"در حال ذخیره عکس صفحه‌نمایش..."</string>
+    <string name="screenshot_saving_title" msgid="8242282144535555697">"در حال ذخیره عکس صفحه‌نمایش..."</string>
+    <string name="screenshot_saving_text" msgid="2419718443411738818">"عکس صفحه‌نمایش ذخیره شد."</string>
+    <string name="screenshot_saved_title" msgid="6461865960961414961">"عکس صفحه‌نمایش گرفته شد."</string>
     <string name="screenshot_saved_text" msgid="1152839647677558815">"برای مشاهده عکس صفحه‌نمایشتان، لمس کنید."</string>
-    <string name="screenshot_failed_title" msgid="705781116746922771">"تصویر صفحه گرفته نشد."</string>
+    <string name="screenshot_failed_title" msgid="705781116746922771">"عکس صفحه‌نمایش گرفته نشد."</string>
     <string name="screenshot_failed_text" msgid="1260203058661337274">"به دلیل فضای ذخیره‌سازی کم یا عدم اجازه برنامه یا سازمانتان، نمی‌توان از صفحه عکس گرفت."</string>
     <string name="usb_preference_title" msgid="6551050377388882787">"‏گزینه‌های انتقال فایل USB"</string>
     <string name="use_mtp_button_title" msgid="4333504413563023626">"‏نصب به‌عنوان دستگاه پخش رسانه (MTP)"</string>
@@ -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>
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ثانیه‌های ساعت را در نوار وضعیت نشان می‌دهد. ممکن است بر ماندگاری باتری تأثیر بگذارد."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ترتیب مجدد در تنظیمات سریع"</string>
     <string name="show_brightness" msgid="6613930842805942519">"نمایش روشنایی در تنظیمات سریع"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"استفاده از صفحه‌بندی در تنظیمات سریع"</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 655cf31..2d138cb 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Näytä sekunnit tilapalkin kellossa. Tämä voi vaikuttaa akun kestoon."</string>
     <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="qs_paging" msgid="7020133150248666132">"Käytä sivutusta 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 2f2c331..979ee35 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes sur l\'horloge dans la barre d\'état. Cela peut réduire l\'autonomie de la pile."</string>
     <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="qs_paging" msgid="7020133150248666132">"Utiliser la pagination 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 9f34431..9ab6613 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes dans la barre d\'état. Cela risque de réduire l\'autonomie de la batterie."</string>
     <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="qs_paging" msgid="7020133150248666132">"Utiliser mise en page 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 df90ef4..bc44150 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra os segundos do reloxo na barra de estado. Pode influír na duración da batería."</string>
     <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="qs_paging" msgid="7020133150248666132">"Utilizar paxinación 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 42ee460..ba8c517 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ઘડિયાળ સેકન્ડ સ્થિતિ બારમાં બતાવો. બૅટરીની આવરદા પર અસર કરી શકે છે."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ઝડપી સેટિંગ્સને ફરીથી ગોઠવો"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ઝડપી સેટિંગ્સમાં તેજ બતાવો"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ઝડપી સેટિંગ્સમાં પૃષ્ઠાંકનનો ઉપયોગ કરો"</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 86f536d..669ce5a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"स्थिति बार में घड़ी के सेकंड दिखाएं. इससे बैटरी के जीवनकाल पर प्रभाव पड़ सकता है."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"त्वरित सेटिंग को पुन: व्यवस्थित करें"</string>
     <string name="show_brightness" msgid="6613930842805942519">"त्वरित सेटिंग में स्क्रीन की रोशनी दिखाएं"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"त्वरित सेटिंग में पेजिंग का उपयोग करें"</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 043e7ad..e928cbc 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -440,6 +440,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Prikazuju se sekunde na satu na traci statusa. Može utjecati na trajanje baterije."</string>
     <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="qs_paging" msgid="7020133150248666132">"Upotrijebi redni broj stranice 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 e2b0812..a347ede 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Másodpercek megjelenítése az állapotsor óráján. Ez hatással lehet az akkumulátor üzemidejére."</string>
     <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="qs_paging" msgid="7020133150248666132">"Oldalelrendezés használata 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 f8fc232..18f6c88 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -325,8 +325,8 @@
     <string name="interruption_level_priority_twoline" msgid="1564715335217164124">"Միայն\nկարևորները"</string>
     <string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"Միայն\nզարթուցիչ"</string>
     <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g> մինչև լրիվ լիցքավորումը)"</string>
-    <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Արագ լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>՝ մինչև լցվելը)"</string>
-    <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Դանդաղ լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>՝ մինչև լցվելը)"</string>
+    <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Արագ լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>՝ մինչև ավարտ)"</string>
+    <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Դանդաղ լիցքավորում (<xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>՝ մինչև ավարտ)"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Անջատել օգտվողին"</string>
     <string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Փոխել օգտվողին. ներկայիս օգտվողն է՝ <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
     <string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Ընթացիկ օգտվողը՝ <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Ցույց տալ ժամացույցի վայրկյանները կարգավիճակի տողում: Կարող է ազդել մարտկոցի աշխատանքի ժամանակի վրա:"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Վերադասավորել Արագ կարգավորումները"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ցույց տալ պայծառությունն Արագ կարգավորումներում"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Օգտագործել էջերի դասավորությունը Արագ կարգավորումներում"</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 7dbcda9..afcac63 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Tampilkan detik jam di bilah status. Dapat memengaruhi masa pakai baterai."</string>
     <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="qs_paging" msgid="7020133150248666132">"Gunakan pembagian laman 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 b28b6fd..48d0d1a 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Sýna sekúndur á klukku í stöðustikunni. Getur haft áhrif á endingu rafhlöðu."</string>
     <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="qs_paging" msgid="7020133150248666132">"Nota síðuskoðun í 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 54e6f56..dea0330 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra i secondi nell\'orologio nella barra di stato. Ciò potrebbe ridurre la durata della carica della batteria."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Riorganizza Impostazioni rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostra luminosità in Impostazioni rapide"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Utilizza nuovo layout 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 c959feb..772a3fa 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"הצג שניות בשעון בשורת הסטטוס. פעולה זו עשויה להשפיע על אורך חיי הסוללה."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
     <string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"השתמש באפשרות הדפדוף בהגדרות המהירות"</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 29476d8..6e570e3 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ステータスバーに時計の秒を表示します。電池使用量に影響する可能性があります。"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"クイック設定を並べ替え"</string>
     <string name="show_brightness" msgid="6613930842805942519">"クイック設定に明るさ調整バーを表示する"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"クイック設定でページ設定を使用する"</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 d83c9e9..6f26123 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"საათის წამების ჩვენება სტატუსის ზოლში. შეიძლება გავლენა იქონიოს ბატარეაზე."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"სწრაფი პარამეტრების გადაწყობა"</string>
     <string name="show_brightness" msgid="6613930842805942519">"სიკაშკაშის ჩვენება სწრაფ პარამეტრებში"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"გამოიყენეთ გვერდების დანომვრა სწრაფ პარამეტრებში"</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 5c066b3..2668de4 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Күйін көрсету жолағында сағат секундтарын көрсету. Батареяның қызмет көрсету мерзіміне әсер етуі мүмкін."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Жылдам параметрлерді қайта реттеу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Жылдам параметрлерде жарықтықты көрсету"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Жылдам параметрлерде беттерді нөмірлеуді пайдалану"</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 4c469b0..08d761f 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"បង្ហាញវិនាទីនៅលើរបារស្ថានភាពអាចនឹងប៉ះពាល់ដល់ថាមពលថ្ម។"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"រៀបចំការកំណត់រហ័សឡើងវិញ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"បង្ហាញកម្រិតពន្លឺនៅក្នុងការកំណត់រហ័ស"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ប្រើការចុះទំព័រនៅក្នុងការកំណត់រហ័ស"</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 4215a91..2cf1697 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯಲ್ಲಿ ಗಡಿಯಾರ ಸೆಕೆಂಡುಗಳನ್ನು ತೋರಿಸು. ಇದಕ್ಕೆ ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಯು ಪರಿಣಾಮಬೀರಬಹುದು."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳನ್ನು ಮರುಹೊಂದಿಸಿ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳಲ್ಲಿ ಪ್ರಖರತೆಯನ್ನು ತೋರಿಸಿ"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳಲ್ಲಿ ಪುಟಗಳನ್ನು ಬಳಸಿ"</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 8ee5946..4e8be28 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"상태 표시줄에 시계 초 단위를 표시합니다. 배터리 수명에 영향을 줄 수도 있습니다."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"빠른 설정 재정렬"</string>
     <string name="show_brightness" msgid="6613930842805942519">"빠른 설정에서 밝기 표시"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"빠른 설정에서 페이지 레이아웃 사용"</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 ce0afd4..fc6897f 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Абал тилкесинен сааттын секунддары көрсөтүлсүн. Батареянын кубаты көбүрөөк сарпталышы мүмкүн."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Ыкчам жөндөөлөрдү кайра коюу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ыкчам жөндөөлөрдөн жарык деңгээлин көрсөтүү"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Ыкчам жөндөөлөрдөн баракты номерлөөнү колдонуу"</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 b45395e..6bb2043 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ສະ​ແດງວິ​ນາ​ທີ​ໂມງ​ຢູ່​ໃນ​ແຖບ​ສະ​ຖາ​ນະ. ອາດ​ຈະ​ມີ​ຜົນ​ກະ​ທົບ​ຕໍ່​ອາ​ຍຸ​ແບັດ​ເຕີ​ຣີ."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ຈັດ​ວາງ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ​ຄືນ​ໃໝ່"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ສະ​ແດງ​ຄວາມ​ແຈ້ງ​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ໃຊ້​ການ​ໃສ່​ໜ້າ​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</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 194668b..8883cb6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Rodyti laikrodžio sekundes būsenos juostoje. Tai gali paveikti akumuliatoriaus naudojimo laiką."</string>
     <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="qs_paging" msgid="7020133150248666132">"Naudoti puslapių kaitą 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 2b3be58..47ca06a 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -440,6 +440,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Statusa joslā rādīt pulksteņa sekundes. Var ietekmēt akumulatora darbības laiku."</string>
     <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="qs_paging" msgid="7020133150248666132">"Izmantot lapošanu ā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 912c30f..891ffd8 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Прикажи ги секундите на часовникот на статусната лента. Може да влијае на траењето на батеријата."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуредете ги Брзи поставки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажете ја осветленоста во Брзи поставки"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Користете прелистување страници во Брзи поставки"</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 c85ecc7..64d4184 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"സ്റ്റാറ്റസ് ബാറിൽ ക്ലോക്ക് സെക്കൻഡ് കാണിക്കുന്നത് ബാറ്ററിയുടെ ലൈഫിനെ ബാധിക്കാം."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ദ്രുത ക്രമീകരണം പുനഃസജ്ജീകരിക്കുക"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ദ്രുത ക്രമീകരണത്തിൽ തെളിച്ചം കാണിക്കുക"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ദ്രുത്ര ക്രമീകരണത്തിൽ പേജിംഗ് ഉപയോഗിക്കുക"</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 6b29aff..740ee0d 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -437,6 +437,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Статус талбарт цагийн секундыг харуулах. Энэ нь тэжээлийн цэнэгт нөлөөлж болно."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Түргэн тохиргоог дахин засварлах"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Түргэн тохиргоонд гэрэлтүүлэг харах"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Хуудаслалтыг түргэн тохиргоонд ашиглаарай"</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 9d518a0..00d7bc8 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"स्टेटस बारमध्‍ये घड्‍याळ सेकंद दर्शवा. कदाचित बॅटरी आयुष्‍य प्रभावित होऊ शकते."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिंग्जची पुनर्रचना करा"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिंग्जमध्‍ये चमक दर्शवा"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"द्रुत सेटिंग्जमध्ये लिखाण वापरा"</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 10dca3e..10865dd 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Tunjukkan saat jam dalam bar status. Mungkin menjejaskan hayat bateri."</string>
     <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="qs_paging" msgid="7020133150248666132">"Gunakan penghalaman 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 adfd9f8..5cb6087 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"အခြေအနေပြနေရာမှာ နာရီ စက္ကန့်များကို ပြပါ။ ဘက်ထရီ သက်တမ်းကို အကျိုးသက်ရောက်နိုင်တယ်။"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"အမြန် ဆက်တင်များကို ပြန်စီစဉ်ရန်"</string>
     <string name="show_brightness" msgid="6613930842805942519">"အမြန် ဆက်တင်များထဲက တောက်ပမှုကို ပြရန်"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"အမြန် ဆက်တင်များထဲတွင် စာမျက်နှာ ပုံစံချမှုကို အသုံးပြုပါ"</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 ecfb7f2..39b404a 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statusfeltet på klokken. Det kan påvirke batteritiden."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Omorganiser hurtiginnstillingene"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i hurtiginnstillingene"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Bruk sidetall 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 d997aff..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>
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"वस्तुस्थिति पट्टीको घडीमा सेकेन्ड देखाउनुहोस्। ब्याट्री आयु प्रभावित हुन सक्छ।"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"द्रुत सेटिङहरूमा पेजिंग प्रयोग गर्नुहोस्"</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 c6bab06..6bbb340 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Klokseconden op de statusbalk weergeven. Kan van invloed zijn op de accuduur."</string>
     <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="qs_paging" msgid="7020133150248666132">"Paginering gebruiken 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 74e5fcb..61b68c1 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"ਸਥਿਤੀ ਬਾਰ ਵਿੱਚ ਘੜੀ ਸਕਿੰਟ ਦਿਖਾਓ। ਬੈਟਰੀ ਸਮਰੱਥਾ ਤੇ ਅਸਰ ਪੈ ਸਕਦਾ ਹੈ।"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਕ੍ਰਮ ਦਿਓ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਮਕ ਦਿਖਾਓ"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਪੇਜਿੰਗ ਵਰਤੋ"</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 e04fd71..caa3081 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Pokaż sekundy na zegarku na pasku stanu. Może mieć wpływ na czas pracy baterii."</string>
     <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="qs_paging" msgid="7020133150248666132">"Użyj stronicowania 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 2a8e3ab..b82eb32 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
     <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="qs_paging" msgid="7020133150248666132">"Usar paginação 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 8453e5b..c137908 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de estado. Pode afetar a autonomia da bateria."</string>
     <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="qs_paging" msgid="7020133150248666132">"Utilizar paginação 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 2a8e3ab..b82eb32 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
     <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="qs_paging" msgid="7020133150248666132">"Usar paginação 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 d85e791..b5819ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -159,7 +159,7 @@
     <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"Se obţine GPS."</string>
     <string name="accessibility_tty_enabled" msgid="4613200365379426561">"TeleTypewriter activat."</string>
     <string name="accessibility_ringer_vibrate" msgid="666585363364155055">"Vibrare sonerie."</string>
-    <string name="accessibility_ringer_silent" msgid="9061243307939135383">"Sonerie silenţioasă."</string>
+    <string name="accessibility_ringer_silent" msgid="9061243307939135383">"Sonerie silențioasă."</string>
     <!-- no translation found for accessibility_casting (6887382141726543668) -->
     <skip />
     <string name="accessibility_recents_item_will_be_dismissed" msgid="395770242498031481">"Închideți <xliff:g id="APP">%s</xliff:g>."</string>
@@ -440,6 +440,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Afișează secundele pe ceas în bara de stare. Poate afecta autonomia bateriei."</string>
     <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="qs_paging" msgid="7020133150248666132">"Folosiți paginarea î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 39d8617..0806684 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -443,6 +443,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Показывать в строке состояния время с точностью до секунды (заряд батареи может расходоваться быстрее)."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Изменить порядок Быстрых настроек"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Добавить яркость в Быстрые настройки"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Разбить Быстрые настройки на страницы"</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 51bb047..39147ff 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"තත්ත්ව තීරුවෙහි ඔරලෝසු තත්පර පෙන්වන්න. බැටරි ආයු කාලයට බලපෑමට හැකිය."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"ඉක්මන් සැකසීම් යළි පිළිවෙළට සකසන්න"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ඉක්මන් සැකසීම්වල දීප්තිය පෙන්වන්න"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ඉක්මන් සැකසීම්වල පිටු පිරිසැලසුම් භාවිත කරන්න"</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 2886e36..86c8fe2 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -443,6 +443,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Zobrazí sekundy v stavovom riadku. Môže to ovplyvňovať výdrž batérie."</string>
     <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="qs_paging" msgid="7020133150248666132">"Použite stránkovanie 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 e325ef1..c5dd188 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Prikaže sekunde pri uri v vrstici stanja. To lahko vpliva na čas delovanja pri akumulatorskem napajanju."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Preuredi hitre nastavitve"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Prikaz svetlosti v hitrih nastavitvah"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Uporaba postavitve strani 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 5bcf941..fcd65f7 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Trego sekondat e orës në shiritin e statusit. Mund të ndikojë te jeta e baterisë."</string>
     <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="qs_paging" msgid="7020133150248666132">"Përdor faqosjen 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 522b2af..2c55a5c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -440,6 +440,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Секунде на сату се приказују на статусној траци. То може да утиче на трајање батерије."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуреди Брза подешавања"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажи осветљеност у Брзим подешавањима"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Користи листање страница у Брзим подешавањима"</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 d91335c..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>
@@ -393,7 +393,7 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"Utöka"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Komprimera"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Skärmen har fästs"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"Detta visar skärmen tills du lossar den. Tryck länge på bakåtknappen och Översikt samtidigt om du vill lossa skärmen."</string>
+    <string name="screen_pinning_description" msgid="1346522416878235405">"Med den här funktionen är skärmen synlig tills du lossar den. Tryck länge på Tillbaka och Översikt samtidigt om du vill lossa skärmen."</string>
     <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Detta visar skärmen tills du lossar den. Tryck länge på Översikt om du vill lossa skärmen."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tack"</string>
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Visa klocksekunder i statusfältet. Detta kan påverka batteritiden."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Ordna snabbinställningarna"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Visa ljusstyrka i snabbinställningarna"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Använd sidindelning 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 1cf4293..bbe271d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Onyesha sekunde za saa katika sehemu ya arifa. Inaweza kuathiri muda wa matumizi ya betri."</string>
     <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="qs_paging" msgid="7020133150248666132">"Tumia nambari za ukurasa 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 670bccc..08bef13 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"நிலைப் பட்டியில் கடிகார வினாடிகளைக் காட்டும். பேட்டரியின் ஆயுளைக் குறைக்கலாம்."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"விரைவு அமைப்புகளை மறுவரிசைப்படுத்து"</string>
     <string name="show_brightness" msgid="6613930842805942519">"விரைவு அமைப்புகளில் ஒளிர்வுப் பட்டியைக் காட்டு"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"விரைவு அமைப்புகளில் புதிய பக்கத் தளவமைப்பைப் பயன்படுத்து"</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 bbd9979..46bc704 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"స్థితి పట్టీలో గడియారం సెకన్లు చూపుతుంది. బ్యాటరీ శక్తి ప్రభావితం చేయవచ్చు."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"శీఘ్ర సెట్టింగ్‌ల ఏర్పాటు క్రమం మార్చు"</string>
     <string name="show_brightness" msgid="6613930842805942519">"శీఘ్ర సెట్టింగ్‌ల్లో ప్రకాశం చూపు"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"శీఘ్ర సెట్టింగ్‌ల్లో పేజింగ్‌ను ఉపయోగించు"</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 2486519..f7e7be0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"แสดงวินาทีของนาฬิกาในแถบสถานะ อาจส่งผลต่ออายุแบตเตอรี"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"จัดเรียงการตั้งค่าด่วนใหม่"</string>
     <string name="show_brightness" msgid="6613930842805942519">"แสดงความสว่างในการตั้งค่าด่วน"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"ใช้การแบ่งหน้าในการตั้งค่าด่วน"</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 3d1243e..92a6cdc 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Ipakita ang mga segundo ng orasan sa status bar. Maaaring makaapekto sa tagal ng baterya."</string>
     <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="qs_paging" msgid="7020133150248666132">"Gamitin ang paging 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 4dadfbc..2b9d6da 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Durum çubuğunda saatin saniyelerini gösterir. Pil ömrünü etkileyebilir."</string>
     <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="qs_paging" msgid="7020133150248666132">"Hızlı Ayarlar\'da sayfa ayırmayı kullan"</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 d158dbe..c040439 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Показувати секунди на годиннику в рядку стану. Акумулятор може розряджатися швидше."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Упорядкувати швидкі налаштування"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показувати панель яскравості у швидких налаштуваннях"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Показувати швидкі налаштування у вигляді сторінок"</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 f8d8c71..b0a5336 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"گھڑی کے سیکنڈز اسٹیٹس بار میں دکھائیں۔ اس کا بیٹری کی زندگی پر اثر پڑ سکتا ہے۔"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"فوری ترتیبات کو دوبارہ ترتیب دیں"</string>
     <string name="show_brightness" msgid="6613930842805942519">"فوری ترتیبات میں چمکیلا پن دکھائیں"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"فوری ترتیبات میں پیجنگ استعمال کریں"</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 0186809..9b9e2cd 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Holat panelida soat soniyalari ko‘rsatilsin. Bu batareya resursiga ta’sir qilishi mumkin."</string>
     <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="qs_paging" msgid="7020133150248666132">"Tezkor sozlamalarni sahifalarga ajratish"</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 49b5d1d..d4ed7a5 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Hiển thị giây đồng hồ trong thanh trạng thái. Có thể ảnh hưởng đến thời lượng pin."</string>
     <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="qs_paging" msgid="7020133150248666132">"Sử dụng đánh số trang 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 93ed5ca..05416ca 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"在状态栏中显示时钟的秒数。这可能会影响电池的续航时间。"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速设置"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速设置中显示亮度栏"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"在快速设置中使用分页功能"</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 2196111..ba4d3cb 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數,但可能會影響電池壽命。"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定顯示亮度"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"在快速設定使用分頁"</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 fe4fe06..6e0c852 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -441,6 +441,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數。這可能會影響電池續航力。"</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定中顯示亮度"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"在快速設定中使用分頁功能"</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 2f068af..147f6b6 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -439,6 +439,8 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Bonisa amasekhondi wewashi kubha yesimo. Ingathinta impilo yebhethri."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"Hlela kabusha izilungiselelo ezisheshayo"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Bonisa ukukhanya kuzilungiselelo ezisheshayo"</string>
-    <string name="qs_paging" msgid="7020133150248666132">"Sebenzisa i-paging 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/config.xml b/packages/SystemUI/res/values/config.xml
index 1d19589..bae8017 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -222,7 +222,7 @@
     <bool name="doze_pulse_on_notifications">true</bool>
 
     <!-- Doze: when to pulse after a buzzworthy notification arrives -->
-    <string name="doze_pulse_schedule" translatable="false">1s,10s,30s,60s</string>
+    <string name="doze_pulse_schedule" translatable="false">10s,30s,60s</string>
 
     <!-- Doze: maximum number of times the notification pulse schedule can be reset -->
     <integer name="doze_pulse_schedule_resets">2</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index abfd863..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>
@@ -556,4 +557,7 @@
     <dimen name="fab_margin">16dp</dimen>
     <dimen name="fab_elevation">12dp</dimen>
     <dimen name="fab_press_translation_z">9dp</dimen>
+
+    <!-- TODO: Remove this -->
+    <dimen name="qs_header_neg_padding">-8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/donottranslate.xml b/packages/SystemUI/res/values/donottranslate.xml
index 30ff704..351a1fd 100644
--- a/packages/SystemUI/res/values/donottranslate.xml
+++ b/packages/SystemUI/res/values/donottranslate.xml
@@ -20,10 +20,4 @@
     <!-- Date format for display: should match the lockscreen in /policy.  -->
     <string name="system_ui_date_pattern">@*android:string/system_ui_date_pattern</string>
 
-    <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
-    <item type="string" name="keyguard_indication_charging_time_fast_if_translated">@string/keyguard_indication_charging_time</item>
-
-    <!-- DO NOT TRANSLATE - temporary hack to show the speed-less label if no translation is available -->
-    <item type="string" name="keyguard_indication_charging_time_slowly_if_translated">@string/keyguard_indication_charging_time</item>
-
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d567e97..3a5680d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1156,7 +1156,7 @@
     <!-- Option to show brightness bar in quick settings [CHAR LIMIT=60] -->
     <string name="show_brightness">Show brightness in Quick Settings</string>
     <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
-    <string name="qs_paging">Use paging in Quick Settings</string>
+    <string name="qs_paging" translatable="false">Use the new Quick Settings</string>
 
     <!-- Category in the System UI Tuner settings, where new/experimental
          settings are -->
@@ -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/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 07e7688d..5980108 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -37,10 +37,6 @@
                 android:key="qs_paged_panel"
                 android:title="@string/qs_paging" />
 
-            <com.android.systemui.tuner.TunerSwitch
-                android:key="qs_allow_customize"
-                android:title="@string/qs_customize" />
-
         </PreferenceCategory>
 
     </PreferenceScreen>
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/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 36efead..3370091 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -88,6 +88,7 @@
     private boolean mPowerSaveActive;
     private boolean mCarMode;
     private long mNotificationPulseTime;
+    private long mLastScheduleResetTime;
     private long mEarliestPulseDueToLight;
     private int mScheduleResetsRemaining;
 
@@ -356,13 +357,21 @@
             return;
         }
         final long pulseDuration = mDozeParameters.getPulseDuration(false /*pickup*/);
-        if ((notificationTimeMs - mNotificationPulseTime) < pulseDuration) {
+        boolean pulseImmediately = System.currentTimeMillis() >= notificationTimeMs;
+        if ((notificationTimeMs - mLastScheduleResetTime) >= pulseDuration) {
+            mScheduleResetsRemaining--;
+            mLastScheduleResetTime = notificationTimeMs;
+        } else if (!pulseImmediately){
             if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
             return;
         }
-        mScheduleResetsRemaining--;
         if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
         mNotificationPulseTime = notificationTimeMs;
+        if (pulseImmediately) {
+            DozeLog.traceNotificationPulse(0);
+            requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+        }
+        // schedule the rest of the pulses
         rescheduleNotificationPulse(true /*predicate*/);
     }
 
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 c612600..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,17 +174,23 @@
     }
 
     public static class TilePage extends TileLayout {
-        private int mMaxRows = 4;
+        private int mMaxRows = 3;
 
         public TilePage(Context context, AttributeSet attrs) {
             super(context, attrs);
             mAllowDual = false;
+            updateResources();
         }
 
         public void setMaxRows(int maxRows) {
             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/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index ca38528..47189b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -140,13 +140,13 @@
         mDialog.setTitle(getTitle(deviceOwner));
         mDialog.setMessage(getMessage(deviceOwner, profileOwner, primaryVpn, profileVpn, managed));
         mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
-        if (mSecurityController.isVpnEnabled()) {
-            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
+        if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) {
+            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
         }
         mDialog.show();
     }
 
-    private String getNegativeButton() {
+    private String getSettingsButton() {
         return mContext.getString(R.string.status_bar_settings_settings_button);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 880349e..c6bcfc4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,7 +34,6 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
@@ -54,8 +53,7 @@
 public class QSPanel extends FrameLayout implements Tunable {
 
     public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
-    public static final String QS_PAGED_PANEL = "qs_paged_panel";
-    public static final String QS_ALLOW_CUSTOMIZE = "qs_allow_customize";
+    public static final String QS_THE_NEW_QS = "qs_paged_panel";
 
     protected final Context mContext;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
@@ -135,8 +133,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        TunerService.get(mContext).addTunable(this,
-                QS_SHOW_BRIGHTNESS, QS_PAGED_PANEL, QS_ALLOW_CUSTOMIZE);
+        TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS, QS_THE_NEW_QS);
     }
 
     @Override
@@ -150,14 +147,15 @@
         if (QS_SHOW_BRIGHTNESS.equals(key)) {
             mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0
                     ? VISIBLE : GONE);
-        } else if (QS_PAGED_PANEL.equals(key)) {
+        } else if (QS_THE_NEW_QS.equals(key)) {
+            boolean theNewQs = newValue != null && Integer.parseInt(newValue) != 0;
             if (mTileLayout != null) {
                 for (int i = 0; i < mRecords.size(); i++) {
                     mTileLayout.removeTile(mRecords.get(i));
                 }
                 mQsContainer.removeView((View) mTileLayout);
             }
-            int layout = newValue != null && Integer.parseInt(newValue) != 0
+            int layout = theNewQs
                     ? R.layout.qs_paged_tile_layout : R.layout.qs_tile_layout;
             mTileLayout =
                     (QSTileLayout) LayoutInflater.from(mContext).inflate(layout, mQsContainer, false);
@@ -165,14 +163,14 @@
             for (int i = 0; i < mRecords.size(); i++) {
                 mTileLayout.addTile(mRecords.get(i));
             }
-        } else if (QS_ALLOW_CUSTOMIZE.equals(key)) {
-            if (newValue != null && Integer.parseInt(newValue) != 0) {
+            if (theNewQs) {
                 mCustomizePanel = (QSCustomizer) LayoutInflater.from(mContext)
                         .inflate(R.layout.qs_customize_panel, null);
                 mCustomizePanel.setHost(mHost);
             } else {
                 if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
-                    mCustomizePanel.hide();
+                    mCustomizePanel.hide(mCustomizePanel.getWidth() / 2,
+                            mCustomizePanel.getHeight() / 2);
                 }
                 mCustomizePanel = null;
             }
@@ -242,7 +240,7 @@
 
     public void onCollapse() {
         if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
-            mCustomizePanel.hide();
+            mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
         }
     }
 
@@ -382,7 +380,12 @@
             public boolean onLongClick(View v) {
                 if (mCustomizePanel != null) {
                     if (!mCustomizePanel.isCustomizing()) {
-                        mCustomizePanel.show();
+                        int[] loc = new int[2];
+                        getLocationInWindow(loc);
+                        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2 + loc[0];
+                        int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r)
+                                + r.tileView.getHeight() / 2 + loc[1];
+                        mCustomizePanel.show(x, y);
                     }
                 } else {
                     r.tile.longClick();
@@ -409,7 +412,7 @@
     public void closeDetail() {
         if (mCustomizePanel != null && mCustomizePanel.isCustomizing()) {
             // Treat this as a detail panel for now, to make things easy.
-            mCustomizePanel.hide();
+            mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
             return;
         }
         showDetail(false, mDetailRecord);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index e562682..61cb224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Animatable;
@@ -136,7 +137,7 @@
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
 
-    protected final void refreshState() {
+    public final void refreshState() {
         refreshState(null);
     }
 
@@ -325,6 +326,7 @@
 
     public interface Host {
         void startActivityDismissingKeyguard(Intent intent);
+        void startActivityDismissingKeyguard(PendingIntent intent);
         void warn(String message, Throwable t);
         void collapsePanels();
         Looper getLooper();
@@ -358,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/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index e575923..d914beb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -267,7 +267,7 @@
         final int w = MeasureSpec.getSize(widthMeasureSpec);
         final int h = MeasureSpec.getSize(heightMeasureSpec);
         final int iconSpec = exactly(mIconSizePx);
-        mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), iconSpec);
+        mIcon.measure(MeasureSpec.makeMeasureSpec(w, getIconMeasureMode()), iconSpec);
         switch (mType) {
             case QS_TYPE_QUICK:
                 mCircle.measure(
@@ -287,6 +287,10 @@
         setMeasuredDimension(w, h);
     }
 
+    protected int getIconMeasureMode() {
+        return MeasureSpec.EXACTLY;
+    }
+
     private static int exactly(int size) {
         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
new file mode 100644
index 0000000..a2d9ef0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -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.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Version of QSPanel that only shows 4 Quick Tiles in the QS Header.
+ */
+public class QuickQSPanel extends QSPanel {
+
+    public QuickQSPanel(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        if (mTileLayout != null) {
+            for (int i = 0; i < mRecords.size(); i++) {
+                mTileLayout.removeTile(mRecords.get(i));
+            }
+            mQsContainer.removeView((View) mTileLayout);
+        }
+        mTileLayout = new HeaderTileLayout(context);
+        mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        // No tunings for you.
+        if (key.equals(QS_SHOW_BRIGHTNESS)) {
+            // No Brightness for you.
+            super.onTuningChanged(key, "0");
+        }
+    }
+
+    @Override
+    public void setTiles(Collection<QSTile<?>> tiles) {
+        ArrayList<QSTile<?>> quickTiles = new ArrayList<>();
+        for (QSTile<?> tile : tiles) {
+            if (tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
+                quickTiles.add(tile);
+            }
+            if (quickTiles.size() == 2) {
+                break;
+            }
+        }
+        super.setTiles(quickTiles);
+    }
+
+    private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
+
+        public HeaderTileLayout(Context context) {
+            super(context);
+            setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            int qsCompensation = (int)
+                    context.getResources().getDimension(R.dimen.qs_header_neg_padding);
+            setPadding(0, qsCompensation, 0, 0);
+            ImageView downArrow = new ImageView(context);
+            downArrow.setImageResource(R.drawable.ic_expand_more);
+            downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
+                    android.R.color.white, null)));
+            downArrow.setLayoutParams(generateLayoutParams());
+            downArrow.setPadding(0, -qsCompensation, 0, 0);
+            addView(downArrow);
+            setOrientation(LinearLayout.HORIZONTAL);
+        }
+
+        @Override
+        public void addTile(TileRecord tile) {
+            tile.tileView.setLayoutParams(generateLayoutParams());
+            // These shouldn't be normal tiles, but they will be for now so that the circles don't
+            // show up.
+            tile.tileView.setType(QSTileView.QS_TYPE_NORMAL);
+            addView(tile.tileView, getChildCount() - 1 /* Leave icon at end */);
+        }
+
+        private LayoutParams generateLayoutParams() {
+            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT);
+            lp.weight = 1;
+            return lp;
+        }
+
+        @Override
+        public void removeTile(TileRecord tile) {
+            removeView(tile.tileView);
+        }
+
+        @Override
+        public void setTileVisibility(TileRecord tile, int visibility) {
+            tile.tileView.setVisibility(visibility);
+        }
+
+        @Override
+        public int getOffsetTop(TileRecord tile) {
+            return 0;
+        }
+
+        @Override
+        public void updateResources() {
+            // No resources here.
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index 9ac7944..a2e1296 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -83,6 +83,11 @@
         layoutIndicator(mOut);
     }
 
+    @Override
+    protected int getIconMeasureMode() {
+        return MeasureSpec.AT_MOST;
+    }
+
     private void layoutIndicator(View indicator) {
         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         int left, right;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 8bd05fa..1336eec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -67,17 +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);
-        mCellWidth = (int)(mCellHeight * TILE_ASPECT);
-        mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
-        mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
-        mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+        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 = 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 f676ea3..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;
@@ -160,6 +165,11 @@
         }
 
         @Override
+        public boolean isVpnRestricted() {
+            return false;
+        }
+
+        @Override
         public String getPrimaryVpnName() {
             return null;
         }
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 7e74785..b5a885c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -15,25 +15,34 @@
  */
 package com.android.systemui.qs.customize;
 
+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.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;
@@ -48,10 +57,12 @@
  * *someday* do fancy animations to get into/out of it.
  */
 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener {
+        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
+        OnCancelListener, OnDismissListener {
 
     private static final int MENU_SAVE = Menu.FIRST;
     private static final int MENU_RESET = Menu.FIRST + 1;
+    private final QSDetailClipper mClipper;
 
     private PhoneStatusBar mPhoneStatusBar;
 
@@ -64,11 +75,13 @@
     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);
         mPhoneStatusBar = ((SystemUIApplication) mContext.getApplicationContext())
                 .getComponent(PhoneStatusBar.class);
+        mClipper = new QSDetailClipper(this);
     }
 
     public void setHost(QSTileHost host) {
@@ -86,12 +99,11 @@
         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) {
-                // TODO: Is this all we want...?
-                hide();
+                hide(0, 0);
             }
         });
         mToolbar.setOnMenuItemClickListener(this);
@@ -115,17 +127,18 @@
         mFab.setOnClickListener(this);
     }
 
-    public void show() {
+    public void show(int x, int y) {
         isShown = true;
         mHost.setSavedTiles();
-        // TODO: Fancy shmancy reveal.
         mPhoneStatusBar.getStatusBarWindow().addView(this);
+        mQsPanel.setListening(true);
+        mClipper.animateCircularClip(x, y, true, this);
     }
 
-    public void hide() {
+    public void hide(int x, int y) {
         isShown = false;
-        // TODO: Similarly awesome or better hide.
-        mPhoneStatusBar.getStatusBarWindow().removeView(this);
+        mQsPanel.setListening(false);
+        mClipper.animateCircularClip(x, y, false, this);
     }
 
     public boolean isCustomizing() {
@@ -146,7 +159,8 @@
 
     private void save() {
         mHost.saveCurrentTiles();
-        hide();
+        // TODO: At save button.
+        hide(0, 0);
     }
 
     @Override
@@ -163,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());
     }
@@ -194,7 +216,61 @@
     @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);
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (!isShown) {
+            mPhoneStatusBar.getStatusBarWindow().removeView(this);
+        }
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+        // Don't care.
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+        // Don't care.
+    }
 }
\ No newline at end of file
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/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index 3d0dc7b..c7f2284 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -99,7 +99,7 @@
         try {
             if (pi != null) {
                 if (pi.isActivity()) {
-                    getHost().startActivityDismissingKeyguard(pi.getIntent());
+                    getHost().startActivityDismissingKeyguard(pi);
                 } else {
                     pi.send();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 9781664..cdb6b93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -39,6 +39,8 @@
             public static final boolean EnableDismissAll = false;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
+            // This disables the search bar integration
+            public static final boolean DisableSearchBar = true;
             // This disables the bitmap and icon caches
             public static final boolean DisableBackgroundCache = false;
             // Enables the simulated task affiliations
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 a78351a..c216f97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,907 +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() {
-            // Temporarily skip this if multi stack is enabled
-            if (mConfig.multiWindowEnabled) return;
-
-            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 /* isTopTaskHome */);
-                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 some static datastructures
-        TaskStackViewLayoutAlgorithm.initializeCurve();
-        // 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);
-            }
-        }
-        mConfig.getAvailableTaskStackBounds(windowRect,
-                mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
-                mSearchBarBounds, mTaskStackBounds);
-        int systemBarBottomInset = mConfig.hasTransposedNavBar ? 0 : mNavBarHeight;
-
-        // Rebind the header bar and draw it for the transition
-        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
-        Rect taskStackBounds = new Rect(mTaskStackBounds);
-        taskStackBounds.bottom -= systemBarBottomInset;
-        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 destination rect
-        mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
-        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, mTriggeredFromAltTab, isTopTaskHome);
-        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 5c61c5ca6..c416967 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -38,9 +39,17 @@
 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;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -65,6 +74,7 @@
     RecentsConfiguration mConfig;
     RecentsPackageMonitor mPackageMonitor;
     long mLastTabKeyEventTime;
+    boolean mFinishedOnStartup;
 
     // Top level views
     RecentsView mRecentsView;
@@ -108,57 +118,16 @@
 
         @Override
         public void run() {
-            // Finish Recents
-            if (mLaunchIntent != null) {
-                try {
-                    if (mLaunchOpts != null) {
-                        startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
-                    } else {
-                        startActivityAsUser(mLaunchIntent, UserHandle.CURRENT);
-                    }
-                } catch (Exception e) {
-                    Console.logError(RecentsActivity.this,
-                            getString(R.string.recents_launch_error_message, "Home"));
-                }
-            } else {
-                finish();
-                overridePendingTransition(R.anim.recents_to_launcher_enter,
-                        R.anim.recents_to_launcher_exit);
+            try {
+                startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
+            } catch (Exception e) {
+                Console.logError(RecentsActivity.this,
+                        getString(R.string.recents_launch_error_message, "Home"));
             }
         }
     }
 
     /**
-     * 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
-                    dismissRecentsToHomeRaw(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() {
@@ -167,10 +136,10 @@
             String action = intent.getAction();
             if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 // When the screen turns off, dismiss Recents to Home
-                dismissRecentsToHome(false);
+                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();
             }
@@ -181,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);
         }
@@ -237,15 +206,19 @@
                 mEmptyView = mEmptyViewStub.inflate();
             }
             mEmptyView.setVisibility(View.VISIBLE);
-            mRecentsView.setSearchBarVisibility(View.GONE);
+            if (!Constants.DebugFlags.App.DisableSearchBar) {
+                mRecentsView.setSearchBarVisibility(View.GONE);
+            }
         } else {
             if (mEmptyView != null) {
                 mEmptyView.setVisibility(View.GONE);
             }
-            if (mRecentsView.hasValidSearchBar()) {
-                mRecentsView.setSearchBarVisibility(View.VISIBLE);
-            } else {
-                refreshSearchWidgetView();
+            if (!Constants.DebugFlags.App.DisableSearchBar) {
+                if (mRecentsView.hasValidSearchBar()) {
+                    mRecentsView.setSearchBarVisibility(View.VISIBLE);
+                } else {
+                    refreshSearchWidgetView();
+                }
             }
         }
 
@@ -274,7 +247,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 &&
@@ -283,20 +256,22 @@
             if (mRecentsView.launchFocusedTask()) return true;
             // If we launched from Home, then return to Home
             if (launchState.launchedFromHome) {
-                dismissRecentsToHomeRaw(true);
+                dismissRecentsToHome(true);
                 return true;
             }
             // Otherwise, try and return to the Task that Recents was launched from
             if (mRecentsView.launchPreviousTask()) return true;
             // If none of the other cases apply, then just go Home
-            dismissRecentsToHomeRaw(true);
+            dismissRecentsToHome(true);
             return true;
         }
         return false;
     }
 
-    /** Dismisses Recents directly to Home. */
-    void dismissRecentsToHomeRaw(boolean animated) {
+    /**
+     * Dismisses Recents directly to Home without checking whether it is currently visible.
+     */
+    void dismissRecentsToHome(boolean animated) {
         if (animated) {
             ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
                     null, mFinishLaunchHomeRunnable, null);
@@ -314,11 +289,11 @@
     }
 
     /** Dismisses Recents directly to Home if we currently aren't transitioning. */
-    boolean dismissRecentsToHome(boolean animated) {
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+    boolean dismissRecentsToHomeIfVisible(boolean animated) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // Return to Home
-            dismissRecentsToHomeRaw(animated);
+            dismissRecentsToHome(animated);
             return true;
         }
         return false;
@@ -328,20 +303,27 @@
     @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)
-        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+        mConfig = RecentsConfiguration.getInstance();
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+        }
+        mPackageMonitor = new RecentsPackageMonitor();
+        mPackageMonitor.register(this);
 
         // Set the Recents layout
         setContentView(R.layout.recents);
@@ -354,12 +336,16 @@
         mScrimViews = new SystemBarScrimViews(this);
 
         // Bind the search app widget when we first start up
-        mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
+        }
 
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+        }
         registerReceiver(mSystemBroadcastReceiver, filter);
     }
 
@@ -372,21 +358,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();
@@ -394,15 +365,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
@@ -417,69 +395,63 @@
     @Override
     protected void onStop() {
         super.onStop();
-        MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        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
         // wait on the system to send a signal that was never queued.
+        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         launchState.launchedFromHome = false;
         launchState.launchedFromSearchHome = false;
         launchState.launchedFromAppWithThumbnail = false;
         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();
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            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);
         }
@@ -524,7 +496,7 @@
 
     @Override
     public void onUserInteraction() {
-        mRecentsView.onUserInteraction();
+        EventBus.getDefault().send(new UserInteractionEvent());
     }
 
     @Override
@@ -545,19 +517,13 @@
     /**** RecentsView.RecentsViewCallbacks Implementation ****/
 
     @Override
-    public void onExitToHomeAnimationTriggered() {
-        // Animate the SystemUI scrim views out
-        mScrimViews.startExitRecentsAnimation();
-    }
-
-    @Override
     public void onTaskViewClicked() {
     }
 
     @Override
     public void onTaskLaunchFailed() {
         // Return to Home
-        dismissRecentsToHomeRaw(true);
+        dismissRecentsToHome(true);
     }
 
     @Override
@@ -566,30 +532,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 (!Constants.DebugFlags.App.DisableSearchBar && 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,
@@ -601,20 +591,37 @@
 
     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) {
         getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
     }
 
+    public final void onBusEvent(DragStartEvent event) {
+        // Lock the orientation while dragging
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+
+        // TODO: docking requires custom accessibility actions
+    }
+
+    public final void onBusEvent(DragEndEvent event) {
+        // Unlock the orientation when dragging completes
+        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 52b9521..d8f4023 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -70,13 +70,13 @@
     public final int smallestWidth;
 
     /** Misc **/
+    public boolean hasDockedTasks;
     public boolean useHardwareLayers;
     public boolean fakeShadows;
     public int svelteLevel;
     public int searchBarSpaceHeightPx;
 
     /** Dev options and global settings */
-    public boolean multiWindowEnabled;
     public boolean lockToAppEnabled;
 
     /** Private constructor */
@@ -111,9 +111,8 @@
     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;
-        multiWindowEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
+        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
         // system to get certain values.
@@ -143,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();
     }
@@ -153,7 +154,7 @@
      * Returns the task stack bounds in the current orientation. These bounds do not account for
      * the system insets.
      */
-    public void getAvailableTaskStackBounds(Rect windowBounds, int topInset,
+    public void getTaskStackBounds(Rect windowBounds, int topInset,
             int rightInset, Rect searchBarBounds, Rect taskStackBounds) {
         if (hasTransposedNavBar) {
             // In landscape phones, the search bar appears on the left, but we overlay it on top
@@ -164,7 +165,8 @@
         } else {
             // In portrait, the search bar appears on the top (which already has the inset)
             int swInset = getInsetToSmallestWidth(windowBounds.right - windowBounds.left);
-            taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom,
+            int top = searchBarBounds.isEmpty() ? topInset : 0;
+            taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom + top,
                     windowBounds.right - swInset, windowBounds.bottom);
         }
     }
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..07c7897
--- /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 (!Constants.DebugFlags.App.DisableSearchBar && 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 (!Constants.DebugFlags.App.DisableSearchBar && 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 59df293..31ee8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -30,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.
  */
@@ -77,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};
 
@@ -90,7 +91,6 @@
     public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
         mFragmentManager = mgr;
         mRecentsActivity = activity;
-        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
     }
 
     /** Shows the resize-task dialog. */
@@ -141,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) {
@@ -235,11 +236,9 @@
 
         // In debug mode, we force all task to be resizeable regardless of the
         // current app configuration.
-        if (RecentsConfiguration.getInstance().multiWindowEnabled) {
-            for (int i = additionalTasks; i >= 0; --i) {
-                if (mTasks[i] != null) {
-                    mSsp.setTaskResizeable(mTasks[i].key.id);
-                }
+        for (int i = additionalTasks; i >= 0; --i) {
+            if (mTasks[i] != null) {
+                ssp.setTaskResizeable(mTasks[i].key.id);
             }
         }
 
@@ -247,7 +246,7 @@
         // the focus ends on the selected one.
         for (int i = additionalTasks; i >= 0; --i) {
             if (mTasks[i] != null) {
-                mRecentsView.launchTask(mTasks[i], mBounds[i]);
+                mRecentsView.launchTask(mTasks[i], mBounds[i], FREEFORM_WORKSPACE_STACK_ID);
             }
         }
     }
@@ -275,11 +274,12 @@
         // Dismiss the dialog before trying to launch the task
         dismissAllowingStateLoss();
 
-        if (mTasks[0].key.stackId != ActivityManager.DOCKED_STACK_ID) {
+        if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
             int taskId = mTasks[0].key.id;
-            mSsp.setTaskResizeable(taskId);
-            mSsp.dockTask(taskId, createMode);
-            mRecentsView.launchTask(mTasks[0], null);
+            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/events/ui/dragndrop/DragDockStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java
new file mode 100644
index 0000000..f2c3c33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.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.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * This event is sent when a user drag enters or exits a dock region.
+ */
+public class DragDockStateChangedEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskStack.DockState dockState;
+
+    public DragDockStateChangedEvent(Task task, TaskStack.DockState dockState) {
+        this.task = task;
+        this.dockState = dockState;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
new file mode 100644
index 0000000..827998d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DragView;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This event is sent whenever a drag ends.
+ */
+public class DragEndEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskView taskView;
+    public final DragView dragView;
+    public final TaskStack.DockState dockState;
+    public final ReferenceCountedTrigger postAnimationTrigger;
+
+    public DragEndEvent(Task task, TaskView taskView, DragView dragView,
+            TaskStack.DockState dockState, ReferenceCountedTrigger postAnimationTrigger) {
+        this.task = task;
+        this.taskView = taskView;
+        this.dragView = dragView;
+        this.dockState = dockState;
+        this.postAnimationTrigger = postAnimationTrigger;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
new file mode 100644
index 0000000..2d42a0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.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.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.views.DragView;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This event is sent whenever a drag starts.
+ */
+public class DragStartEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskView taskView;
+    public final DragView dragView;
+
+    public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+        this.task = task;
+        this.taskView = taskView;
+        this.dragView = dragView;
+    }
+}
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/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
new file mode 100644
index 0000000..8e85bfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -0,0 +1,263 @@
+/*
+ * 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.misc;
+
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * Represents a 2d curve that is parameterized along the arc length of the curve (p), and allows the
+ * conversions of x->p and p->x.
+ */
+public class ParametricCurve {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ParametricCurve";
+
+    private static final int PrecisionSteps = 250;
+
+    /**
+     * A 2d function, representing the curve.
+     */
+    public interface CurveFunction {
+        float f(float x);
+        float invF(float y);
+    }
+
+    /**
+     * A function that returns a value given a parametric value.
+     */
+    public interface ParametricCurveFunction {
+        float f(float p);
+    }
+
+    float[] xp;
+    float[] px;
+
+    CurveFunction mFn;
+    ParametricCurveFunction mScaleFn;
+
+    public ParametricCurve(CurveFunction fn, ParametricCurveFunction scaleFn) {
+        long t1;
+        if (DEBUG) {
+            t1 = SystemClock.currentThreadTimeMicro();
+            Log.d(TAG, "initializeCurve");
+        }
+        mFn = fn;
+        mScaleFn = scaleFn;
+        xp = new float[PrecisionSteps + 1];
+        px = new float[PrecisionSteps + 1];
+
+        // Approximate f(x)
+        float[] fx = new float[PrecisionSteps + 1];
+        float step = 1f / PrecisionSteps;
+        float x = 0;
+        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
+            fx[xStep] = fn.f(x);
+            x += step;
+        }
+        // Calculate the arc length for x:1->0
+        float pLength = 0;
+        float[] dx = new float[PrecisionSteps + 1];
+        dx[0] = 0;
+        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
+            dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
+            pLength += dx[xStep];
+        }
+        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
+        float p = 0;
+        px[0] = 0f;
+        px[PrecisionSteps] = 1f;
+        if (DEBUG) {
+            Log.d(TAG, "p[0]=0");
+            Log.d(TAG, "p[" + PrecisionSteps + "]=1");
+        }
+        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
+            p += Math.abs(dx[xStep] / pLength);
+            px[xStep] = p;
+            if (DEBUG) {
+                Log.d(TAG, "p[" + xStep + "]=" + p);
+            }
+        }
+        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
+        // function.
+        int xStep = 0;
+        p = 0;
+        xp[0] = 0f;
+        xp[PrecisionSteps] = 1f;
+        if (DEBUG) {
+            Log.d(TAG, "x[0]=0");
+            Log.d(TAG, "x[" + PrecisionSteps + "]=1");
+        }
+        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
+            // Walk forward in px and find the x where px <= p && p < px+1
+            while (xStep < PrecisionSteps) {
+                if (px[xStep] > p) break;
+                xStep++;
+            }
+            // Now, px[xStep-1] <= p < px[xStep]
+            if (xStep == 0) {
+                xp[pStep] = 0;
+            } else {
+                // Find x such that proportionally, x is correct
+                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
+                x = (xStep - 1 + fraction) * step;
+                xp[pStep] = x;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "x[" + pStep + "]=" + xp[pStep]);
+            }
+            p += step;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
+        }
+    }
+
+    /**
+     * Converts from the progress along the arc-length of the curve to a coordinate within the
+     * bounds.  Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
+     */
+    public int pToX(float p, Rect bounds) {
+        int top = bounds.top;
+        int height = bounds.height();
+
+        if (p <= 0f) return top;
+        if (p >= 1f) return top + (int) (p * height);
+
+        float pIndex = p * PrecisionSteps;
+        int pFloorIndex = (int) Math.floor(pIndex);
+        int pCeilIndex = (int) Math.ceil(pIndex);
+        float x = xp[pFloorIndex];
+        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
+            // Interpolate between the two precalculated positions
+            x += (xp[pCeilIndex] - xp[pFloorIndex]) * (pIndex - pFloorIndex);
+        }
+        return top + (int) (x * height);
+    }
+
+    /**
+     * Converts from the progress along the arc-length of the curve to a scale.
+     */
+    public float pToScale(float p) {
+        return mScaleFn.f(p);
+    }
+
+    /**
+     * Converts from a bounds coordinate to the progress along the arc-length of the curve.
+     * Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
+     */
+    public float xToP(int x, Rect bounds) {
+        int top = bounds.top;
+
+        float xf = (float) (x - top) / bounds.height();
+        if (xf <= 0f) return 0f;
+        if (xf >= 1f) return xf;
+
+        float xIndex = xf * PrecisionSteps;
+        int xFloorIndex = (int) Math.floor(xIndex);
+        int xCeilIndex = (int) Math.ceil(xIndex);
+        float p = px[xFloorIndex];
+        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
+            // Interpolate between the two precalculated positions
+            p += (px[xCeilIndex] - px[xFloorIndex]) * (xIndex - xFloorIndex);
+        }
+        return p;
+    }
+
+    /**
+     * Computes the progress offset from the bottom of the curve (p=1) such that the given height
+     * is visible when scaled at the that progress.
+     */
+    public float computePOffsetForScaledHeight(int height, Rect bounds) {
+        int top = bounds.top;
+        int bottom = bounds.bottom;
+
+        if (bounds.height() == 0) {
+            return 0;
+        }
+
+        // Find the next p(x) such that (bottom-x) == scale(p(x))*height
+        int minX = top;
+        int maxX = bottom;
+        long t1;
+        if (DEBUG) {
+            t1 = SystemClock.currentThreadTimeMicro();
+            Log.d(TAG, "computePOffsetForScaledHeight: " + height);
+        }
+        while (minX <= maxX) {
+            int midX = minX + ((maxX - minX) / 2);
+            float pMidX = xToP(midX, bounds);
+            float scaleMidX = mScaleFn.f(pMidX);
+            int scaledHeight = (int) (scaleMidX * height);
+            if ((bottom - midX) < scaledHeight) {
+                maxX = midX - 1;
+            } else if ((bottom - midX) > scaledHeight) {
+                minX = midX + 1;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
+                }
+                return 1f - pMidX;
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
+        }
+        return 1f - xToP(maxX, bounds);
+    }
+
+    /**
+     * Computes the progress offset from the bottom of the curve (p=1) that allows the given height,
+     * unscaled at the progress, will be visible.
+     */
+    public float computePOffsetForHeight(int height, Rect bounds) {
+        int top = bounds.top;
+        int bottom = bounds.bottom;
+
+        if (bounds.height() == 0) {
+            return 0;
+        }
+
+        // Find the next p(x) such that (bottom-x) == height
+        int minX = top;
+        int maxX = bottom;
+        long t1;
+        if (DEBUG) {
+            t1 = SystemClock.currentThreadTimeMicro();
+            Log.d(TAG, "computePOffsetForHeight: " + height);
+        }
+        while (minX <= maxX) {
+            int midX = minX + ((maxX - minX) / 2);
+            if ((bottom - midX) < height) {
+                maxX = midX - 1;
+            } else if ((bottom - midX) > height) {
+                minX = midX + 1;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
+                }
+                return 1f - xToP(midX, bounds);
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
+        }
+        return 1f - xToP(maxX, bounds);
+    }
+}
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 e0f820d..a51e475 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -46,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;
@@ -60,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;
@@ -71,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.
@@ -79,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;
     }
@@ -97,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;
@@ -119,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();
@@ -241,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;
                 }
@@ -297,7 +295,7 @@
         if (mIam == null) return;
 
         try {
-            mIam.moveTaskToDockedStack(taskId, createMode, true);
+            mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -327,6 +325,21 @@
         return mAm.isInHomeStack(taskId);
     }
 
+    /**
+     * @return whether there are any docked tasks.
+     */
+    public boolean hasDockedTask() {
+        if (mIam == null) return false;
+
+        ActivityManager.StackInfo stackInfo = null;
+        try {
+            stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return stackInfo != null;
+    }
+
     /** Returns the top task thumbnail for the given task id */
     public Bitmap getTaskThumbnail(int taskId) {
         if (mAm == null) return null;
@@ -397,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);
@@ -534,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();
     }
 
     /**
@@ -707,7 +735,8 @@
             ActivityOptions options) {
         if (mIam != null) {
             try {
-                mIam.startActivityFromRecents(taskId, options == null ? null : options.toBundle());
+                mIam.startActivityFromRecents(
+                        taskId, INVALID_STACK_ID, options == null ? null : options.toBundle());
                 return true;
             } catch (Exception e) {
                 Console.logError(context,
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 e810410..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,103 +18,43 @@
 
 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 java.util.ArrayList;
+import android.view.ViewParent;
 
 /* Common code */
 public class Utilities {
 
+    /**
+     * @return the first parent walking up the view hierarchy that has the given class type.
+     *
+     * @param parentClass must be a class derived from {@link View}
+     */
+    public static <T extends View> T findParent(View v, Class<T> parentClass) {
+        ViewParent parent = v.getParent();
+        while (parent != null) {
+            if (parent.getClass().equals(parentClass)) {
+                return (T) parent;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
     /** 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 8eec87e..b3937c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,20 +16,31 @@
 
 package com.android.systemui.recents.model;
 
+import android.animation.ObjectAnimator;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+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;
 
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+
 
 /**
  * An interface for a task filter to query whether a particular task should show in a stack.
@@ -165,7 +176,8 @@
         /* Notifies when a task has been added to the stack */
         public void onStackTaskAdded(TaskStack stack, Task t);
         /* Notifies when a task has been removed from the stack */
-        public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
+        public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+                                       Task newFrontMostTask);
         /* Notifies when all task has been removed from the stack */
         public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
         /** Notifies when the stack was filtered */
@@ -174,6 +186,98 @@
         public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
     }
 
+
+    public enum DockState {
+        NONE(-1, 96, null, null, null),
+        LEFT(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, 192,
+                new RectF(0, 0, 0.3f, 1), new RectF(0, 0, 0.3f, 1), new RectF(0.7f, 0, 1, 1)),
+        TOP(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, 192,
+                new RectF(0, 0, 1, 0.3f), new RectF(0, 0, 1, 0.3f), new RectF(0, 0.7f, 1, 1)),
+        RIGHT(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, 192,
+                new RectF(0.7f, 0, 1, 1), new RectF(0.7f, 0, 1, 1), new RectF(0, 0, 0.3f, 1)),
+        BOTTOM(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, 192,
+                new RectF(0, 0.7f, 1, 1), new RectF(0, 0.7f, 1, 1), new RectF(0, 0, 1, 0.3f));
+
+        // Represents the view state of this dock state
+        public class ViewState {
+            public final int dockAreaAlpha;
+            public final ColorDrawable dockAreaOverlay;
+            private ObjectAnimator dockAreaOverlayAnimator;
+
+            private ViewState(int alpha) {
+                dockAreaAlpha = alpha;
+                dockAreaOverlay = new ColorDrawable(0xFFffffff);
+                dockAreaOverlay.setAlpha(0);
+            }
+
+            /**
+             * Creates a new alpha animation.
+             */
+            public void startAlphaAnimation(int alpha, int duration) {
+                if (dockAreaOverlay.getAlpha() != alpha) {
+                    if (dockAreaOverlayAnimator != null) {
+                        dockAreaOverlayAnimator.cancel();
+                    }
+                    dockAreaOverlayAnimator = ObjectAnimator.ofInt(dockAreaOverlay, "alpha", alpha);
+                    dockAreaOverlayAnimator.setDuration(duration);
+                    dockAreaOverlayAnimator.start();
+                }
+            }
+        }
+
+        public final int createMode;
+        public final ViewState viewState;
+        private final RectF dockArea;
+        private final RectF touchArea;
+        private final RectF stackArea;
+
+        /**
+         * @param createMode used to pass to ActivityManager to dock the task
+         * @param touchArea the area in which touch will initiate this dock state
+         * @param stackArea the area for the stack if a task is docked
+         */
+        DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea,
+                RectF stackArea) {
+            this.createMode = createMode;
+            this.viewState = new ViewState(dockAreaAlpha);
+            this.dockArea = dockArea;
+            this.touchArea = touchArea;
+            this.stackArea = stackArea;
+        }
+
+        /**
+         * Returns whether {@param x} and {@param y} are contained in the touch area scaled to the
+         * given {@param width} and {@param height}.
+         */
+        public boolean touchAreaContainsPoint(int width, int height, float x, float y) {
+            int left = (int) (touchArea.left * width);
+            int top = (int) (touchArea.top * height);
+            int right = (int) (touchArea.right * width);
+            int bottom = (int) (touchArea.bottom * height);
+            return x >= left && y >= top && x <= right && y <= bottom;
+        }
+
+        /**
+         * Returns the docked task bounds with the given {@param width} and {@param height}.
+         */
+        public Rect getDockedBounds(int width, int height) {
+            return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height),
+                    (int) (dockArea.right * width), (int) (dockArea.bottom * height));
+        }
+
+        /**
+         * Returns the stack bounds with the given {@param width} and {@param height}.
+         */
+        public Rect getStackBounds(Rect stackRect) {
+            int width = stackRect.width();
+            int height = stackRect.height();
+            return new Rect((int) (stackRect.left + stackArea.left * width),
+                    (int) (stackRect.top + stackArea.top * height),
+                    (int) (stackRect.left + (stackArea.right - stackArea.left) * width),
+                    (int) (stackRect.top + (stackArea.bottom - stackArea.top) * height));
+        }
+    }
+
     // The task offset to apply to a task id as a group affiliation
     static final int IndividualTaskIdOffset = 1 << 16;
 
@@ -221,6 +325,7 @@
     /** Removes a task */
     public void removeTask(Task t) {
         if (mTaskList.contains(t)) {
+            boolean wasFrontMostTask = (getFrontMostTask() == t);
             removeTaskImpl(t);
             Task newFrontMostTask = getFrontMostTask();
             if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
@@ -228,7 +333,7 @@
             }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, newFrontMostTask);
+                mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
             }
         }
     }
@@ -256,7 +361,7 @@
             removeTaskImpl(t);
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, null);
+                mCb.onStackTaskRemoved(this, t, false, null);
             }
         }
         mTaskList.set(tasks);
@@ -322,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) {
@@ -388,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) {
@@ -475,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/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
new file mode 100644
index 0000000..96dfaac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
@@ -0,0 +1,39 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.widget.ImageView;
+
+public class DragView extends ImageView {
+
+    // The offset from the top-left of the dragBitmap
+    Point mTopLeftOffset;
+
+    public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
+        super(context);
+
+        mTopLeftOffset = tlOffset;
+        setImageBitmap(dragBitmap);
+    }
+
+    public Point getTopLeftOffset() {
+        return mTopLeftOffset;
+    }
+}
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 de8730d..7f618e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -22,14 +22,17 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManagerGlobal;
@@ -39,18 +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.
@@ -66,8 +77,6 @@
         public void onTaskViewClicked();
         public void onTaskLaunchFailed();
         public void onAllTaskViewsDismissed();
-        public void onExitToHomeAnimationTriggered();
-        public void onScreenPinningRequest();
         public void runAfterPause(Runnable r);
     }
 
@@ -78,6 +87,16 @@
     TaskStackView mTaskStackView;
     RecentsAppWidgetHostView mSearchBar;
     RecentsViewCallbacks mCb;
+
+    RecentsViewTouchHandler mTouchHandler;
+    DragView mDragView;
+    TaskStack.DockState[] mVisibleDockStates = {
+            TaskStack.DockState.LEFT,
+            TaskStack.DockState.TOP,
+            TaskStack.DockState.RIGHT,
+            TaskStack.DockState.BOTTOM,
+    };
+
     Interpolator mFastOutSlowInInterpolator;
 
     Rect mSystemInsets = new Rect();
@@ -96,10 +115,12 @@
 
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setWillNotDraw(false);
         mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
+        mTouchHandler = new RecentsViewTouchHandler(this);
     }
 
     /** Sets the callbacks */
@@ -166,7 +187,8 @@
                 TaskView tv = taskViews.get(j);
                 Task task = tv.getTask();
                 if (tv.isFocusedTask()) {
-                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
+                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null,
+                            INVALID_STACK_ID);
                     return true;
                 }
             }
@@ -175,7 +197,7 @@
     }
 
     /** Launches a given task. */
-    public boolean launchTask(Task task, Rect taskBounds) {
+    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
         if (mTaskStackView != null) {
             TaskStack stack = mTaskStackView.getStack();
             // Iterate the stack views and try and find the given task.
@@ -185,7 +207,7 @@
                 TaskView tv = taskViews.get(j);
                 if (tv.getTask() == task) {
                     onTaskViewClicked(mTaskStackView, tv, stack, task, false, taskBounds != null,
-                            taskBounds);
+                            taskBounds, destinationStack);
                     return true;
                 }
             }
@@ -200,13 +222,15 @@
             ArrayList<Task> tasks = stack.getTasks();
 
             // Find the launch task in the stack
+            // TODO: replace this with an event from RecentsActivity
             if (!tasks.isEmpty()) {
                 int taskCount = tasks.size();
                 for (int j = 0; j < taskCount; j++) {
                     if (tasks.get(j).isLaunchTarget) {
                         Task task = tasks.get(j);
                         TaskView tv = mTaskStackView.getChildViewForTask(task);
-                        onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null);
+                        onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null,
+                                INVALID_STACK_ID);
                         return true;
                     }
                 }
@@ -237,7 +261,7 @@
         ctx.postAnimationTrigger.decrement();
 
         // Notify of the exit animation
-        mCb.onExitToHomeAnimationTriggered();
+        EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
     }
 
     /** Adds the search bar */
@@ -267,6 +291,20 @@
         }
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        super.onAttachedToWindow();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        EventBus.getDefault().unregister(this);
+        EventBus.getDefault().unregister(mTouchHandler);
+    }
+
     /**
      * This is called with the full size of the window since we are handling our own insets.
      */
@@ -286,13 +324,19 @@
         }
 
         Rect taskStackBounds = new Rect();
-        mConfig.getAvailableTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+        mConfig.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
                 mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
         if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
-            mTaskStackView.setTaskStackBounds(taskStackBounds);
+            mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
+        if (mDragView != null) {
+            mDragView.measure(
+                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+        }
+
         setMeasuredDimension(width, height);
     }
 
@@ -314,6 +358,11 @@
         if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
+
+        if (mDragView != null) {
+            mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
+                    top + mDragView.getMeasuredHeight());
+        }
     }
 
     @Override
@@ -323,14 +372,38 @@
         return insets.consumeSystemWindowInsets();
     }
 
-    /** Notifies each task view of the user interaction. */
-    public void onUserInteraction() {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onUserInteraction();
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return mTouchHandler.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mTouchHandler.onTouchEvent(ev);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
+            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+            if (d.getAlpha() > 0) {
+                d.draw(canvas);
+            }
         }
     }
 
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
+            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+            if (d == who) {
+                return true;
+            }
+        }
+        return super.verifyDrawable(who);
+    }
+
     /** Focuses the next task in the first stack view */
     public void focusNextTask(boolean forward) {
         // Get the first stack view
@@ -374,12 +447,13 @@
     private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
             final TaskView clickedView, final int offsetX, final int offsetY,
             final float stackScroll,
-            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+            final ActivityOptions.OnAnimationStartedListener animStartedListener,
+            final int destinationStack) {
         Runnable r = new Runnable() {
             @Override
             public void run() {
                 overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
-                        stackScroll, animStartedListener);
+                        stackScroll, animStartedListener, destinationStack);
 
             }
         };
@@ -389,9 +463,10 @@
 
     private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
             TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
-            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+            final ActivityOptions.OnAnimationStartedListener animStartedListener,
+            int destinationStack) {
         List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
-                clickedTask, offsetX, offsetY, stackScroll);
+                clickedTask, offsetX, offsetY, stackScroll, destinationStack);
         if (specs == null) {
             return;
         }
@@ -422,8 +497,10 @@
     }
 
     private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
-            TaskView clickedTask, int offsetX, int offsetY, float stackScroll) {
-        final int targetStackId = clickedTask.getTask().key.stackId;
+            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
+            int destinationStack) {
+        final int targetStackId = destinationStack != INVALID_STACK_ID ?
+                destinationStack : clickedTask.getTask().key.stackId;
         if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
                 && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
             return null;
@@ -517,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);
     }
@@ -527,9 +604,8 @@
 
     @Override
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
-                                  final TaskStack stack, final Task task, final boolean lockToTask,
-                                  final boolean boundsValid, final Rect bounds) {
-
+            final TaskStack stack, final Task task, final boolean lockToTask,
+            final boolean boundsValid, final Rect bounds, int destinationStack) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
             mCb.onTaskViewClicked();
@@ -546,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 &&
@@ -568,7 +643,8 @@
                             postDelayed(new Runnable() {
                                 @Override
                                 public void run() {
-                                    mCb.onScreenPinningRequest();
+                                    EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                            getContext(), ssp));
                                 }
                             }, 350);
                             mTriggered = true;
@@ -577,10 +653,10 @@
                 };
             }
             postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
-                    animStartedListener);
+                    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();
@@ -600,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
@@ -658,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
@@ -697,4 +766,91 @@
                     .start();
         }
     }
+
+    /**** EventBus Events ****/
+
+    public final void onBusEvent(DragStartEvent event) {
+        // Add the drag view
+        mDragView = event.dragView;
+        addView(mDragView);
+
+        updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
+                TaskStack.DockState.NONE.viewState.dockAreaAlpha);
+    }
+
+    public final void onBusEvent(DragDockStateChangedEvent event) {
+        if (event.dockState == TaskStack.DockState.NONE) {
+            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
+                    TaskStack.DockState.NONE.viewState.dockAreaAlpha);
+        } else {
+            updateVisibleDockRegions(new TaskStack.DockState[] {event.dockState}, -1);
+        }
+    }
+
+    public final void onBusEvent(final DragEndEvent event) {
+        event.postAnimationTrigger.increment();
+        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // Remove the drag view
+                removeView(mDragView);
+                mDragView = null;
+                updateVisibleDockRegions(null, -1);
+
+                // Dock the new task if we are hovering over a valid dock state
+                if (event.dockState != TaskStack.DockState.NONE) {
+                    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);
+                }
+            }
+        });
+        if (event.dockState == TaskStack.DockState.NONE) {
+            // Animate the alpha back to what it was before
+            Rect taskBounds = mTaskStackView.getStackAlgorithm().getUntransformedTaskViewBounds();
+            int left = taskBounds.left + (int) ((1f - event.taskView.getScaleX()) * taskBounds.width()) / 2;
+            int top = taskBounds.top + (int) ((1f - event.taskView.getScaleY()) * taskBounds.height()) / 2;
+            event.dragView.animate()
+                    .scaleX(1f)
+                    .scaleY(1f)
+                    .translationX(left + event.taskView.getTranslationX())
+                    .translationY(top + event.taskView.getTranslationY())
+                    .setDuration(175)
+                    .setInterpolator(mFastOutSlowInInterpolator)
+                    .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
+                    .start();
+
+            // Animate the overlay alpha back to 0
+            updateVisibleDockRegions(null, -1);
+        } else {
+            event.postAnimationTrigger.decrement();
+        }
+    }
+
+    /**
+     * Updates the dock region to match the specified dock state.
+     */
+    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) {
+        ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>();
+        if (newDockStates != null) {
+            for (TaskStack.DockState dockState : newDockStates) {
+                newDockStatesSet.add(dockState);
+            }
+        }
+        for (TaskStack.DockState dockState : mVisibleDockStates) {
+            TaskStack.DockState.ViewState viewState = dockState.viewState;
+            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
+                // This is no longer visible, so hide it
+                viewState.startAlphaAnimation(0, 150);
+            } else {
+                // This state is now visible, update the bounds and show it
+                int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
+                viewState.dockAreaOverlay.setBounds(
+                        dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight()));
+                viewState.dockAreaOverlay.setCallback(this);
+                viewState.startAlphaAnimation(alpha, 150);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
new file mode 100644
index 0000000..cf4c9cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -0,0 +1,172 @@
+/*
+ * 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.views;
+
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.view.MotionEvent;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.EventBus;
+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.ReferenceCountedTrigger;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+
+/**
+ * Represents the dock regions for each orientation.
+ */
+class DockRegion {
+    public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+            TaskStack.DockState.LEFT
+    };
+    public static TaskStack.DockState[] PHONE_PORTRAIT = {
+            // We only allow docking to the top for now
+            TaskStack.DockState.TOP
+    };
+    public static TaskStack.DockState[] TABLET_LANDSCAPE = {
+            TaskStack.DockState.LEFT
+    };
+    public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+}
+
+/**
+ * Handles touch events for a RecentsView.
+ */
+class RecentsViewTouchHandler {
+
+    private RecentsView mRv;
+
+    private Task mDragTask;
+    private TaskView mTaskView;
+    private DragView mDragView;
+
+    private Point mDownPos = new Point();
+    private boolean mDragging;
+    private TaskStack.DockState mLastDockState = TaskStack.DockState.NONE;
+
+    public RecentsViewTouchHandler(RecentsView rv) {
+        mRv = rv;
+    }
+
+    /**
+     * Returns the preferred dock states for the current orientation.
+     */
+    public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+        boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        TaskStack.DockState[] dockStates = isLandscape ?
+                (config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) :
+                (config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT);
+        return dockStates;
+    }
+
+    /** Touch preprocessing for handling below */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        handleTouchEvent(ev);
+        return mDragging;
+    }
+
+    /** Handles touch events once we have intercepted them */
+    public boolean onTouchEvent(MotionEvent ev) {
+        handleTouchEvent(ev);
+        return mDragging;
+    }
+
+    /**** Events ****/
+
+    public final void onBusEvent(DragStartEvent event) {
+        mRv.getParent().requestDisallowInterceptTouchEvent(true);
+        mDragging = true;
+        mDragTask = event.task;
+        mTaskView = event.taskView;
+        mDragView = event.dragView;
+
+        float x = mDownPos.x - mDragView.getTopLeftOffset().x;
+        float y = mDownPos.y - mDragView.getTopLeftOffset().y;
+        mDragView.setTranslationX(x);
+        mDragView.setTranslationY(y);
+    }
+
+    public final void onBusEvent(DragEndEvent event) {
+        mDragging = false;
+        mDragTask = null;
+        mTaskView = null;
+        mDragView = null;
+        mLastDockState = TaskStack.DockState.NONE;
+    }
+
+    /**
+     * Handles dragging touch events
+     * @param ev
+     */
+    private void handleTouchEvent(MotionEvent ev) {
+        boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+            case MotionEvent.ACTION_MOVE: {
+                if (mDragging) {
+                    int width = mRv.getMeasuredWidth();
+                    int height = mRv.getMeasuredHeight();
+                    float evX = ev.getX();
+                    float evY = ev.getY();
+                    float x = evX - mDragView.getTopLeftOffset().x;
+                    float y = evY - mDragView.getTopLeftOffset().y;
+
+                    // Update the dock state
+                    TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
+                    TaskStack.DockState foundDockState = TaskStack.DockState.NONE;
+                    for (int i = dockStates.length - 1; i >= 0; i--) {
+                        TaskStack.DockState state = dockStates[i];
+                        if (state.touchAreaContainsPoint(width, height, evX, evY)) {
+                            foundDockState = state;
+                            break;
+                        }
+                    }
+                    if (mLastDockState != foundDockState) {
+                        mLastDockState = foundDockState;
+                        EventBus.getDefault().send(new DragDockStateChangedEvent(mDragTask,
+                                foundDockState));
+                    }
+
+                    mDragView.setTranslationX(x);
+                    mDragView.setTranslationY(y);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                if (mDragging) {
+                    ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
+                            mRv.getContext(), null, null, null);
+                    postAnimationTrigger.increment();
+                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+                            mLastDockState, postAnimationTrigger));
+                    postAnimationTrigger.decrement();
+                    break;
+                }
+            }
+        }
+    }
+}
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 67e3f82..5928854 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,8 +20,8 @@
 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;
@@ -30,20 +30,20 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 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.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;
-import com.android.systemui.statusbar.DismissView;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,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,
@@ -61,7 +63,7 @@
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
-                                      boolean lockToTask, boolean boundsValid, Rect bounds);
+                boolean lockToTask, boolean boundsValid, Rect bounds, int destinationStack);
         public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
         public void onTaskStackFilterTriggered();
         public void onTaskStackUnfilterTriggered();
@@ -77,8 +79,6 @@
     ViewPool<TaskView, Task> mViewPool;
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     DozeTrigger mUIDozeTrigger;
-    DismissView mDismissAllButton;
-    boolean mDismissAllButtonAnimating;
     int mFocusedTaskIndex = -1;
     int mPrevAccessibilityFocusedIndex = -1;
     // Optimizations
@@ -89,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<>();
@@ -318,7 +318,7 @@
 
             if (boundTranslationsToRect) {
                 transform.translationY = Math.min(transform.translationY,
-                        mLayoutAlgorithm.mViewRect.bottom);
+                        mLayoutAlgorithm.mStackRect.bottom);
             }
             prevTransform = transform;
         }
@@ -332,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();
@@ -342,19 +341,6 @@
             boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
                     stackScroll, visibleRange, false);
 
-            // Inflate and add the dismiss button if necessary
-            if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) {
-                mDismissAllButton = (DismissView)
-                        mInflater.inflate(R.layout.recents_dismiss_button, this, false);
-                mDismissAllButton.setOnButtonClickListener(new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        mStack.removeAllTasks();
-                    }
-                });
-                addView(mDismissAllButton, 0);
-            }
-
             // Return all the invisible children to the pool
             mTmpTaskViewMap.clear();
             List<TaskView> taskViews = getTaskViews();
@@ -369,11 +355,6 @@
                 } else {
                     mViewPool.returnViewToPool(tv);
                     reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
-
-                    // Hide the dismiss button if the front most task is invisible
-                    if (task == mStack.getFrontMostTask()) {
-                        hideDismissAllButton(null);
-                    }
                 }
             }
 
@@ -399,12 +380,6 @@
                         }
                         tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
                     }
-
-                    // If we show the front most task view then ensure that the dismiss button
-                    // is visible too.
-                    if (!mAwaitingFirstLayout && (task == mStack.getFrontMostTask())) {
-                        showDismissAllButton();
-                    }
                 }
 
                 // Animate the task into place
@@ -437,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;
                     }
                 }
@@ -461,31 +437,30 @@
                 // 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;
     }
 
     /** Updates the min and max virtual scroll bounds */
-    void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
-            boolean launchedFromHome) {
+    void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);
+        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
 
         // Debug logging
         if (boundScrollToNewMinMax) {
@@ -545,8 +520,8 @@
             if (findClosestToCenter) {
                 // If there is no task focused, then find the task that is closes to the center
                 // of the screen and use that as the currently focused task
-                int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
-                int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
+                int x = mLayoutAlgorithm.mStackRect.centerX();
+                int y = mLayoutAlgorithm.mStackRect.centerY();
                 for (int i = taskViewCount - 1; i >= 0; i--) {
                     TaskView tv = taskViews.get(i);
                     tv.getHitRect(mTmpRect);
@@ -697,7 +672,6 @@
         // Synchronize the views
         synchronizeStackViewsWithModel();
         clipTaskViews();
-        updateDismissButtonPosition();
         // Notify accessibility
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
     }
@@ -709,17 +683,16 @@
         mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
 
         // Update the scroll bounds
-        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
+        updateMinMaxScroll(false);
     }
 
     /**
      * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes
      * of getting the task rect to animate to.
      */
-    public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab,
-            boolean launchedFromHome) {
+    public void updateMinMaxScrollForStack(TaskStack stack) {
         mStack = stack;
-        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
+        updateMinMaxScroll(false);
     }
 
     /**
@@ -730,8 +703,9 @@
         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
     }
 
-    public void setTaskStackBounds(Rect taskStackBounds) {
+    public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) {
         mTaskStackBounds.set(taskStackBounds);
+        mLayoutAlgorithm.setSystemInsets(systemInsets);
     }
 
     /**
@@ -775,16 +749,6 @@
                         MeasureSpec.EXACTLY));
         }
 
-        // Measure the dismiss button
-        if (mDismissAllButton != null) {
-            int taskRectWidth = mLayoutAlgorithm.mTaskRect.width();
-            int dismissAllButtonHeight = getResources().getDimensionPixelSize(
-                    R.dimen.recents_dismiss_all_button_size);
-            mDismissAllButton.measure(
-                    MeasureSpec.makeMeasureSpec(taskRectWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(dismissAllButtonHeight, MeasureSpec.EXACTLY));
-        }
-
         setMeasuredDimension(width, height);
     }
 
@@ -811,15 +775,6 @@
                     mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
         }
 
-        // Layout the dismiss button at the top of the screen, and just translate it accordingly
-        // when synchronizing the views with the model to attach it to the bottom of the front-most
-        // task view
-        if (mDismissAllButton != null) {
-            mDismissAllButton.layout(mLayoutAlgorithm.mTaskRect.left, 0,
-                    mLayoutAlgorithm.mTaskRect.left + mDismissAllButton.getMeasuredWidth(),
-                    mDismissAllButton.getMeasuredHeight());
-        }
-
         if (mAwaitingFirstLayout) {
             mAwaitingFirstLayout = false;
             onFirstLayout();
@@ -828,8 +783,7 @@
 
     /** Handler for the first layout. */
     void onFirstLayout() {
-        int offscreenY = mLayoutAlgorithm.mViewRect.bottom -
-                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
+        int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
 
         // Find the launch target task
         Task launchTargetTask = null;
@@ -924,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) {
@@ -948,9 +901,6 @@
                             tv.setFocusedTask(true);
                         }
                     }
-
-                    // Show the dismiss button
-                    showDismissAllButton();
                 }
             });
         }
@@ -962,10 +912,7 @@
         mStackScroller.stopScroller();
         mStackScroller.stopBoundScrollAnimation();
         // Animate all the task views out of view
-        ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
-                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
-        // Animate the dismiss-all button
-        hideDismissAllButton(null);
+        ctx.offscreenTranslationY = mLayoutAlgorithm.mStackRect.bottom;
 
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
@@ -979,20 +926,14 @@
     public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
         // Clear the focused task
         resetFocusedTask();
-        // Animate the dismiss-all button
-        hideDismissAllButton(new Runnable() {
-            @Override
-            public void run() {
-                List<TaskView> taskViews = getTaskViews();
-                int taskViewCount = taskViews.size();
-                int count = 0;
-                for (int i = taskViewCount - 1; i >= 0; i--) {
-                    TaskView tv = taskViews.get(i);
-                    tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
-                    count++;
-                }
-            }
-        });
+        List<TaskView> taskViews = getTaskViews();
+        int taskViewCount = taskViews.size();
+        int count = 0;
+        for (int i = taskViewCount - 1; i >= 0; i--) {
+            TaskView tv = taskViews.get(i);
+            tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
+            count++;
+        }
     }
 
     /** Animates a task view in this stack as it launches. */
@@ -1013,84 +954,10 @@
         }
     }
 
-    /** Shows the dismiss button */
-    void showDismissAllButton() {
-        if (mDismissAllButton == null) return;
-
-        if (mDismissAllButtonAnimating || mDismissAllButton.getVisibility() != View.VISIBLE ||
-                Float.compare(mDismissAllButton.getAlpha(), 0f) == 0) {
-            mDismissAllButtonAnimating = true;
-            mDismissAllButton.setVisibility(View.VISIBLE);
-            mDismissAllButton.showClearButton();
-            mDismissAllButton.findViewById(R.id.dismiss_text).setAlpha(1f);
-            mDismissAllButton.setAlpha(0f);
-            mDismissAllButton.animate()
-                    .alpha(1f)
-                    .setDuration(250)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mDismissAllButtonAnimating = false;
-                        }
-                    })
-                    .start();
-        }
-    }
-
-    /** Hides the dismiss button */
-    void hideDismissAllButton(final Runnable postAnimRunnable) {
-        if (mDismissAllButton == null) return;
-
-        mDismissAllButtonAnimating = true;
-        mDismissAllButton.animate()
-                .alpha(0f)
-                .setDuration(200)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mDismissAllButtonAnimating = false;
-                        mDismissAllButton.setVisibility(View.GONE);
-                        if (postAnimRunnable != null) {
-                            postAnimRunnable.run();
-                        }
-                    }
-                })
-                .start();
-    }
-
-    /** Updates the dismiss button position */
-    void updateDismissButtonPosition() {
-        if (mDismissAllButton == null) return;
-
-        // Update the position of the clear-all button to hang it off the first task view
-        if (mStack.getTaskCount() > 0) {
-            mTmpCoord[0] = mTmpCoord[1] = 0;
-            TaskView tv = getChildViewForTask(mStack.getFrontMostTask());
-            TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.getTaskCount() - 1);
-            if (tv != null && transform.visible) {
-                Utilities.mapCoordInDescendentToSelf(tv, this, mTmpCoord, false);
-                mDismissAllButton.setTranslationY(mTmpCoord[1] + (tv.getScaleY() * tv.getHeight()));
-                mDismissAllButton.setTranslationX(-(mLayoutAlgorithm.mStackRect.width() -
-                        transform.rect.width()) / 2f);
-            }
-        }
-    }
-
-    /** 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;
@@ -1113,7 +980,8 @@
     }
 
     @Override
-    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
+    public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+            Task newFrontMostTask) {
         // Remove the view associated with this task, we can't rely on updateTransforms
         // to work here because the task is no longer in the list
         TaskView tv = getChildViewForTask(removedTask);
@@ -1132,14 +1000,18 @@
         }
 
         // Update the min/max scroll and animate other task views into their new positions
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        updateMinMaxScroll(true, launchState.launchedWithAltTab, launchState.launchedFromHome);
+        updateMinMaxScroll(true);
 
-        // Offset the stack by as much as the anchor task would otherwise move back
-        if (pullStackForward) {
+        if (wasFrontMostTask) {
+            // Since the max scroll progress is offset from the bottom of the stack, just scroll
+            // to ensure that the new front most task is now fully visible
+            mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
+        } else if (pullStackForward) {
+            // Otherwise, offset the scroll by half the movement of the anchor task to allow the
+            // tasks behind the removed task to move forward, and the tasks in front to move back
             float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
             mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
-                    - prevAnchorTaskScroll));
+                    - prevAnchorTaskScroll) / 2);
             mStackScroller.boundScroll();
         }
 
@@ -1167,9 +1039,6 @@
             if (shouldFinishActivity) {
                 mCb.onAllTaskViewsDismissed(null);
             }
-        } else {
-            // Fade the dismiss button back in
-            showDismissAllButton();
         }
     }
 
@@ -1269,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);
@@ -1293,12 +1162,10 @@
         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
-        if (mUIDozeTrigger.hasTriggered()) {
-            tv.setNoUserInteractionState();
-        }
+        tv.setNoUserInteractionState();
 
         // If we've finished the start animation, then ensure we always enable the focus animations
         if (mStartEnterAnimationCompleted) {
@@ -1315,8 +1182,7 @@
             for (int i = 0; i < taskViewCount; i++) {
                 Task tvTask = taskViews.get(i).getTask();
                 if (taskIndex < mStack.indexOfTask(tvTask)) {
-                    // Offset by 1 if we have a dismiss-all button
-                    insertIndex = i + (Constants.DebugFlags.App.EnableDismissAll ? 1 : 0);
+                    insertIndex = i;
                     break;
                 }
             }
@@ -1353,7 +1219,8 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, false, null);
+            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, false, null,
+                    INVALID_STACK_ID);
         }
     }
 
@@ -1384,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
@@ -1421,6 +1288,7 @@
 
         // Remove the task from the view
         mStack.removeTask(task);
+
         // If the dismissed task was focused, then we should focus the new task in the same index
         if (taskWasFocused) {
             ArrayList<Task> tasks = mStack.getTasks();
@@ -1437,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/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index a8e6f47..9a5d9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -17,26 +17,33 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
+import android.util.Log;
 import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.ParametricCurve;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 
-/* The layout logic for a TaskStackView.
- *
- * We are using a curve that defines the curve of the tasks as that go back in the recents list.
- * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
- * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
+
+/**
+ * The layout logic for a TaskStackView.
  */
 public class TaskStackViewLayoutAlgorithm {
 
-    // These are all going to change
-    static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
+    private static final boolean DEBUG = false;
+    private static final String TAG = "TaskStackViewLayoutAlgorithm";
+
+    // The min scale of the last task at the top of the curve
+    private static final float STACK_PEEK_MIN_SCALE = 0.75f;
+    // The scale of the last task
+    private static final float SINGLE_TASK_SCALE = 0.95f;
+    // The percentage of height of task to show between tasks
+    private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
 
     // A report of the visibility state of the stack
     public class VisibilityReport {
@@ -53,67 +60,169 @@
     Context mContext;
     RecentsConfiguration mConfig;
 
-    // The various rects that define the stack view
-    Rect mViewRect = new Rect();
-    Rect mStackVisibleRect = new Rect();
-    Rect mStackRect = new Rect();
-    Rect mTaskRect = new Rect();
+    // This is the view bounds inset exactly by the search bar, but without the bottom inset
+    // see RecentsConfiguration.getTaskStackBounds()
+    public Rect mStackRect = new Rect();
+    // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
+    // of the stack rect
+    public Rect mTaskRect = new Rect();
+    // This is the current system insets
+    public Rect mSystemInsets = new Rect();
 
-    // The min/max scroll progress
+    // The smallest scroll progress, at this value, the back most task will be visible
     float mMinScrollP;
+    // The largest scroll progress, at this value, the front most task will be visible above the
+    // navigation bar
     float mMaxScrollP;
+    // The initial progress that the scroller is set
     float mInitialScrollP;
-    int mWithinAffiliationOffset;
-    int mBetweenAffiliationOffset;
-    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>();
+
+    // The relative progress to ensure that the height between affiliated tasks is respected
+    float mWithinAffiliationPOffset;
+    // The relative progress to ensure that the height between non-affiliated tasks is
+    // respected
+    float mBetweenAffiliationPOffset;
+    // The relative progress to ensure that the task height is respected
+    float mTaskHeightPOffset;
+    // The relative progress to ensure that the half task height is respected
+    float mTaskHalfHeightPOffset;
+    // The relative progress to ensure that the offset from the bottom of the stack to the bottom
+    // of the task is respected
+    float mTaskBottomPOffset;
+    // The front-most task bottom offset
+    int mTaskBottomOffset;
+
+    // The last computed task count
+    int mNumTasks;
+    // The min/max z translations
+    int mMinTranslationZ;
+    int mMaxTranslationZ;
+
+    // Optimization, allows for quick lookup of task -> progress
+    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
 
     // Log function
-    static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve
-    static final float LogBase = 3000;
-    static final int PrecisionSteps = 250;
-    static float[] xp;
-    static float[] px;
+    static ParametricCurve sCurve;
 
     public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) {
+        Resources res = context.getResources();
+        mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
+        mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
         mContext = context;
         mConfig = config;
+        if (sCurve == null) {
+            sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
+                // The large the XScale, the longer the flat area of the curve
+                private static final float XScale = 1.75f;
+                private static final float LogBase = 3000;
 
-        // Precompute the path
-        initializeCurve();
+                float reverse(float x) {
+                    return (-x * XScale) + 1;
+                }
+
+                @Override
+                public float f(float x) {
+                    return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
+                }
+
+                @Override
+                public float invF(float y) {
+                    return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
+                }
+            }, new ParametricCurve.ParametricCurveFunction() {
+                @Override
+                public float f(float p) {
+                    if (p < 0) return STACK_PEEK_MIN_SCALE;
+                    if (p > 1) return 1f;
+                    float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
+                    float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
+                    return scale;
+                }
+            });
+        }
     }
 
-    /** Computes the stack and task rects */
-    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
-        // Compute the stack rects
-        mViewRect.set(0, 0, windowWidth, windowHeight);
-        mStackRect.set(taskStackBounds);
-        mStackVisibleRect.set(taskStackBounds);
-        mStackVisibleRect.bottom = mViewRect.bottom;
+    /**
+     * Sets the system insets.
+     */
+    public void setSystemInsets(Rect systemInsets) {
+        mSystemInsets.set(systemInsets);
+        if (DEBUG) {
+            Log.d(TAG, "setSystemInsets: " + systemInsets);
+        }
+    }
 
-        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
+    /**
+     * Computes the stack and task rects
+     */
+    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
+        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width());
         int heightPadding = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_stack_top_padding);
-        mStackRect.inset(widthPadding, heightPadding);
 
-        // Compute the task rect
-        int size = mStackRect.width();
-        int left = mStackRect.left + (mStackRect.width() - size) / 2;
-        mTaskRect.set(left, mStackRect.top,
-                left + size, mStackRect.top + size);
+        // Compute the stack rect, inset from the given task stack bounds
+        mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
+                taskStackBounds.right - widthPadding, windowHeight);
+        mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
 
-        // Update the affiliation offsets
-        float visibleTaskPct = 0.5f;
-        mWithinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
+        // Compute the task rect, align it to the top-center square in the stack rect
+        int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset);
+        int xOffset = (mStackRect.width() - size) / 2;
+        mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
+                mStackRect.right - xOffset, mStackRect.top + size);
+
+        // Compute the progress offsets
+        int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
-        mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
+        int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
+        mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
+                mStackRect);
+        mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
+                mStackRect);
+        mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
+                mStackRect);
+        mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
+                mStackRect);
+        mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
+
+        if (DEBUG) {
+            Log.d(TAG, "computeRects");
+            Log.d(TAG, "\tmStackRect: " + mStackRect);
+            Log.d(TAG, "\tmTaskRect: " + mTaskRect);
+            Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
+
+            Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
+            Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
+            Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
+            Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
+            Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset);
+
+            Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
+            Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
+
+            for (int height = 0; height <= 1000; height += 50) {
+                float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
+                float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
+                Log.d(TAG, "offset: " + height + ", " +
+                        p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
+                                sCurve.pToScale(1f - p) + ", " +
+                        p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
+            }
+        }
     }
 
-    /** Computes the minimum and maximum scroll progress values.  This method may be called before
-     * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
-    void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
-            boolean launchedFromHome) {
+    /**
+     * Computes the minimum and maximum scroll progress values.  This method may be called before
+     * the RecentsConfiguration is set, so we need to pass in the alt-tab state.
+     */
+    void computeMinMaxScroll(ArrayList<Task> tasks) {
+        if (DEBUG) {
+            Log.d(TAG, "computeMinMaxScroll");
+        }
+
         // Clear the progress map
         mTaskProgressMap.clear();
+        mNumTasks = tasks.size();
 
         // Return early if we have no tasks
         if (tasks.isEmpty()) {
@@ -121,57 +230,41 @@
             return;
         }
 
-        // Note that we should account for the scale difference of the offsets at the screen bottom
-        int taskHeight = mTaskRect.height();
-        float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
-        float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
-                mWithinAffiliationOffset);
-        float scale = curveProgressToScale(pWithinAffiliateTop);
-        int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
-        pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
-                mWithinAffiliationOffset + scaleYOffset);
-        float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
-        float pBetweenAffiliateOffset = pAtBottomOfStackRect -
-                screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
-        float pTaskHeightOffset = pAtBottomOfStackRect -
-                screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
-        float pNavBarOffset = pAtBottomOfStackRect -
-                screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
-                        mStackRect.bottom));
-        float pDismissAllButtonOffset = 0f;
-        if (Constants.DebugFlags.App.EnableDismissAll) {
-            int dismissAllButtonHeight = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.recents_dismiss_all_button_size);
-            pDismissAllButtonOffset = pAtBottomOfStackRect -
-                screenYToCurveProgress(mStackVisibleRect.bottom - dismissAllButtonHeight);
-        }
-
-        // Update the task offsets
-        float pAtBackMostCardTop = 0.5f;
-        float pAtFrontMostCardTop = pAtBackMostCardTop;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
-
-            if (i < (taskCount - 1)) {
-                // Increment the peek height
-                float pPeek = task.group.isFrontMostTask(task) ?
-                        pBetweenAffiliateOffset : pWithinAffiliateOffset;
-                pAtFrontMostCardTop += pPeek;
-            }
-        }
-
-        mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset -
-                ((1f - pTaskHeightOffset - pNavBarOffset));
-        mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
-        if (launchedWithAltTab && launchedFromHome) {
-            // Center the top most task, since that will be focused first
-            mInitialScrollP = mMaxScrollP;
+        // We calculate the progress by taking the progress of the element from the bottom of the
+        // screen
+        if (mNumTasks == 1) {
+            // Just center the task in the visible stack rect
+            mMinScrollP = mMaxScrollP = mInitialScrollP = 0f;
+            mTaskProgressMap.put(tasks.get(0).key, 0f);
         } else {
-            mInitialScrollP = pAtFrontMostCardTop - 0.825f;
+            // Update the tasks from back to front with the new progresses. We set the initial
+            // progress to the progress at which the top of the last task is near the center of the
+            // visible stack rect.
+            float pAtBackMostTaskTop = 0;
+            float pAtFrontMostTaskTop = pAtBackMostTaskTop;
+            int taskCount = tasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task task = tasks.get(i);
+                mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+
+                if (i < (taskCount - 1)) {
+                    // Increment the peek height
+                    float pPeek = task.group.isFrontMostTask(task) ?
+                            mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
+                    pAtFrontMostTaskTop += pPeek;
+                }
+            }
+
+            // Set the max scroll progress to the point at which the bottom of the front-most task
+            // is aligned to the bottom of the stack (including nav bar and stack padding)
+            mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
+            // Basically align the back-most task such that its progress is the same as the top of
+            // the front most task at the max scroll
+            mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
+            // The offset the inital scroll position to the front of the stack, with half the front
+            // task height visible
+            mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
         }
-        mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP));
     }
 
     /**
@@ -190,7 +283,7 @@
         int numVisibleTasks = 1;
         int numVisibleThumbnails = 1;
         float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
-        int prevScreenY = curveProgressToScreenY(progress);
+        int prevScreenY = sCurve.pToX(progress, mStackRect);
         for (int i = tasks.size() - 2; i >= 0; i--) {
             Task task = tasks.get(i);
             progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
@@ -199,9 +292,9 @@
             }
             boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
             if (isFrontMostTaskInGroup) {
-                float scaleAtP = curveProgressToScale(progress);
+                float scaleAtP = sCurve.pToScale(progress);
                 int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
-                int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
+                int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
                 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
                 if (hasVisibleThumbnail) {
                     numVisibleThumbnails++;
@@ -227,7 +320,10 @@
         return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
     }
 
-    /** Update/get the transform */
+    /**
+     * Returns the transform for the given task.  This transform is relative to the mTaskRect, which
+     * is what the view is measured and laid out with.
+     */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
         // Return early if we have an invalid index
@@ -242,154 +338,72 @@
     /** Update/get the transform */
     public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
-        float pTaskRelative = taskProgress - stackScroll;
-        float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
-        // If the task top is outside of the bounds below the screen, then immediately reset it
-        if (pTaskRelative > 1f) {
-            transformOut.reset();
-            transformOut.rect.set(mTaskRect);
-            return transformOut;
+        if (DEBUG) {
+            Log.d(TAG, "getStackTransform: " + stackScroll);
         }
-        // The check for the top is trickier, since we want to show the next task if it is at all
-        // visible, even if p < 0.
-        if (pTaskRelative < 0f) {
-            if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
+
+        if (mNumTasks == 1) {
+            // Center the task in the stack, changing the scale will not follow the curve, but just
+            // modulate some values directly
+            float pTaskRelative = -stackScroll;
+            float scale = SINGLE_TASK_SCALE;
+            int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
+            transformOut.scale = scale;
+            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+            transformOut.translationZ = mMaxTranslationZ;
+            transformOut.rect.set(mTaskRect);
+            transformOut.rect.offset(0, transformOut.translationY);
+            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+            transformOut.visible = true;
+            transformOut.p = pTaskRelative;
+            return transformOut;
+        } else {
+            float pTaskRelative = taskProgress - stackScroll;
+            float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+            // If the task top is outside of the bounds below the screen, then immediately reset it
+            if (pTaskRelative > 1f) {
                 transformOut.reset();
                 transformOut.rect.set(mTaskRect);
                 return transformOut;
             }
+            // The check for the top is trickier, since we want to show the next task if it is at
+            // all visible, even if p < 0.
+            if (pTaskRelative < 0f) {
+                if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
+                    transformOut.reset();
+                    transformOut.rect.set(mTaskRect);
+                    return transformOut;
+                }
+            }
+            float scale = sCurve.pToScale(pBounded);
+            int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
+            transformOut.scale = scale;
+            transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
+                    scaleYOffset;
+            transformOut.translationZ = Math.max(mMinTranslationZ,
+                    mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
+            transformOut.rect.set(mTaskRect);
+            transformOut.rect.offset(0, transformOut.translationY);
+            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+            transformOut.visible = true;
+            transformOut.p = pTaskRelative;
+            return transformOut;
         }
-        float scale = curveProgressToScale(pBounded);
-        int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
-        int minZ = mContext.getResources().getDimensionPixelSize(R.dimen.recents_task_view_z_min);
-        int maxZ = mContext.getResources().getDimensionPixelSize(R.dimen.recents_task_view_z_max);
-        transformOut.scale = scale;
-        transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
-                scaleYOffset;
-        transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
-        transformOut.rect.set(mTaskRect);
-        transformOut.rect.offset(0, transformOut.translationY);
-        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-        transformOut.visible = true;
-        transformOut.p = pTaskRelative;
-        return transformOut;
     }
 
-    /** Returns the untransformed task view bounds. */
+    /**
+     * Returns the untransformed task view bounds.
+     */
     public Rect getUntransformedTaskViewBounds() {
         return new Rect(mTaskRect);
     }
 
-    /** Returns the scroll to such task top = 1f; */
+    /**
+     * Returns the scroll progress to scroll to such that the top of the task is at the top of the
+     * stack.
+     */
     float getStackScrollForTask(Task t) {
         if (!mTaskProgressMap.containsKey(t.key)) return 0f;
         return mTaskProgressMap.get(t.key);
     }
-
-    /** Initializes the curve. */
-    public static void initializeCurve() {
-        if (xp != null && px != null) return;
-        xp = new float[PrecisionSteps + 1];
-        px = new float[PrecisionSteps + 1];
-
-        // Approximate f(x)
-        float[] fx = new float[PrecisionSteps + 1];
-        float step = 1f / PrecisionSteps;
-        float x = 0;
-        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
-            fx[xStep] = logFunc(x);
-            x += step;
-        }
-        // Calculate the arc length for x:1->0
-        float pLength = 0;
-        float[] dx = new float[PrecisionSteps + 1];
-        dx[0] = 0;
-        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
-            dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
-            pLength += dx[xStep];
-        }
-        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
-        float p = 0;
-        px[0] = 0f;
-        px[PrecisionSteps] = 1f;
-        for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
-            p += Math.abs(dx[xStep] / pLength);
-            px[xStep] = p;
-        }
-        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
-        // function.
-        int xStep = 0;
-        p = 0;
-        xp[0] = 0f;
-        xp[PrecisionSteps] = 1f;
-        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
-            // Walk forward in px and find the x where px <= p && p < px+1
-            while (xStep < PrecisionSteps) {
-                if (px[xStep] > p) break;
-                xStep++;
-            }
-            // Now, px[xStep-1] <= p < px[xStep]
-            if (xStep == 0) {
-                xp[pStep] = 0;
-            } else {
-                // Find x such that proportionally, x is correct
-                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
-                x = (xStep - 1 + fraction) * step;
-                xp[pStep] = x;
-            }
-            p += step;
-        }
-    }
-
-    /** Reverses and scales out x. */
-    static float reverse(float x) {
-        return (-x * XScale) + 1;
-    }
-    /** The log function describing the curve. */
-    static float logFunc(float x) {
-        return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
-    }
-    /** The inverse of the log function describing the curve. */
-    float invLogFunc(float y) {
-        return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
-    }
-
-    /** Converts from the progress along the curve to a screen coordinate. */
-    int curveProgressToScreenY(float p) {
-        if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
-        float pIndex = p * PrecisionSteps;
-        int pFloorIndex = (int) Math.floor(pIndex);
-        int pCeilIndex = (int) Math.ceil(pIndex);
-        float xFraction = 0;
-        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
-            float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
-            xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
-        }
-        float x = xp[pFloorIndex] + xFraction;
-        return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
-    }
-
-    /** Converts from the progress along the curve to a scale. */
-    float curveProgressToScale(float p) {
-        if (p < 0) return StackPeekMinScale;
-        if (p > 1) return 1f;
-        float scaleRange = (1f - StackPeekMinScale);
-        float scale = StackPeekMinScale + (p * scaleRange);
-        return scale;
-    }
-
-    /** Converts from a screen coordinate to the progress along the curve. */
-    float screenYToCurveProgress(int screenY) {
-        float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
-        if (x < 0 || x > 1) return x;
-        float xIndex = x * PrecisionSteps;
-        int xFloorIndex = (int) Math.floor(xIndex);
-        int xCeilIndex = (int) Math.ceil(xIndex);
-        float pFraction = 0;
-        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
-            float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
-            pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
-        }
-        return px[xFloorIndex] + pFraction;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index e4fbc76..3d3b13d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -175,11 +175,11 @@
     /**** OverScroller ****/
 
     int progressToScrollRange(float p) {
-        return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height());
+        return (int) (p * mLayoutAlgorithm.mStackRect.height());
     }
 
     float scrollRangeToProgress(int s) {
-        return (float) s / mLayoutAlgorithm.mStackVisibleRect.height();
+        return (float) s / mLayoutAlgorithm.mStackRect.height();
     }
 
     /** Called from the view draw, computes the next scroll. */
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 48c5b46..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;
@@ -136,6 +136,7 @@
             return true;
         }
 
+        TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
         boolean wasScrolling = mScroller.isScrolling() ||
                 (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
         int action = ev.getAction();
@@ -144,7 +145,8 @@
                 // Save the touch down info
                 mInitialMotionX = mLastMotionX = (int) ev.getX();
                 mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
+                        layoutAlgorithm.mStackRect);
                 mActivePointerId = ev.getPointerId(0);
                 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
                 // Stop the current scroll if it is still flinging
@@ -155,6 +157,14 @@
                 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
                 break;
             }
+            case MotionEvent.ACTION_POINTER_DOWN: {
+                final int index = ev.getActionIndex();
+                mActivePointerId = ev.getPointerId(index);
+                mLastMotionX = (int) ev.getX(index);
+                mLastMotionY = (int) ev.getY(index);
+                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
+                break;
+            }
             case MotionEvent.ACTION_MOVE: {
                 if (mActivePointerId == INACTIVE_POINTER_ID) break;
 
@@ -177,7 +187,21 @@
 
                 mLastMotionX = x;
                 mLastMotionY = y;
-                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                int pointerIndex = ev.getActionIndex();
+                int pointerId = ev.getPointerId(pointerIndex);
+                if (pointerId == mActivePointerId) {
+                    // Select a new active pointer id and reset the motion state
+                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+                    mActivePointerId = ev.getPointerId(newPointerIndex);
+                    mLastMotionX = (int) ev.getX(newPointerIndex);
+                    mLastMotionY = (int) ev.getY(newPointerIndex);
+                    mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
+                    mVelocityTracker.clear();
+                }
                 break;
             }
             case MotionEvent.ACTION_CANCEL:
@@ -213,13 +237,15 @@
         // Update the velocity tracker
         initVelocityTrackerIfNotExists();
 
+        TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
         int action = ev.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 // Save the touch down info
                 mInitialMotionX = mLastMotionX = (int) ev.getX();
                 mInitialMotionY = mLastMotionY = (int) ev.getY();
-                mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
+                        layoutAlgorithm.mStackRect);
                 mActivePointerId = ev.getPointerId(0);
                 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
                 // Stop the current scroll if it is still flinging
@@ -240,7 +266,8 @@
                 mActivePointerId = ev.getPointerId(index);
                 mLastMotionX = (int) ev.getX(index);
                 mLastMotionY = (int) ev.getY(index);
-                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
+                        layoutAlgorithm.mStackRect);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -252,7 +279,7 @@
                 int x = (int) ev.getX(activePointerIndex);
                 int y = (int) ev.getY(activePointerIndex);
                 int yTotal = Math.abs(y - mInitialMotionY);
-                float curP = mSv.mLayoutAlgorithm.screenYToCurveProgress(y);
+                float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect);
                 float deltaP = mLastP - curP;
                 if (!mIsScrolling) {
                     if (yTotal > mScrollTouchSlop) {
@@ -279,7 +306,7 @@
                 }
                 mLastMotionX = x;
                 mLastMotionY = y;
-                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
                 mTotalPMotion += Math.abs(deltaP);
                 break;
             }
@@ -324,7 +351,7 @@
                     mActivePointerId = ev.getPointerId(newPointerIndex);
                     mLastMotionX = (int) ev.getX(newPointerIndex);
                     mLastMotionY = (int) ev.getY(newPointerIndex);
-                    mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
+                    mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
                     mVelocityTracker.clear();
                 }
                 break;
@@ -368,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 */
@@ -420,8 +446,6 @@
         if (parent != null) {
             parent.requestDisallowInterceptTouchEvent(true);
         }
-        // Fade out the dismiss button
-        mSv.hideDismissAllButton(null);
     }
 
     @Override
@@ -450,8 +474,6 @@
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
-        // Restore the dismiss button
-        mSv.showDismissAllButton();
     }
 
     @Override
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 910db87..bab4da7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -21,13 +21,17 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.animation.AccelerateInterpolator;
@@ -36,17 +40,20 @@
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+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.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.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 /* A task view */
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
-        View.OnClickListener {
+        View.OnClickListener, View.OnLongClickListener {
 
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
@@ -79,6 +86,8 @@
     View mActionButtonView;
     TaskViewCallbacks mCb;
 
+    Point mDownTouchPos = new Point();
+
     Interpolator mFastOutSlowInInterpolator;
     Interpolator mFastOutLinearInInterpolator;
     Interpolator mQuintOutInterpolator;
@@ -156,7 +165,6 @@
         mContent = findViewById(R.id.task_view_content);
         mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
         mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
-        mThumbnailView.updateClipToTaskBar(mHeaderView);
         mActionButtonView = findViewById(R.id.lock_to_app_fab);
         mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
             @Override
@@ -169,6 +177,14 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
@@ -191,6 +207,7 @@
         mThumbnailView.measure(
                 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+        mThumbnailView.updateClipToTaskBar(mHeaderView);
         setMeasuredDimension(width, height);
         invalidateOutline();
     }
@@ -608,8 +625,12 @@
 
     /** Compute the dim as a function of the scale of this view. */
     int getDimFromTaskProgress() {
+        // TODO: Temporarily disable the dim on the stack
+        /*
         float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
         return (int) (dim * 255);
+        */
+        return 0;
     }
 
     /** Update the dim as a function of the scale of this view. */
@@ -719,6 +740,7 @@
             mHeaderView.rebindToTask(mTask);
             // Rebind any listeners
             mActionButtonView.setOnClickListener(this);
+            setOnLongClickListener(mConfig.hasDockedTasks ? null : this);
         }
         mTaskDataLoaded = true;
     }
@@ -737,7 +759,7 @@
     }
 
     @Override
-    public void onMultiStackDebugTaskStackIdChanged() {
+    public void onTaskStackIdChanged() {
         mHeaderView.rebindToTask(mTask);
     }
 
@@ -753,4 +775,75 @@
             mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView));
         }
     }
+
+    /**** View.OnLongClickListener Implementation ****/
+
+    @Override
+    public boolean onLongClick(View v) {
+        if (v == this) {
+            // Start listening for drag events
+            setClipViewInStack(false);
+
+            final int width = (int) (getScaleX() * getWidth());
+            final int height = (int) (getScaleY() * getHeight());
+            Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(dragBitmap);
+            c.scale(getScaleX(), getScaleY());
+            mThumbnailView.draw(c);
+            mHeaderView.draw(c);
+            c.setBitmap(null);
+
+            // Initiate the drag
+            final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
+            dragView.setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    outline.setRect(0, 0, width, height);
+                }
+            });
+            dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    // Hide this task view after the drag view is attached
+                    setVisibility(View.INVISIBLE);
+                    // Animate the alpha slightly to indicate dragging
+                    dragView.setElevation(getElevation());
+                    dragView.setTranslationZ(getTranslationZ());
+                    dragView.animate()
+                            .scaleX(1.05f)
+                            .scaleY(1.05f)
+                            .setDuration(175)
+                            .setInterpolator(mFastOutSlowInInterpolator)
+                            .start();
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    // Do nothing
+                }
+            });
+            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+            EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+            return true;
+        }
+        return false;
+    }
+
+    /**** Events ****/
+
+    public final void onBusEvent(DragEndEvent event) {
+        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // If docked state == null:
+                // Animate the drag view back from where it is, to the view location, then after it returns,
+                // update the clip state
+                setClipViewInStack(true);
+
+                // Show this task view
+                setVisibility(View.VISIBLE);
+            }
+        });
+        EventBus.getDefault().unregister(this);
+    }
 }
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 7de8b7b..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() {
@@ -244,11 +239,9 @@
                 mLightDismissDrawable : mDarkDismissDrawable);
         mDismissButton.setContentDescription(String.format(mDismissContentDescription,
                 t.contentDescription));
-        mMoveTaskButton.setVisibility((mConfig.multiWindowEnabled) ? View.VISIBLE : View.INVISIBLE);
-        if (mConfig.multiWindowEnabled) {
-            updateResizeTaskBarIcon(t);
-            mMoveTaskButton.setOnClickListener(this);
-        }
+        updateResizeTaskBarIcon(t);
+        mMoveTaskButton.setVisibility(View.VISIBLE);
+        mMoveTaskButton.setOnClickListener(this);
 
         // In accessibility, a single click on the focused app info button will show it
         AccessibilityManager am = (AccessibilityManager) getContext().
@@ -263,16 +256,14 @@
         mTask = null;
         mApplicationIcon.setImageDrawable(null);
         mApplicationIcon.setOnClickListener(null);
-
-        if (mConfig.multiWindowEnabled) {
-            mMoveTaskButton.setOnClickListener(null);
-        }
+        mMoveTaskButton.setOnClickListener(null);
     }
 
     /** 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;
@@ -471,7 +462,7 @@
             // In accessibility, a single click on the focused app info button will show it
             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
         } else if (v == mDismissButton) {
-            TaskView tv = (TaskView) getParent().getParent();
+            TaskView tv = Utilities.findParent(this, TaskView.class);
             tv.dismissTask();
 
             // Keep track of deletions by the dismiss button
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 a55e026..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;
@@ -31,9 +31,12 @@
     public float scale = 1f;
     public float alpha = 1f;
     public boolean visible = false;
-    public Rect rect = new Rect();
     float p = 0f;
 
+    // This is a window-space rect that is purely used for coordinating the animation of an app
+    // window into Recents.
+    public RectF rect = new RectF();
+
     public TaskViewTransform() {
         // Do nothing
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index e7a3c8a..8938669 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1539,6 +1539,7 @@
             if (viableAction != null) {
                 Notification stripped = n.clone();
                 Notification.Builder.stripForDelivery(stripped);
+                stripped.extras.putBoolean("android.rebuild", true);
                 stripped.actions = new Notification.Action[] { viableAction };
                 stripped.extras.putBoolean("android.rebuild.contentView", true);
                 stripped.contentView = null;
@@ -1547,6 +1548,13 @@
                 stripped.extras.putBoolean("android.rebuild.hudView", true);
                 stripped.headsUpContentView = null;
 
+                stripped.extras.putParcelable(Notification.EXTRA_LARGE_ICON,
+                        stripped.getLargeIcon());
+                if (SystemProperties.getBoolean("debug.strip_third_line", false)) {
+                    stripped.extras.putCharSequence(Notification.EXTRA_INFO_TEXT, null);
+                    stripped.extras.putCharSequence(Notification.EXTRA_SUMMARY_TEXT, null);
+                }
+
                 Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
 
                 n.actions = rebuilt.actions;
@@ -1580,27 +1588,88 @@
         if (remoteInput != null) {
             View bigContentView = entry.getExpandedContentView();
             if (bigContentView != null) {
-                inflateRemoteInput(bigContentView, remoteInput, actions);
+                inflateRemoteInput(bigContentView, entry, remoteInput, actions);
             }
             View headsUpContentView = entry.getHeadsUpContentView();
             if (headsUpContentView != null) {
-                inflateRemoteInput(headsUpContentView, remoteInput, actions);
+                inflateRemoteInput(headsUpContentView, entry, remoteInput, actions);
             }
         }
 
     }
 
-    private void inflateRemoteInput(View view, RemoteInput remoteInput,
+    private void inflateRemoteInput(View view, Entry entry, RemoteInput remoteInput,
             Notification.Action[] actions) {
         View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions);
         if (actionContainerCandidate instanceof ViewGroup) {
             ViewGroup actionContainer = (ViewGroup) actionContainerCandidate;
-            actionContainer.removeAllViews();
-            actionContainer.addView(
-                    RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput));
+            RemoteInputView riv = inflateRemoteInputView(actionContainer, entry,
+                    actions[0], remoteInput);
+            if (riv != null) {
+                actionContainer.removeAllViews();
+                actionContainer.addView(riv);
+            }
         }
     }
 
+    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
+            Notification.Action action, RemoteInput remoteInput) {
+        return null;
+    }
+
+    public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+        if (!isDeviceProvisioned()) return;
+
+        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+        final boolean afterKeyguardGone = intent.isActivity()
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mCurrentUserId);
+        dismissKeyguardThenExecute(new OnDismissAction() {
+            public boolean onDismiss() {
+                new Thread() {
+                    @Override
+                    public void run() {
+                        try {
+                            if (keyguardShowing && !afterKeyguardGone) {
+                                ActivityManagerNative.getDefault()
+                                        .keyguardWaitingForActivityDrawn();
+                            }
+
+                            // The intent we are sending is for the application, which
+                            // won't have permission to immediately start an activity after
+                            // the user switches to home.  We know it is safe to do at this
+                            // point, so make sure new activity switches are now allowed.
+                            ActivityManagerNative.getDefault().resumeAppSwitches();
+                        } catch (RemoteException e) {
+                        }
+
+                        try {
+                            intent.send();
+                        } catch (PendingIntent.CanceledException e) {
+                            // the stack trace isn't very helpful here.
+                            // Just log the exception message.
+                            Log.w(TAG, "Sending intent failed: " + e);
+
+                            // TODO: Dismiss Keyguard.
+                        }
+                        if (intent.isActivity()) {
+                            mAssistManager.hideAssist();
+                            overrideActivityPendingAppTransition(keyguardShowing
+                                    && !afterKeyguardGone);
+                        }
+                    }
+                }.start();
+
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                        true /* force */, true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            }
+        }, afterKeyguardGone);
+    }
+
     private final class NotificationClicker implements View.OnClickListener {
         public void onClick(final View v) {
             if (!(v instanceof ExpandableNotificationRow)) {
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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5e6fdd0..6d13947 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -200,12 +200,12 @@
         switch (mChargingSpeed) {
             case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
                 chargingId = hasChargingTime
-                        ? R.string.keyguard_indication_charging_time_fast_if_translated
+                        ? R.string.keyguard_indication_charging_time_fast
                         : R.string.keyguard_plugged_in_charging_fast;
                 break;
             case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
                 chargingId = hasChargingTime
-                        ? R.string.keyguard_indication_charging_time_slowly_if_translated
+                        ? R.string.keyguard_indication_charging_time_slowly
                         : R.string.keyguard_plugged_in_charging_slowly;
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
new file mode 100644
index 0000000..f243b00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -0,0 +1,102 @@
+/*
+ * 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.statusbar;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Keeps track of the currently active {@link RemoteInputView}s.
+ */
+public class RemoteInputController {
+
+    private final ArrayList<WeakReference<NotificationData.Entry>> mRemoteInputs = new ArrayList<>();
+    private final StatusBarWindowManager mStatusBarWindowManager;
+    private final HeadsUpManager mHeadsUpManager;
+
+    public RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager) {
+        mStatusBarWindowManager = sbwm;
+        mHeadsUpManager = headsUpManager;
+    }
+
+    public void addRemoteInput(NotificationData.Entry entry) {
+        Preconditions.checkNotNull(entry);
+
+        boolean found = pruneWeakThenRemoveAndContains(
+                entry /* contains */, null /* remove */);
+        if (!found) {
+            mRemoteInputs.add(new WeakReference<>(entry));
+        }
+
+        apply(entry);
+    }
+
+    public void removeRemoteInput(NotificationData.Entry entry) {
+        Preconditions.checkNotNull(entry);
+
+        pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */);
+
+        apply(entry);
+    }
+
+    private void apply(NotificationData.Entry entry) {
+        mStatusBarWindowManager.setRemoteInputActive(isRemoteInputActive());
+        mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+    }
+
+    /**
+     * @return true if {@param entry} has an active RemoteInput
+     */
+    public boolean isRemoteInputActive(NotificationData.Entry entry) {
+        return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */);
+    }
+
+    /**
+     * @return true if any entry has an active RemoteInput
+     */
+    public boolean isRemoteInputActive() {
+        pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */);
+        return !mRemoteInputs.isEmpty();
+    }
+
+    /**
+     * Prunes dangling weak references, removes entries referring to {@param remove} and returns
+     * whether {@param contains} is part of the array in a single loop.
+     * @param remove if non-null, removes this entry from the active remote inputs
+     * @return true if {@param contains} is in the set of active remote inputs
+     */
+    private boolean pruneWeakThenRemoveAndContains(
+            NotificationData.Entry contains, NotificationData.Entry remove) {
+        boolean found = false;
+        for (int i = mRemoteInputs.size() - 1; i >= 0; i--) {
+            NotificationData.Entry item = mRemoteInputs.get(i).get();
+            if (item == null || item == remove) {
+                mRemoteInputs.remove(i);
+            } else if (item == contains) {
+                found = true;
+            }
+        }
+        return found;
+    }
+
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index f7c3c67..cc30882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -250,6 +250,7 @@
     @Override
     public void setNoSims(boolean show) {
         mNoSimsVisible = show && !mBlockMobile;
+        apply();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
index 9ef320bc..8f689c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.PendingIntent;
 import android.content.Intent;
 
 /**
@@ -24,6 +25,7 @@
  * Keyguard.
  */
 public interface ActivityStarter {
+    void startPendingIntentDismissingKeyguard(PendingIntent intent);
     void startActivity(Intent intent, boolean dismissShade);
     void startActivity(Intent intent, boolean dismissShade, Callback callback);
     void preventNextAnimation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
new file mode 100644
index 0000000..497f044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.phone;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+
+public abstract class BaseStatusBarHeader extends RelativeLayout implements
+        NetworkControllerImpl.EmergencyListener {
+
+    public BaseStatusBarHeader(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public abstract int getCollapsedHeight();
+    public abstract int getExpandedHeight();
+    public abstract void setExpanded(boolean b);
+    public abstract void setExpansion(float headerExpansionFraction);
+    public abstract void setListening(boolean listening);
+    public abstract void updateEverything();
+    public abstract void setActivityStarter(ActivityStarter activityStarter);
+    public abstract void setQSPanel(QSPanel qSPanel);
+    public abstract void setBatteryController(BatteryController batteryController);
+    public abstract void setNextAlarmController(NextAlarmController nextAlarmController);
+    public abstract void setUserInfoController(UserInfoController userInfoController);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 364e884..4ac2c31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 import android.animation.LayoutTransition;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -742,9 +744,9 @@
 
     private void activateTask(int taskPersistentId) {
         // Launch or bring the activity to front.
-        IActivityManager manager = ActivityManagerNative.getDefault();
+        final IActivityManager iAm = ActivityManagerNative.getDefault();
         try {
-            manager.startActivityFromRecents(taskPersistentId, null /* options */);
+            iAm.startActivityFromRecents(taskPersistentId, INVALID_STACK_ID, null /* options */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Exception when activating a recent task", e);
         } catch (IllegalArgumentException e) {
@@ -810,7 +812,7 @@
                     removeCallbacks(mShowMenuCallback);
                     break;
             }
-            return true;
+            return false;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 011889a..33e514d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -187,15 +187,6 @@
         mBarTransitions = new NavigationBarTransitions(this);
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ViewRootImpl root = getViewRootImpl();
-        if (root != null) {
-            root.setDrawDuringWindowsAnimating(true);
-        }
-    }
-
     public BarTransitions getBarTransitions() {
         return mBarTransitions;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 08353cb..79701ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -35,6 +35,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
@@ -43,7 +44,6 @@
 import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 import android.widget.TextView;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.DejankUtils;
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.tuner.TunerService;
 
 import java.util.List;
 
@@ -71,7 +72,7 @@
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
-        HeadsUpManager.OnHeadsUpChangedListener {
+        HeadsUpManager.OnHeadsUpChangedListener, TunerService.Tunable {
 
     private static final boolean DEBUG = false;
 
@@ -92,7 +93,7 @@
     public static final long DOZE_ANIMATION_DURATION = 700;
 
     private KeyguardAffordanceHelper mAfforanceHelper;
-    private StatusBarHeaderView mHeader;
+    private BaseStatusBarHeader mHeader;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private QSContainer mQsContainer;
@@ -219,10 +220,13 @@
     private final Interpolator mTouchResponseInterpolator =
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
 
+    private boolean mNewQs;
+
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
         mFalsingManager = FalsingManager.getInstance(context);
+        TunerService.get(context).addTunable(this, QSPanel.QS_THE_NEW_QS);
     }
 
     public void setStatusBar(PhoneStatusBar bar) {
@@ -230,9 +234,27 @@
     }
 
     @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (QSPanel.QS_THE_NEW_QS.equals(key)) {
+            boolean b = newValue != null && Integer.parseInt(newValue) != 0;
+            if (mNewQs != b) {
+                if (mHeader != null) {
+                    // We are too late, no good way to re-initialize yet, just die and come back up.
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                } else {
+                    mNewQs = b;
+                }
+            }
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mHeader = (StatusBarHeaderView) findViewById(R.id.header);
+        ViewStub stub = (ViewStub) findViewById(R.id.status_bar_header);
+        stub.setLayoutResource(mNewQs
+                ? R.layout.quick_status_bar_expanded_header : R.layout.status_bar_expanded_header);
+        mHeader = (BaseStatusBarHeader) stub.inflate();
         mHeader.setOnClickListener(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
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 bd16257..ebe7785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.view.ViewTreeObserver;
@@ -228,8 +229,7 @@
         }
 
         // On expanding, single mouse click expands the panel instead of dragging.
-        if (isFullyCollapsed() && event.getPointerCount() == 1
-                && event.getToolType(0) == MotionEvent.TOOL_TYPE_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/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 37edc28..cfde791 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -25,6 +25,7 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.app.RemoteInput;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
@@ -82,6 +83,7 @@
 import android.view.ThreadedRenderer;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewStub;
 import android.view.WindowManager;
@@ -128,6 +130,7 @@
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.SpeedBumpView;
@@ -150,6 +153,7 @@
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
@@ -299,6 +303,8 @@
 
     StatusBarIconController mIconController;
 
+    private RemoteInputController mRemoteInputController;
+
     // expanded notifications
     NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     View mExpandedContents;
@@ -308,7 +314,7 @@
     private QSPanel mQSPanel;
 
     // top bar
-    StatusBarHeaderView mHeader;
+    BaseStatusBarHeader mHeader;
     KeyguardStatusBarView mKeyguardStatusBar;
     View mKeyguardStatusView;
     KeyguardBottomAreaView mKeyguardBottomArea;
@@ -800,7 +806,7 @@
         mStatusBarView.setScrimController(mScrimController);
         mDozeScrimController = new DozeScrimController(mScrimController, context);
 
-        mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
+        mHeader = (BaseStatusBarHeader) mStatusBarWindow.findViewById(R.id.header);
         mHeader.setActivityStarter(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
         mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
@@ -862,13 +868,15 @@
                 (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
         mNetworkController.addSignalCallback(signalCluster);
         mNetworkController.addSignalCallback(signalClusterKeyguard);
-        mNetworkController.addSignalCallback(signalClusterQs);
         signalCluster.setSecurityController(mSecurityController);
         signalCluster.setNetworkController(mNetworkController);
         signalClusterKeyguard.setSecurityController(mSecurityController);
         signalClusterKeyguard.setNetworkController(mNetworkController);
-        signalClusterQs.setSecurityController(mSecurityController);
-        signalClusterQs.setNetworkController(mNetworkController);
+        if (signalClusterQs != null) {
+            mNetworkController.addSignalCallback(signalClusterQs);
+            signalClusterQs.setSecurityController(mSecurityController);
+            signalClusterQs.setNetworkController(mNetworkController);
+        }
         final boolean isAPhone = mNetworkController.hasVoiceCallingFeature();
         if (isAPhone) {
             mNetworkController.addEmergencyListener(mHeader);
@@ -1084,6 +1092,13 @@
         return mStatusBarWindow;
     }
 
+    @Override
+    protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
+            Notification.Action action, RemoteInput remoteInput) {
+        return RemoteInputView.inflate(mContext, root, entry, action, remoteInput,
+                mRemoteInputController);
+    }
+
     public int getStatusBarHeight() {
         if (mNaturalBarHeight < 0) {
             final Resources res = mContext.getResources();
@@ -2840,6 +2855,8 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = new StatusBarWindowManager(mContext);
+        mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
+                mHeadsUpManager);
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -3244,6 +3261,15 @@
         return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
     }
 
+    public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                startPendingIntentDismissingKeyguard(intent);
+            }
+        });
+    }
+
     public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
         mHandler.postDelayed(new Runnable() {
             @Override
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 6ec9494..96b919e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -97,6 +98,10 @@
         TunerService.get(mContext).addTunable(this, TILES_SETTING);
     }
 
+    PhoneStatusBar getPhoneStatusBar() {
+        return mStatusBar;
+    }
+
     public void destroy() {
         TunerService.get(mContext).removeTunable(this);
     }
@@ -117,6 +122,11 @@
     }
 
     @Override
+    public void startActivityDismissingKeyguard(PendingIntent intent) {
+        mStatusBar.postStartActivityDismissingKeyguard(intent);
+    }
+
+    @Override
     public void warn(String message, Throwable t) {
         // already logged
     }
@@ -236,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);
@@ -262,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
new file mode 100644
index 0000000..662dbd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -0,0 +1,360 @@
+/*
+ * 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.statusbar.phone;
+
+import android.app.AlarmManager;
+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;
+import android.widget.ImageView;
+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;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.tuner.TunerService;
+
+public class QuickStatusBarHeader extends BaseStatusBarHeader implements
+        NextAlarmController.NextAlarmChangeCallback, View.OnClickListener {
+
+    private ActivityStarter mActivityStarter;
+    private NextAlarmController mNextAlarmController;
+    private SettingsButton mSettingsButton;
+    private View mSettingsContainer;
+    private TextView mAlarmStatus;
+    private View mQsDetailHeader;
+
+    private ImageView mQsDetailHeaderProgress;
+    private QSPanel mQsPanel;
+    private Switch mQsDetailHeaderSwitch;
+
+    private boolean mExpanded;
+    private boolean mAlarmShowing;
+    private boolean mShowingDetail;
+
+    private boolean mDetailTransitioning;
+    private ViewGroup mExpandedGroup;
+    private TextView mQsDetailHeaderTitle;
+    private boolean mListening;
+    private AlarmManager.AlarmClockInfo mNextAlarm;
+
+    private QSPanel mHeaderQsPanel;
+
+    public QuickStatusBarHeader(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
+
+        mHeaderQsPanel = (QSPanel) findViewById(R.id.quick_qs_panel);
+
+        mSettingsButton = (SettingsButton) findViewById(R.id.settings_button);
+        mSettingsContainer = findViewById(R.id.settings_button_container);
+        mSettingsButton.setOnClickListener(this);
+
+        mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
+        mAlarmStatus.setOnClickListener(this);
+
+        mQsDetailHeader = findViewById(R.id.qs_detail_header);
+        mQsDetailHeader.setAlpha(0);
+        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
+    public int getCollapsedHeight() {
+        return getHeight();
+    }
+
+    @Override
+    public int getExpandedHeight() {
+        return getHeight();
+    }
+
+    @Override
+    public void setExpanded(boolean expanded) {
+        mExpanded = expanded;
+    }
+
+    @Override
+    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+        mNextAlarm = nextAlarm;
+        if (nextAlarm != null) {
+            // TODO:...
+//            mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
+        }
+        mAlarmShowing = nextAlarm != null;
+        updateEverything();
+    }
+
+    @Override
+    public void setExpansion(float headerExpansionFraction) {
+        float offset = getHeight() * headerExpansionFraction;
+        mExpandedGroup.setTranslationY(offset - getHeight());
+        mHeaderQsPanel.setTranslationY(offset);
+    }
+
+    public void setListening(boolean listening) {
+        if (listening == mListening) {
+            return;
+        }
+        mHeaderQsPanel.setListening(listening);
+        mListening = listening;
+        updateListeners();
+    }
+
+    @Override
+    public void updateEverything() {
+        updateVisibilities();
+    }
+
+    private void updateVisibilities() {
+        mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.GONE);
+        mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE);
+        mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
+                TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    private void updateListeners() {
+        if (mListening) {
+            mNextAlarmController.addStateChangedCallback(this);
+        } else {
+            mNextAlarmController.removeStateChangedCallback(this);
+        }
+    }
+
+    @Override
+    public void setActivityStarter(ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
+    @Override
+    public void setQSPanel(final QSPanel qsPanel) {
+        mQsPanel = qsPanel;
+        setupHost(qsPanel.getHost());
+        if (mQsPanel != null) {
+            mQsPanel.setCallback(mQsPanelCallback);
+        }
+    }
+
+    public void setupHost(QSTileHost host) {
+        final QSTileHost myHost = new QSTileHost(host.getContext(), host.getPhoneStatusBar(),
+                host.getBluetoothController(), host.getLocationController(),
+                host.getRotationLockController(), host.getNetworkController(),
+                host.getZenModeController(), host.getHotspotController(),
+                host.getCastController(), host.getFlashlightController(),
+                host.getUserSwitcherController(), host.getUserInfoController(),
+                host.getKeyguardMonitor(), host.getSecurityController(),
+                host.getBatteryController());
+        mHeaderQsPanel.setHost(myHost);
+        mHeaderQsPanel.setTiles(myHost.getTiles());
+        myHost.setCallback(new QSTile.Host.Callback() {
+            @Override
+            public void onTilesChanged() {
+                mHeaderQsPanel.setTiles(myHost.getTiles());
+            }
+        });
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSettingsButton) {
+            if (mSettingsButton.isTunerClick()) {
+                if (TunerService.isTunerEnabled(mContext)) {
+                    TunerService.showResetRequest(mContext, new Runnable() {
+                        @Override
+                        public void run() {
+                            // Relaunch settings so that the tuner disappears.
+                            startSettingsActivity();
+                        }
+                    });
+                } else {
+                    Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show();
+                    TunerService.setTunerEnabled(mContext, true);
+                }
+            }
+            startSettingsActivity();
+        } else if (v == mAlarmStatus && mNextAlarm != null) {
+            PendingIntent showIntent = mNextAlarm.getShowIntent();
+            if (showIntent != null && showIntent.isActivity()) {
+                mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
+            }
+        }
+    }
+
+    private void startSettingsActivity() {
+        mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
+                true /* dismissShade */);
+    }
+
+    @Override
+    public void setNextAlarmController(NextAlarmController nextAlarmController) {
+        mNextAlarmController = nextAlarmController;
+    }
+
+    @Override
+    public void setBatteryController(BatteryController batteryController) {
+        // Don't care
+    }
+
+    @Override
+    public void setUserInfoController(UserInfoController userInfoController) {
+        // Don't care.
+    }
+
+    @Override
+    public void setEmergencyCallsOnly(boolean emergencyOnly) {
+        // Don't care.
+    }
+
+    private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
+        private boolean mScanState;
+
+        @Override
+        public void onShowingDetail(final QSTile.DetailAdapter detail) {
+            mDetailTransitioning = true;
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleShowingDetail(detail);
+                }
+            });
+        }
+
+        @Override
+        public void onToggleStateChanged(final boolean state) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleToggleStateChanged(state);
+                }
+            });
+
+        }
+
+        @Override
+        public void onScanStateChanged(final boolean state) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    handleScanStateChanged(state);
+                }
+            });
+        }
+
+        private void handleToggleStateChanged(boolean state) {
+            mQsDetailHeaderSwitch.setChecked(state);
+        }
+
+        private void handleScanStateChanged(boolean state) {
+            if (mScanState == state) return;
+            mScanState = state;
+            final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
+            if (state) {
+                mQsDetailHeaderProgress.animate().alpha(1f);
+                anim.start();
+            } else {
+                mQsDetailHeaderProgress.animate().alpha(0f);
+                anim.stop();
+            }
+        }
+
+        private void handleShowingDetail(final QSTile.DetailAdapter detail) {
+            final boolean showingDetail = detail != null;
+            transition(mExpandedGroup, !showingDetail);
+            if (mAlarmShowing) {
+                transition(mAlarmStatus, !showingDetail);
+            }
+            transition(mQsDetailHeader, showingDetail);
+            mShowingDetail = showingDetail;
+            if (showingDetail) {
+                mQsDetailHeaderTitle.setText(detail.getTitle());
+                final Boolean toggleState = detail.getToggleState();
+                if (toggleState == null) {
+                    mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
+                    mQsDetailHeader.setClickable(false);
+                } else {
+                    mQsDetailHeaderSwitch.setVisibility(VISIBLE);
+                    mQsDetailHeaderSwitch.setChecked(toggleState);
+                    mQsDetailHeader.setClickable(true);
+                    mQsDetailHeader.setOnClickListener(new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            boolean checked = !mQsDetailHeaderSwitch.isChecked();
+                            mQsDetailHeaderSwitch.setChecked(checked);
+                            detail.setToggleState(checked);
+                        }
+                    });
+                }
+            } else {
+                mQsDetailHeader.setClickable(false);
+            }
+        }
+
+        private void transition(final View v, final boolean in) {
+            if (in) {
+                v.bringToFront();
+                v.setVisibility(VISIBLE);
+            }
+            if (v.hasOverlappingRendering()) {
+                v.animate().withLayer();
+            }
+            v.animate()
+                    .alpha(in ? 1 : 0)
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!in) {
+                                v.setVisibility(INVISIBLE);
+                            }
+                            mDetailTransitioning = false;
+                        }
+                    })
+                    .start();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index e9c4e49..6cda561 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -57,9 +57,9 @@
 /**
  * The view to manage the header area in the expanded status bar.
  */
-public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener,
+public class StatusBarHeaderView extends BaseStatusBarHeader implements View.OnClickListener,
         BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback,
-        EmergencyListener {
+        EmergencyListener, TunerService.Tunable {
 
     private boolean mExpanded;
     private boolean mListening;
@@ -128,6 +128,8 @@
     private boolean mShowingDetail;
     private boolean mDetailTransitioning;
 
+    private boolean mAllowExpand = true;
+
     public StatusBarHeaderView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -232,6 +234,28 @@
         updateClockCollapsedMargin();
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        TunerService.get(mContext).addTunable(this, QSPanel.QS_THE_NEW_QS);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        TunerService.get(mContext).removeTunable(this);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (QSPanel.QS_THE_NEW_QS.equals(key)) {
+            mAllowExpand = newValue == null || Integer.parseInt(newValue) == 0;
+            if (!mAllowExpand) {
+                setExpanded(false);
+            }
+        }
+    }
+
     private void updateClockCollapsedMargin() {
         Resources res = getResources();
         int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin);
@@ -290,7 +314,7 @@
     }
 
     public int getExpandedHeight() {
-        return mExpandedHeight;
+        return mAllowExpand ? mExpandedHeight : mCollapsedHeight;
     }
 
     public void setListening(boolean listening) {
@@ -302,6 +326,9 @@
     }
 
     public void setExpanded(boolean expanded) {
+        if (!mAllowExpand) {
+            expanded = false;
+        }
         boolean changed = expanded != mExpanded;
         mExpanded = expanded;
         if (changed) {
@@ -527,8 +554,8 @@
             startBatteryActivity();
         } else if (v == mAlarmStatus && mNextAlarm != null) {
             PendingIntent showIntent = mNextAlarm.getShowIntent();
-            if (showIntent != null && showIntent.isActivity()) {
-                mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */);
+            if (showIntent != null) {
+                mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index dd62d9b..abe51ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -123,7 +123,7 @@
     private void applyFocusableFlag(State state) {
         boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
         if (state.keyguardShowing && state.keyguardNeedsInput && state.bouncerShowing
-                || BaseStatusBar.ENABLE_REMOTE_INPUT && panelFocusable) {
+                || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -292,6 +292,11 @@
         apply(mCurrentState);
     }
 
+    public void setRemoteInputActive(boolean remoteInputActive) {
+        mCurrentState.remoteInputActive = remoteInputActive;
+        apply(mCurrentState);
+    }
+
     /**
      * Set whether the screen brightness is forced to the value we use for doze mode by the status
      * bar window.
@@ -326,6 +331,8 @@
          */
         int statusBarState;
 
+        boolean remoteInputActive;
+
         private boolean isKeyguardShowingAndNotOccluded() {
             return keyguardShowing && !keyguardOccluded;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index cfd3358..35a17e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -144,13 +144,6 @@
     protected void onAttachedToWindow () {
         super.onAttachedToWindow();
 
-        // We really need to be able to animate while window animations are going on
-        // so that activities may be started asynchronously from panel animations
-        final ViewRootImpl root = getViewRootImpl();
-        if (root != null) {
-            root.setDrawDuringWindowsAnimating(true);
-        }
-
         // We need to ensure that our window doesn't suffer from overdraw which would normally
         // occur if our window is translucent. Since we are drawing the whole window anyway with
         // the scrim, we don't need the window to be cleared in the beginning.
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 ed9b123..a515f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -102,6 +102,7 @@
     private boolean mHeadsUpGoingAway;
     private boolean mWaitingOnCollapseWhenGoingAway;
     private boolean mIsObserving;
+    private boolean mRemoteInputActive;
 
     public HeadsUpManager(final Context context, View statusBarWindowView) {
         mContext = context;
@@ -169,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();
@@ -246,6 +246,9 @@
             return;
         }
         mHasPinnedNotification = hasPinnedNotification;
+        if (mHasPinnedNotification) {
+            MetricsLogger.count(mContext, "note_peek", 1);
+        }
         updateTouchableRegionListener();
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
@@ -536,6 +539,18 @@
         return clicked != null && clicked;
     }
 
+    public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+        if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+            headsUpEntry.remoteInputActive = remoteInputActive;
+            if (remoteInputActive) {
+                headsUpEntry.removeAutoRemovalCallbacks();
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */);
+            }
+        }
+    }
+
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
@@ -545,6 +560,7 @@
         public long postTime;
         public long earliestRemovaltime;
         private Runnable mRemoveHeadsUpRunnable;
+        public boolean remoteInputActive;
 
         public void setEntry(final NotificationData.Entry entry) {
             this.entry = entry;
@@ -565,12 +581,18 @@
         }
 
         public void updateEntry() {
+            updateEntry(true);
+        }
+
+        public void updateEntry(boolean updatePostTime) {
             mSortedEntries.remove(HeadsUpEntry.this);
             long currentTime = mClock.currentTimeMillis();
             earliestRemovaltime = currentTime + mMinimumDisplayTime;
-            postTime = Math.max(postTime, currentTime);
+            if (updatePostTime) {
+                postTime = Math.max(postTime, currentTime);
+            }
             removeAutoRemovalCallbacks();
-            if (!hasFullScreenIntent(entry)) {
+            if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
                 mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
@@ -580,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/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7d721c2..2ad9287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.policy;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.RemoteInputController;
 
 import android.annotation.NonNull;
 import android.app.Notification;
@@ -33,12 +35,15 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+
 /**
  * Host for the remote input.
  */
@@ -51,6 +56,8 @@
     private PendingIntent mPendingIntent;
     private RemoteInput mRemoteInput;
     private Notification.Action mAction;
+    private RemoteInputController mController;
+    private NotificationData.Entry mEntry;
 
     public RemoteInputView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -85,12 +92,13 @@
         });
         mEditText.setOnClickListener(this);
         mEditText.setInnerFocusable(false);
+        mEditText.mDefocusListener = this;
     }
 
     private void sendRemoteInput() {
         Bundle results = new Bundle();
         results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
-        Intent fillInIntent = new Intent();
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
                 results);
 
@@ -105,7 +113,8 @@
     }
 
     public static RemoteInputView inflate(Context context, ViewGroup root,
-            Notification.Action action, RemoteInput remoteInput) {
+            NotificationData.Entry entry, Notification.Action action, RemoteInput remoteInput,
+            RemoteInputController controller) {
         RemoteInputView v = (RemoteInputView)
                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
 
@@ -113,6 +122,8 @@
         v.mPendingIntent = action.actionIntent;
         v.mRemoteInput = remoteInput;
         v.mAction = action;
+        v.mController = controller;
+        v.mEntry = entry;
 
         return v;
     }
@@ -122,15 +133,22 @@
         if (v == mEditText) {
             if (!mEditText.isFocusable()) {
                 mEditText.setInnerFocusable(true);
-                InputMethodManager imm = InputMethodManager.getInstance();
-                if (imm != null) {
-                    imm.viewClicked(mEditText);
-                    imm.showSoftInput(mEditText, 0);
-                }
+                mController.addRemoteInput(mEntry);
+                mEditText.mShowImeOnInputConnection = true;
             }
         }
     }
 
+    public void onDefocus() {
+        mController.removeRemoteInput(mEntry);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mController.removeRemoteInput(mEntry);
+    }
+
     /**
      * An EditText that changes appearance based on whether it's focusable and becomes
      * un-focusable whenever the user navigates away from it or it becomes invisible.
@@ -138,6 +156,8 @@
     public static class RemoteEditText extends EditText {
 
         private final Drawable mBackground;
+        private RemoteInputView mDefocusListener;
+        boolean mShowImeOnInputConnection;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
             super(context, attrs);
@@ -147,6 +167,10 @@
         private void defocusIfNeeded() {
             if (isFocusable() && isEnabled()) {
                 setInnerFocusable(false);
+                if (mDefocusListener != null) {
+                    mDefocusListener.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
             }
         }
 
@@ -173,6 +197,28 @@
             return super.onKeyPreIme(keyCode, event);
         }
 
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = InputMethodManager.getInstance();
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
 
         void setInnerFocusable(boolean focusable) {
             setFocusableInTouchMode(focusable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 40984d4..f06e5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -22,6 +22,7 @@
     String getDeviceOwnerName();
     String getProfileOwnerName();
     boolean isVpnEnabled();
+    boolean isVpnRestricted();
     String getPrimaryVpnName();
     String getProfileVpnName();
     void onUserSwitched(int newUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index e0823b4..88f028f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -162,6 +162,13 @@
     }
 
     @Override
+    public boolean isVpnRestricted() {
+        UserHandle currentUser = new UserHandle(mCurrentUserId);
+        return mUserManager.getUserInfo(mCurrentUserId).isRestricted()
+                || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, currentUser);
+    }
+
+    @Override
     public void removeCallback(SecurityControllerCallback callback) {
         synchronized (mCallbacks) {
             if (callback == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index b2df40a..0e91b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -27,7 +27,6 @@
     void removeCallback(Callback callback);
     void setZen(int zen, Uri conditionId, String reason);
     int getZen();
-    void requestConditions(boolean request);
     ZenRule getManualRule();
     ZenModeConfig getConfig();
     long getNextAlarm();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index c07f1a8..96efea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -121,15 +121,6 @@
     }
 
     @Override
-    public void requestConditions(boolean request) {
-        mRequesting = request;
-        mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0);
-        if (!mRequesting) {
-            mConditions.clear();
-        }
-    }
-
-    @Override
     public ZenRule getManualRule() {
         return mConfig == null ? null : mConfig.manualRule;
     }
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 703ee661..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);
         }
 
@@ -350,6 +350,11 @@
             }
 
             @Override
+            public boolean isVpnRestricted() {
+                return false;
+            }
+
+            @Override
             public String getPrimaryVpnName() {
                 return null;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 9a78b6c..911d6a2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -101,7 +101,7 @@
                         }
                     }).show();
         }
-        TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_PAGED_PANEL);
+        TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_THE_NEW_QS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f156607..a03e7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -141,6 +141,7 @@
     public void onConfigurationChanged() {
         mEndNowButton.setText(mContext.getString(R.string.volume_zen_end_now));
         mSpTexts.update();
+        Util.setText(mEndNowButton, mContext.getString(R.string.volume_zen_end_now));
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 3c9a7fc..1770a06 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -1,4 +1,4 @@
-/*
+/**
  * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -58,6 +58,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -76,6 +78,8 @@
     private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
     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);
@@ -86,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;
@@ -104,9 +107,6 @@
     private Callback mCallback;
     private ZenModeController mController;
     private boolean mCountdownConditionSupported;
-    private int mMaxConditions;
-    private int mMaxOptionalConditions;
-    private int mFirstConditionIndex;
     private boolean mRequestingConditions;
     private Condition mExitCondition;
     private int mBucketIndex = -1;
@@ -118,6 +118,7 @@
     private Condition mSessionExitCondition;
     private Condition[] mConditions;
     private Condition mTimeCondition;
+    private Condition mTimeUntilAlarmCondition;
     private boolean mVoiceCapable;
 
     public ZenModePanel(Context context, AttributeSet attrs) {
@@ -125,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);
@@ -135,7 +135,6 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("ZenModePanel state:");
         pw.print("  mCountdownConditionSupported="); pw.println(mCountdownConditionSupported);
-        pw.print("  mMaxConditions="); pw.println(mMaxConditions);
         pw.print("  mRequestingConditions="); pw.println(mRequestingConditions);
         pw.print("  mAttached="); pw.println(mAttached);
         pw.print("  mHidden="); pw.println(mHidden);
@@ -286,14 +285,6 @@
         if (mRequestingConditions == requesting) return;
         if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
         mRequestingConditions = requesting;
-        if (mController != null) {
-            AsyncTask.execute(new Runnable() {
-                @Override
-                public void run() {
-                    mController.requestConditions(requesting);
-                }
-            });
-        }
         if (mRequestingConditions) {
             mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
             if (mTimeCondition != null) {
@@ -304,6 +295,7 @@
                         MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
             }
             if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
+
             mConditions = null; // reset conditions
             handleUpdateConditions();
         } else {
@@ -314,13 +306,9 @@
     public void init(ZenModeController controller) {
         mController = controller;
         mCountdownConditionSupported = mController.isCountdownConditionSupported();
-        final int countdownDelta = mCountdownConditionSupported ? 1 : 0;
-        mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta;
+        final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
         final int minConditions = 1 /*forever*/ + countdownDelta;
-        mMaxConditions = MathUtils.constrain(mContext.getResources()
-                .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100);
-        mMaxOptionalConditions = mMaxConditions - minConditions;
-        for (int i = 0; i < mMaxConditions; i++) {
+        for (int i = 0; i < minConditions; i++) {
             mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
         }
         mSessionZen = getSelectedZen(-1);
@@ -357,28 +345,10 @@
         return condition == null ? null : condition.copy();
     }
 
-    public static String getExitConditionText(Context context, Condition exitCondition) {
-        if (exitCondition == null) {
-            return foreverSummary(context);
-        } else if (isCountdown(exitCondition)) {
-            final Condition condition = parseExistingTimeCondition(context, exitCondition);
-            return condition != null ? condition.summary : foreverSummary(context);
-        } else {
-            return exitCondition.summary;
-        }
-    }
-
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
 
-    public void showSilentHint() {
-        if (DEBUG) Log.d(mTag, "showSilentHint");
-        if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
-        final View noneButton = mZenButtons.getChildAt(0);
-        mIconPulser.start(noneButton);
-    }
-
     private void handleUpdateManualRule(ZenRule rule) {
         final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
         handleUpdateZen(zen);
@@ -410,7 +380,7 @@
             final ConditionTag tag = getConditionTagAt(i);
             if (tag != null) {
                 if (sameConditionId(tag.condition, mExitCondition)) {
-                    bind(exitCondition, mZenConditions.getChildAt(i));
+                    bind(exitCondition, mZenConditions.getChildAt(i), i);
                 }
             }
         }
@@ -495,64 +465,32 @@
         final long span = time - now;
         if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
         return ZenModeConfig.toTimeCondition(context,
-                time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser(),
+                time, Math.round(span / (float) MINUTES_MS), ActivityManager.getCurrentUser(),
                 false /*shortVersion*/);
     }
 
-    private void handleUpdateConditions(Condition[] conditions) {
-        conditions = trimConditions(conditions);
-        if (Arrays.equals(conditions, mConditions)) {
-            final int count = mConditions == null ? 0 : mConditions.length;
-            if (DEBUG) Log.d(mTag, "handleUpdateConditions unchanged conditionCount=" + count);
-            return;
-        }
-        mConditions = conditions;
-        handleUpdateConditions();
-    }
-
-    private Condition[] trimConditions(Condition[] conditions) {
-        if (conditions == null || conditions.length <= mMaxOptionalConditions) {
-            // no need to trim
-            return conditions;
-        }
-        // look for current exit condition, ensure it is included if found
-        int found = -1;
-        for (int i = 0; i < conditions.length; i++) {
-            final Condition c = conditions[i];
-            if (mSessionExitCondition != null && sameConditionId(mSessionExitCondition, c)) {
-                found = i;
-                break;
-            }
-        }
-        final Condition[] rt = Arrays.copyOf(conditions, mMaxOptionalConditions);
-        if (found >= mMaxOptionalConditions) {
-            // found after the first N, promote to the end of the first N
-            rt[mMaxOptionalConditions - 1] = conditions[found];
-        }
-        return rt;
-    }
-
     private void handleUpdateConditions() {
         if (mTransitionHelper.isTransitioning()) {
-            mTransitionHelper.pendingUpdateConditions();
             return;
         }
         final int conditionCount = mConditions == null ? 0 : mConditions.length;
         if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount);
         // forever
-        bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
+        bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX),
+                FOREVER_CONDITION_INDEX);
         // countdown
         if (mCountdownConditionSupported && mTimeCondition != null) {
-            bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+            bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX),
+                    COUNTDOWN_CONDITION_INDEX);
         }
-        // provider conditions
-        for (int i = 0; i < conditionCount; i++) {
-            bind(mConditions[i], mZenConditions.getChildAt(mFirstConditionIndex + i));
-        }
-        // hide the rest
-        for (int i = mZenConditions.getChildCount() - 1; i > mFirstConditionIndex + conditionCount;
-                i--) {
-            mZenConditions.getChildAt(i).setVisibility(GONE);
+        // countdown until alarm
+        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()) {
@@ -569,6 +507,33 @@
         return context.getString(com.android.internal.R.string.zen_mode_forever);
     }
 
+    // Returns a time condition if the next alarm is within the next week.
+    private Condition getTimeUntilNextAlarmCondition() {
+        GregorianCalendar weekRange = new GregorianCalendar();
+        final long now = weekRange.getTimeInMillis();
+        setToMidnight(weekRange);
+        weekRange.roll(Calendar.DATE, 6);
+        final long nextAlarmMs = mController.getNextAlarm();
+        if (nextAlarmMs > 0) {
+            GregorianCalendar nextAlarm = new GregorianCalendar();
+            nextAlarm.setTimeInMillis(nextAlarmMs);
+            setToMidnight(nextAlarm);
+
+            if (weekRange.compareTo(nextAlarm) >= 0) {
+                return ZenModeConfig.toNextAlarmCondition(mContext, now, nextAlarmMs,
+                        ActivityManager.getCurrentUser());
+            }
+        }
+        return null;
+    }
+
+    private void setToMidnight(Calendar calendar) {
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+    }
+
     private ConditionTag getConditionTagAt(int index) {
         return (ConditionTag) mZenConditions.getChildAt(index).getTag();
     }
@@ -610,7 +575,8 @@
             mTimeCondition = ZenModeConfig.toTimeCondition(mContext,
                     MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser());
             mBucketIndex = favoriteIndex;
-            bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX));
+            bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX),
+                    COUNTDOWN_CONDITION_INDEX);
             getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
         }
     }
@@ -623,7 +589,7 @@
         return c != null && mForeverId.equals(c.id);
     }
 
-    private void bind(final Condition condition, final View row) {
+    private void bind(final Condition condition, final View row, final int rowId) {
         if (condition == null) throw new IllegalArgumentException("condition must not be null");
         final boolean enabled = condition.state == Condition.STATE_TRUE;
         final ConditionTag tag =
@@ -640,8 +606,7 @@
         tag.rb.setEnabled(enabled);
         final boolean checked = (mSessionExitCondition != null
                     || mAttachedZen != Global.ZEN_MODE_OFF)
-                && (sameConditionId(mSessionExitCondition, tag.condition)
-                    || isCountdown(mSessionExitCondition) && isCountdown(tag.condition));
+                && (sameConditionId(mSessionExitCondition, tag.condition));
         if (checked != tag.rb.isChecked()) {
             if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId);
             tag.rb.setChecked(checked);
@@ -692,7 +657,7 @@
         button1.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                onClickTimeButton(row, tag, false /*down*/);
+                onClickTimeButton(row, tag, false /*down*/, rowId);
             }
         });
 
@@ -700,7 +665,7 @@
         button2.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                onClickTimeButton(row, tag, true /*up*/);
+                onClickTimeButton(row, tag, true /*up*/, rowId);
             }
         });
         tag.lines.setOnClickListener(new OnClickListener() {
@@ -711,7 +676,7 @@
         });
 
         final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
-        if (time > 0) {
+        if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) {
             button1.setVisibility(VISIBLE);
             button2.setVisibility(VISIBLE);
             if (mBucketIndex > -1) {
@@ -761,7 +726,7 @@
                 tag.line1.getText()));
     }
 
-    private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
+    private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
         MetricsLogger.action(mContext, MetricsLogger.QS_DND_TIME, up);
         Condition newCondition = null;
         final int N = MINUTE_BUCKETS.length;
@@ -777,7 +742,7 @@
                 if (up && bucketTime > time || !up && bucketTime < time) {
                     mBucketIndex = j;
                     newCondition = ZenModeConfig.toTimeCondition(mContext,
-                            bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser(),
+                            bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
                             false /*shortVersion*/);
                     break;
                 }
@@ -794,7 +759,7 @@
                     MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
         }
         mTimeCondition = newCondition;
-        bind(mTimeCondition, row);
+        bind(mTimeCondition, row, rowId);
         tag.rb.setChecked(true);
         select(mTimeCondition);
         announceConditionSelection(tag);
@@ -838,18 +803,12 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         @Override
-        public void onConditionsChanged(Condition[] conditions) {
-            mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
-        }
-
-        @Override
         public void onManualRuleChanged(ZenRule rule) {
             mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
         }
     };
 
     private final class H extends Handler {
-        private static final int UPDATE_CONDITIONS = 1;
         private static final int MANUAL_RULE_CHANGED = 2;
         private static final int UPDATE_WIDGETS = 3;
 
@@ -860,7 +819,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case UPDATE_CONDITIONS: handleUpdateConditions((Condition[]) msg.obj); break;
                 case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break;
                 case UPDATE_WIDGETS: updateWidgets(); break;
             }
@@ -1005,26 +963,20 @@
         private final ArraySet<View> mTransitioningViews = new ArraySet<View>();
 
         private boolean mTransitioning;
-        private boolean mPendingUpdateConditions;
         private boolean mPendingUpdateWidgets;
 
         public void clear() {
             mTransitioningViews.clear();
-            mPendingUpdateConditions = mPendingUpdateWidgets = false;
+            mPendingUpdateWidgets = false;
         }
 
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("  TransitionHelper state:");
-            pw.print("    mPendingUpdateConditions="); pw.println(mPendingUpdateConditions);
             pw.print("    mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets);
             pw.print("    mTransitioning="); pw.println(mTransitioning);
             pw.print("    mTransitioningViews="); pw.println(mTransitioningViews);
         }
 
-        public void pendingUpdateConditions() {
-            mPendingUpdateConditions = true;
-        }
-
         public void pendingUpdateWidgets() {
             mPendingUpdateWidgets = true;
         }
@@ -1050,15 +1002,11 @@
         @Override
         public void run() {
             if (DEBUG) Log.d(mTag, "TransitionHelper run"
-                    + " mPendingUpdateWidgets=" + mPendingUpdateWidgets
-                    + " mPendingUpdateConditions=" + mPendingUpdateConditions);
+                    + " mPendingUpdateWidgets=" + mPendingUpdateWidgets);
             if (mPendingUpdateWidgets) {
                 updateWidgets();
             }
-            if (mPendingUpdateConditions) {
-                handleUpdateConditions();
-            }
-            mPendingUpdateWidgets = mPendingUpdateConditions = false;
+            mPendingUpdateWidgets = false;
         }
 
         private void updateTransitioning() {
@@ -1067,10 +1015,10 @@
             mTransitioning = transitioning;
             if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning);
             if (!mTransitioning) {
-                if (mPendingUpdateConditions || mPendingUpdateWidgets) {
+                if (mPendingUpdateWidgets) {
                     mHandler.post(this);
                 } else {
-                    mPendingUpdateConditions = mPendingUpdateWidgets = false;
+                    mPendingUpdateWidgets = false;
                 }
             }
         }
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/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index 48e0582..f0ca441 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -18,8 +18,11 @@
 
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.net.IConnectivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.Html;
@@ -40,43 +43,47 @@
 
     private IConnectivityManager mService;
 
-    private Button mButton;
-
     @Override
-    protected void onResume() {
-        super.onResume();
-        try {
-            mPackage = getCallingPackage();
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPackage = getCallingPackage();
+        mService = IConnectivityManager.Stub.asInterface(
+                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
 
-            mService = IConnectivityManager.Stub.asInterface(
-                    ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
-
-            if (mService.prepareVpn(mPackage, null, UserHandle.myUserId())) {
-                setResult(RESULT_OK);
-                finish();
-                return;
-            }
-
-            View view = View.inflate(this, R.layout.confirm, null);
-
-            ((TextView) view.findViewById(R.id.warning)).setText(
-                    Html.fromHtml(
-                            getString(R.string.warning, VpnConfig.getVpnLabel(this, mPackage)),
-                    this, null /* tagHandler */));
-
-            mAlertParams.mTitle = getText(R.string.prompt);
-            mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
-            mAlertParams.mPositiveButtonListener = this;
-            mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
-            mAlertParams.mView = view;
-            setupAlert();
-
-            getWindow().setCloseOnTouchOutside(false);
-            mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
-            mButton.setFilterTouchesWhenObscured(true);
-        } catch (Exception e) {
-            Log.e(TAG, "onResume", e);
+        if (prepareVpn()) {
+            setResult(RESULT_OK);
             finish();
+            return;
+        }
+        View view = View.inflate(this, R.layout.confirm, null);
+        ((TextView) view.findViewById(R.id.warning)).setText(
+                Html.fromHtml(getString(R.string.warning, getVpnLabel()),
+                        this, null /* tagHandler */));
+        mAlertParams.mTitle = getText(R.string.prompt);
+        mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
+        mAlertParams.mPositiveButtonListener = this;
+        mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
+        mAlertParams.mView = view;
+        setupAlert();
+
+        getWindow().setCloseOnTouchOutside(false);
+        Button button = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        button.setFilterTouchesWhenObscured(true);
+    }
+
+    private boolean prepareVpn() {
+        try {
+            return mService.prepareVpn(mPackage, null, UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private CharSequence getVpnLabel() {
+        try {
+            return VpnConfig.getVpnLabel(this, mPackage);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException(e);
         }
     }
 
diff --git a/rs/java/android/renderscript/AllocationAdapter.java b/rs/java/android/renderscript/AllocationAdapter.java
index 9bfd6ec..6d7e97e 100644
--- a/rs/java/android/renderscript/AllocationAdapter.java
+++ b/rs/java/android/renderscript/AllocationAdapter.java
@@ -244,23 +244,23 @@
     /**
      *
      *
-     * Create an arbitrary window into the base allocation
+     * Create an arbitrary window into the base allocation.
      * The type describes the shape of the window.
      *
      * Any dimensions present in the type must be equal or smaller
      * to the dimensions in the source allocation.  A dimension
      * present in the allocation that is not present in the type
-     * will be constrained away with the selectors
+     * will be constrained away with the selectors.
      *
-     * If a dimension is present in the type and allcation one of
-     * two things will happen
+     * If a dimension is present in both the type and allocation, one of
+     * two things will happen.
      *
-     * If the type is smaller than the allocation a window will be
+     * If the type is smaller than the allocation, a window will be
      * created, the selected value in the adapter for that dimension
-     * will act as the base address and the type will describe the
+     * will act as the base address, and the type will describe the
      * size of the view starting at that point.
      *
-     * If the type and allocation dimension are of the same size
+     * If the type and allocation dimension are of the same size,
      * then setting the selector for the dimension will be an error.
      */
     static public AllocationAdapter createTyped(RenderScript rs, Allocation a, Type t) {
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 5a27301..7eb8005 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -1585,15 +1585,20 @@
             mMessageThread.mRun = false;
 
             // Wait for mMessageThread to join.  Try in a loop, in case this thread gets interrupted
-            // during the wait.
-            boolean hasJoined = false;
+            // during the wait.  If interrupted, set the "interrupted" status of the current thread.
+            boolean hasJoined = false, interrupted = false;
             while (!hasJoined) {
                 try {
                     mMessageThread.join();
                     hasJoined = true;
-                } catch(InterruptedException e) {
+                } catch (InterruptedException e) {
+                    interrupted = true;
                 }
             }
+            if (interrupted) {
+                Log.v(LOG_TAG, "Interrupted during wait for MessageThread to join");
+                Thread.currentThread().interrupt();
+            }
 
             nContextDestroy();
 
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
new file mode 100644
index 0000000..28121b4
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -0,0 +1,232 @@
+/*
+ ** 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.
+ */
+
+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 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 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:
+                if (maybeFinishDoubleTap(event, policyFlags)) {
+                    return true;
+                }
+                if (mRecognizingGesture) {
+                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+                    if (recognizeGesture()) {
+                        return true;
+                    }
+                }
+                break;
+        }
+
+        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;
+    }
+
+    public void cancelGesture() {
+        mRecognizingGesture = false;
+        mStrokeBuffer.clear();
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        maybeSendLongPress(e, mPolicyFlags);
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent event) {
+        mFirstTapDetected = true;
+        return false;
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent event) {
+        clear();
+        return false;
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent event) {
+        // The processing of the double tap is deferred until the finger is
+        // lifted, so that we can detect a long press on the second tap.
+        mDoubleTapDetected = true;
+        return true;
+    }
+
+    private void maybeSendLongPress(MotionEvent event, int policyFlags) {
+        if (!mDoubleTapDetected) {
+            return;
+        }
+
+        clear();
+
+        mListener.onDoubleTapAndHold(event, policyFlags);
+    }
+
+    private boolean maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
+        if (!mDoubleTapDetected) {
+            return false;
+        }
+
+        clear();
+
+        return mListener.onDoubleTap(event, 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 f6b32f7..ca30349 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -17,19 +17,9 @@
 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.graphics.Rect;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.util.Slog;
-import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -41,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;
@@ -67,7 +55,7 @@
  *
  * @hide
  */
-class TouchExplorer implements EventStreamTransformation {
+class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener {
 
     private static final boolean DEBUG = false;
 
@@ -139,8 +127,8 @@
     // Command for exiting gesture detection mode after a timeout.
     private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
 
-    // Helper to detect and react to double tap in touch explore mode.
-    private final DoubleTapDetector mDoubleTapDetector;
+    // Helper to detect gestures.
+    private final AccessibilityGestureDetector mGestureDetector;
 
     // The scaled minimal distance before we take the middle of the distance between
     // the two dragging pointers as opposed to use the location of the primary one.
@@ -170,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;
 
@@ -218,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(
@@ -230,7 +196,7 @@
         mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                 mDetermineUserIntentTimeout);
-        mDoubleTapDetector = new DoubleTapDetector(mContext);
+        mGestureDetector = new AccessibilityGestureDetector(context, this);
         final float density = context.getResources().getDisplayMetrics().density;
         mScaledMinPointerDistanceToUseMiddleLocation =
             (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
@@ -277,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.
@@ -290,8 +255,8 @@
         // Reset the pointer trackers.
         mReceivedPointerTracker.clear();
         mInjectedPointerTracker.clear();
-        // Clear the double tap detector
-        mDoubleTapDetector.clear();
+        // Clear the gesture detector
+        mGestureDetector.clear();
         // Go to initial state.
         // Clear the long pressing pointer remap data.
         mLongPressingPointerId = -1;
@@ -324,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);
@@ -390,6 +360,106 @@
         }
     }
 
+    @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;
+        }
+
+        final int pointerIndex = event.getActionIndex();
+        final int pointerId = event.getPointerId(pointerIndex);
+
+        Point clickLocation = mTempPoint;
+        final int result = computeClickLocation(clickLocation);
+
+        if (result == CLICK_LOCATION_NONE) {
+            return;
+        }
+
+        mLongPressingPointerId = pointerId;
+        mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x;
+        mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y;
+
+        sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+
+        mCurrentState = STATE_DELEGATING;
+        sendDownForAllNotInjectedPointers(event, policyFlags);
+    }
+
+    @Override
+    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.
+        mSendHoverEnterAndMoveDelayed.cancel();
+        mSendHoverExitDelayed.cancel();
+
+        if (mSendTouchExplorationEndDelayed.isPending()) {
+            mSendTouchExplorationEndDelayed.forceSendAndRemove();
+        }
+        if (mSendTouchInteractionEndDelayed.isPending()) {
+            mSendTouchInteractionEndDelayed.forceSendAndRemove();
+        }
+
+        final int pointerIndex = event.getActionIndex();
+        final int pointerId = event.getPointerId(pointerIndex);
+
+        Point clickLocation = mTempPoint;
+        final int result = computeClickLocation(clickLocation);
+        if (result == CLICK_LOCATION_NONE) {
+            // We can't send a click to no location, but the gesture was still
+            // consumed.
+            return true;
+        }
+
+        // Do the click.
+        PointerProperties[] properties = new PointerProperties[1];
+        properties[0] = new PointerProperties();
+        event.getPointerProperties(pointerIndex, properties[0]);
+        PointerCoords[] coords = new PointerCoords[1];
+        coords[0] = new PointerCoords();
+        coords[0].x = clickLocation.x;
+        coords[0].y = clickLocation.y;
+        MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
+                event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
+                coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
+                event.getSource(), event.getFlags());
+        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;
+    }
+
     /**
      * Handles a motion event in touch exploring state.
      *
@@ -403,17 +473,10 @@
 
         mVelocityTracker.addMovement(rawEvent);
 
-        mDoubleTapDetector.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
@@ -430,7 +493,7 @@
                     mSendTouchInteractionEndDelayed.forceSendAndRemove();
                 }
 
-                if (!mDoubleTapDetector.firstTapDetected() && !mTouchExplorationInProgress) {
+                if (!mGestureDetector.firstTapDetected() && !mTouchExplorationInProgress) {
                     if (!mSendHoverEnterAndMoveDelayed.isPending()) {
                         // Deliver hover enter with a delay to have a chance
                         // to detect what the user is trying to do.
@@ -460,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);
 
@@ -500,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,
@@ -545,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
@@ -587,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);
 
@@ -752,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.
@@ -777,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;
@@ -1055,153 +1067,6 @@
         }
     }
 
-    private class DoubleTapDetector extends GestureDetector.SimpleOnGestureListener {
-        private final GestureDetector mGestureDetector;
-        private boolean mFirstTapDetected;
-        private boolean mDoubleTapDetected;
-        private int mPolicyFlags;
-
-        DoubleTapDetector(Context context) {
-            mGestureDetector = new GestureDetector(context, this);
-            mGestureDetector.setOnDoubleTapListener(this);
-        }
-
-        public void onMotionEvent(MotionEvent event, int policyFlags) {
-            mPolicyFlags = policyFlags;
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mDoubleTapDetected = false;
-                    break;
-
-                case MotionEvent.ACTION_UP:
-                    maybeFinishDoubleTap(event, policyFlags);
-                    break;
-            }
-            mGestureDetector.onTouchEvent(event);
-        }
-
-        @Override
-        public boolean onDown(MotionEvent event) {
-            return true;
-        }
-
-        @Override
-        public void onLongPress(MotionEvent e) {
-            maybeSendLongPress(e, mPolicyFlags);
-        }
-
-        @Override
-        public boolean onSingleTapUp(MotionEvent event) {
-            mFirstTapDetected = true;
-            return false;
-        }
-
-        @Override
-        public boolean onSingleTapConfirmed(MotionEvent event) {
-            clear();
-            return false;
-        }
-
-        @Override
-        public boolean onDoubleTap(MotionEvent event) {
-            // The processing of the double tap is deferred until the finger is
-            // lifted, so that we can detect a long press on the second tap.
-            mDoubleTapDetected = true;
-            return true;
-        }
-
-        private void maybeSendLongPress(MotionEvent event, int policyFlags) {
-            if (!mDoubleTapDetected) {
-                return;
-            }
-
-            clear();
-
-            // Pointers should not be zero when running this command.
-            if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) {
-                return;
-            }
-
-            final int pointerIndex = event.getActionIndex();
-            final int pointerId = event.getPointerId(pointerIndex);
-
-            Point clickLocation = mTempPoint;
-            final int result = computeClickLocation(clickLocation);
-
-            if (result == CLICK_LOCATION_NONE) {
-                return;
-            }
-
-            mLongPressingPointerId = pointerId;
-            mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x;
-            mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y;
-
-            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-
-            mCurrentState = STATE_DELEGATING;
-            sendDownForAllNotInjectedPointers(event, policyFlags);
-        }
-
-        private void maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
-            if (!mDoubleTapDetected) {
-                return;
-            }
-
-            clear();
-
-            // This should never be called when more than two pointers are down.
-            if (event.getPointerCount() > 2) {
-                return;
-            }
-
-            // Remove pending event deliveries.
-            mSendHoverEnterAndMoveDelayed.cancel();
-            mSendHoverExitDelayed.cancel();
-
-            if (mSendTouchExplorationEndDelayed.isPending()) {
-                mSendTouchExplorationEndDelayed.forceSendAndRemove();
-            }
-            if (mSendTouchInteractionEndDelayed.isPending()) {
-                mSendTouchInteractionEndDelayed.forceSendAndRemove();
-            }
-
-            final int pointerIndex = event.getActionIndex();
-            final int pointerId = event.getPointerId(pointerIndex);
-
-            Point clickLocation = mTempPoint;
-            final int result = computeClickLocation(clickLocation);
-            if (result == CLICK_LOCATION_NONE) {
-                return;
-            }
-
-            // Do the click.
-            PointerProperties[] properties = new PointerProperties[1];
-            properties[0] = new PointerProperties();
-            event.getPointerProperties(pointerIndex, properties[0]);
-            PointerCoords[] coords = new PointerCoords[1];
-            coords[0] = new PointerCoords();
-            coords[0].x = clickLocation.x;
-            coords[0].y = clickLocation.y;
-            MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
-                    event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
-                    coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
-                    event.getSource(), event.getFlags());
-            final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
-            sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
-            click_event.recycle();
-            return;
-        }
-
-        public void clear() {
-            mFirstTapDetected = false;
-            mDoubleTapDetected = false;
-        }
-
-        public boolean firstTapDetected() {
-            return mFirstTapDetected;
-        }
-    }
-
     /**
      * Determines whether a two pointer gesture is a dragging one.
      *
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 a7e6f11..c712a56 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -948,13 +948,13 @@
             uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
         }
 
-        if ((uidRules & RULE_REJECT_ALL) != 0
-                || (networkCostly && (uidRules & RULE_REJECT_METERED) != 0)) {
+        if (uidRules == RULE_REJECT_ALL) {
             return true;
+        } else if ((uidRules == RULE_REJECT_METERED) && networkCostly) {
+            return true;
+        } else {
+            return false;
         }
-
-        // no restrictive rules; network is visible
-        return false;
     }
 
     /**
@@ -1366,7 +1366,8 @@
                 bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
             }
         }
-        if (DBG) log("Adding " + bestRoute + " for interface " + bestRoute.getInterface());
+        if (DBG) log("Adding legacy route " + bestRoute +
+                " for UID/PID " + uid + "/" + Binder.getCallingPid());
         try {
             mNetd.addLegacyRouteForNetId(netId, bestRoute, uid);
         } catch (Exception e) {
@@ -1449,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() {
@@ -3214,6 +3212,11 @@
             final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
             final VpnProfile profile = VpnProfile.decode(
                     profileName, mKeyStore.get(Credentials.VPN + profileName));
+            if (profile == null) {
+                Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
+                setLockdownTracker(null);
+                return true;
+            }
             int user = UserHandle.getUserId(Binder.getCallingUid());
             synchronized(mVpns) {
                 setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
@@ -3360,7 +3363,7 @@
                     .setPriority(highPriority ?
                             Notification.PRIORITY_HIGH :
                             Notification.PRIORITY_DEFAULT)
-                    .setDefaults(Notification.DEFAULT_ALL)
+                    .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
                     .setOnlyAlertOnce(true)
                     .build();
 
@@ -3750,7 +3753,7 @@
             synchronized(mRulesLock) {
                 uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
             }
-            if ((uidRules & (RULE_REJECT_METERED | RULE_REJECT_ALL)) != 0) {
+            if (uidRules != RULE_ALLOW_ALL) {
                 // we could silently fail or we can filter the available nets to only give
                 // them those they have access to.  Chose the more useful
                 networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index ebcdf7c..927b995 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -55,7 +55,9 @@
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -107,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;
@@ -116,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;
@@ -136,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;
@@ -161,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;
 
@@ -244,6 +272,18 @@
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                 int plugged = intent.getIntExtra("plugged", 0);
                 updateChargingLocked(plugged != 0);
+            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                    Uri data = intent.getData();
+                    String ssp;
+                    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();
@@ -295,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 {
@@ -370,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";
@@ -391,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
@@ -445,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
          */
@@ -545,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,
@@ -582,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();
@@ -679,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) {
@@ -694,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: {
@@ -743,6 +842,8 @@
 
     final MyHandler mHandler;
 
+    BinderService mBinderService;
+
     private final class BinderService extends IDeviceIdleController.Stub {
         @Override public void addPowerSaveWhitelistApp(String name) {
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
@@ -794,37 +895,20 @@
 
         @Override public void addPowerSaveTempWhitelistApp(String packageName, long duration,
                 int userId, String reason) throws RemoteException {
-            getContext().enforceCallingPermission(
-                    Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
-                    "No permission to change device idle whitelist");
-            final int callingUid = Binder.getCallingUid();
-            userId = ActivityManagerNative.getDefault().handleIncomingUser(
-                    Binder.getCallingPid(),
-                    callingUid,
-                    userId,
-                    /*allowAll=*/ false,
-                    /*requireFull=*/ false,
-                    "addPowerSaveTempWhitelistApp", null);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                DeviceIdleController.this.addPowerSaveTempWhitelistAppInternal(callingUid,
-                        packageName, duration, userId, true, reason);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
+            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
         }
 
         @Override public long addPowerSaveTempWhitelistAppForMms(String packageName,
                 int userId, String reason) throws RemoteException {
             long duration = mConstants.MMS_TEMP_APP_WHITELIST_DURATION;
-            addPowerSaveTempWhitelistApp(packageName, duration, userId, reason);
+            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
             return duration;
         }
 
         @Override public long addPowerSaveTempWhitelistAppForSms(String packageName,
                 int userId, String reason) throws RemoteException {
             long duration = mConstants.SMS_TEMP_APP_WHITELIST_DURATION;
-            addPowerSaveTempWhitelistApp(packageName, duration, userId, reason);
+            addPowerSaveTempWhitelistAppChecked(packageName, duration, userId, reason);
             return duration;
         }
 
@@ -837,6 +921,11 @@
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
+
+        @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+            (new Shell()).exec(this, in, out, err, args, resultReceiver);
+        }
     }
 
     public final class LocalService {
@@ -909,10 +998,12 @@
             // a battery update the next time the level drops.
             mCharging = true;
             mState = STATE_ACTIVE;
+            mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
         }
 
-        publishBinderService(Context.DEVICE_IDLE_CONTROLLER, new BinderService());
+        mBinderService = new BinderService();
+        publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
         publishLocalService(LocalService.class, new LocalService());
     }
 
@@ -966,18 +1057,26 @@
                         .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);
+                filter.addDataScheme("package");
                 getContext().registerReceiver(mReceiver, filter);
 
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
@@ -991,7 +1090,10 @@
     public boolean addPowerSaveWhitelistAppInternal(String name) {
         synchronized (this) {
             try {
-                ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name, 0);
+                ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+                        PackageManager.GET_UNINSTALLED_PACKAGES
+                                | PackageManager.GET_DISABLED_COMPONENTS
+                                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
                 if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
                     reportPowerSaveWhitelistChangedLocked();
                     updateWhitelistAppIdsLocked();
@@ -1104,11 +1206,33 @@
         }
     }
 
+    void addPowerSaveTempWhitelistAppChecked(String packageName, long duration,
+            int userId, String reason) throws RemoteException {
+        getContext().enforceCallingPermission(
+                Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                "No permission to change device idle whitelist");
+        final int callingUid = Binder.getCallingUid();
+        userId = ActivityManagerNative.getDefault().handleIncomingUser(
+                Binder.getCallingPid(),
+                callingUid,
+                userId,
+                /*allowAll=*/ false,
+                /*requireFull=*/ false,
+                "addPowerSaveTempWhitelistApp", null);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            addPowerSaveTempWhitelistAppInternal(callingUid,
+                    packageName, duration, userId, true, reason);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Adds an app to the temporary whitelist and resets the endTime for granting the
      * app an exemption to access network and acquire wakelocks.
      */
-    public void addPowerSaveTempWhitelistAppInternal(int callingUid, String packageName,
+    void addPowerSaveTempWhitelistAppInternal(int callingUid, String packageName,
             long duration, int userId, boolean sync, String reason) {
         try {
             int uid = getContext().getPackageManager().getPackageUid(packageName, userId);
@@ -1122,7 +1246,7 @@
      * Adds an app to the temporary whitelist and resets the endTime for granting the
      * app an exemption to access network and acquire wakelocks.
      */
-    public void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int appId,
+    void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int appId,
             long duration, boolean sync, String reason) {
         final long timeNow = SystemClock.elapsedRealtime();
         Runnable networkPolicyTempWhitelistCallback = null;
@@ -1253,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");
+            }
         }
     }
 
@@ -1286,12 +1421,15 @@
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
         cancelAlarmLocked();
-        cancelSensingAlarmLocked();
         cancelLocatingLocked();
         stopMonitoringMotionLocked();
         mAnyMotionDetector.stop();
     }
 
+    void resetLightIdleManagementLocked() {
+        cancelLightAlarmLocked();
+    }
+
     void exitForceIdleLocked() {
         if (mForceIdle) {
             mForceIdle = false;
@@ -1301,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();
@@ -1331,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();
+                scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
                 mNotMoving = false;
@@ -1344,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,
@@ -1371,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:
@@ -1382,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;
@@ -1409,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();
         }
     }
@@ -1471,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);
         }
     }
 
@@ -1506,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,
@@ -1595,7 +1781,6 @@
             } catch (IOException e) {
             }
         }
-
     }
 
     private void readConfigFileLocked(XmlPullParser parser) {
@@ -1624,7 +1809,10 @@
                     String name = parser.getAttributeValue(null, "n");
                     if (name != null) {
                         try {
-                            ApplicationInfo ai = pm.getApplicationInfo(name, 0);
+                            ApplicationInfo ai = pm.getApplicationInfo(name,
+                                    PackageManager.GET_UNINSTALLED_PACKAGES
+                                            | PackageManager.GET_DISABLED_COMPONENTS
+                                            | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
                             mPowerSaveWhitelistUserApps.put(ai.packageName,
                                     UserHandle.getAppId(ai.uid));
                         } catch (PackageManager.NameNotFoundException e) {
@@ -1698,11 +1886,10 @@
         out.endDocument();
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("Device idle controller (deviceidle) dump options:");
-        pw.println("  [-h] [CMD]");
-        pw.println("  -h: print this help text.");
-        pw.println("Commands:");
+    static void dumpHelp(PrintWriter pw) {
+        pw.println("Device idle controller (deviceidle) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
         pw.println("  step");
         pw.println("    Immediately step to next state, without waiting for alarm.");
         pw.println("  force-idle");
@@ -1718,10 +1905,198 @@
         pw.println("    Print currently whitelisted apps.");
         pw.println("  whitelist [package ...]");
         pw.println("    Add (prefix with +) or remove (prefix with -) packages.");
-        pw.println("  tempwhitelist [package ..]");
+        pw.println("  tempwhitelist [-u] [package ..]");
         pw.println("    Temporarily place packages in whitelist for 10 seconds.");
     }
 
+    class Shell extends ShellCommand {
+        int userId = UserHandle.USER_SYSTEM;
+
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            dumpHelp(pw);
+        }
+    }
+
+    int onShellCommand(Shell shell, String cmd) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        if ("step".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    exitForceIdleLocked();
+                    stepIdleStateLocked();
+                    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);
+                }
+            }
+        } else if ("force-idle".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    if (!mEnabled) {
+                        pw.println("Unable to go idle; not enabled");
+                        return -1;
+                    }
+                    mForceIdle = true;
+                    becomeInactiveIfAppropriateLocked();
+                    int curState = mState;
+                    while (curState != STATE_IDLE) {
+                        stepIdleStateLocked();
+                        if (curState == mState) {
+                            pw.print("Unable to go idle; stopped at ");
+                            pw.println(stateToString(mState));
+                            exitForceIdleLocked();
+                            return -1;
+                        }
+                        curState = mState;
+                    }
+                    pw.println("Now forced in to idle mode");
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("disable".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    if (mEnabled) {
+                        mEnabled = false;
+                        becomeActiveLocked("disabled", Process.myUid());
+                        pw.println("Idle mode disabled");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("enable".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    exitForceIdleLocked();
+                    if (!mEnabled) {
+                        mEnabled = true;
+                        becomeInactiveIfAppropriateLocked();
+                        pw.println("Idle mode enabled");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("enabled".equals(cmd)) {
+            synchronized (this) {
+                pw.println(mEnabled ? "1" : " 0");
+            }
+        } else if ("whitelist".equals(cmd)) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                String arg = shell.getNextArg();
+                if (arg != null) {
+                    getContext().enforceCallingOrSelfPermission(
+                            android.Manifest.permission.DEVICE_POWER, null);
+                    do {
+                        if (arg.length() < 1 || (arg.charAt(0) != '-'
+                                && arg.charAt(0) != '+')) {
+                            pw.println("Package must be prefixed with + or -: " + arg);
+                            return -1;
+                        }
+                        char op = arg.charAt(0);
+                        String pkg = arg.substring(1);
+                        if (op == '+') {
+                            if (addPowerSaveWhitelistAppInternal(pkg)) {
+                                pw.println("Added: " + pkg);
+                            } else {
+                                pw.println("Unknown package: " + pkg);
+                            }
+                        } else {
+                            if (removePowerSaveWhitelistAppInternal(pkg)) {
+                                pw.println("Removed: " + pkg);
+                            }
+                        }
+                    } while ((arg=shell.getNextArg()) != null);
+                } else {
+                    synchronized (this) {
+                        for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
+                            pw.print("system-excidle,");
+                            pw.print(mPowerSaveWhitelistAppsExceptIdle.keyAt(j));
+                            pw.print(",");
+                            pw.println(mPowerSaveWhitelistAppsExceptIdle.valueAt(j));
+                        }
+                        for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+                            pw.print("system,");
+                            pw.print(mPowerSaveWhitelistApps.keyAt(j));
+                            pw.print(",");
+                            pw.println(mPowerSaveWhitelistApps.valueAt(j));
+                        }
+                        for (int j=0; j<mPowerSaveWhitelistUserApps.size(); j++) {
+                            pw.print("user,");
+                            pw.print(mPowerSaveWhitelistUserApps.keyAt(j));
+                            pw.print(",");
+                            pw.println(mPowerSaveWhitelistUserApps.valueAt(j));
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } else if ("tempwhitelist".equals(cmd)) {
+            String opt;
+            while ((opt=shell.getNextOption()) != null) {
+                if ("-u".equals(opt)) {
+                    opt = shell.getNextArg();
+                    if (opt == null) {
+                        pw.println("-u requires a user number");
+                        return -1;
+                    }
+                    shell.userId = Integer.parseInt(opt);
+                }
+            }
+            String arg = shell.getNextArg();
+            if (arg != null) {
+                try {
+                    addPowerSaveTempWhitelistAppChecked(arg, 10000L, shell.userId, "shell");
+                } catch (RemoteException re) {
+                    pw.println("Failed: " + re);
+                }
+            } else {
+                pw.println("At least one package name must be specified");
+                return -1;
+            }
+        } else {
+            return shell.handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -1746,156 +2121,15 @@
                     }
                 } else if ("-a".equals(arg)) {
                     // Ignore, we always dump all.
-                } else if ("step".equals(arg)) {
-                    synchronized (this) {
-                        long token = Binder.clearCallingIdentity();
-                        try {
-                            exitForceIdleLocked();
-                            stepIdleStateLocked();
-                            pw.print("Stepped to: "); pw.println(stateToString(mState));
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
-                        }
-                    }
-                    return;
-                } else if ("force-idle".equals(arg)) {
-                    synchronized (this) {
-                        long token = Binder.clearCallingIdentity();
-                        try {
-                            if (!mEnabled) {
-                                pw.println("Unable to go idle; not enabled");
-                                return;
-                            }
-                            mForceIdle = true;
-                            becomeInactiveIfAppropriateLocked();
-                            int curState = mState;
-                            while (curState != STATE_IDLE) {
-                                stepIdleStateLocked();
-                                if (curState == mState) {
-                                    pw.print("Unable to go idle; stopped at ");
-                                    pw.println(stateToString(mState));
-                                    exitForceIdleLocked();
-                                    return;
-                                }
-                                curState = mState;
-                            }
-                            pw.println("Now forced in to idle mode");
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
-                        }
-                    }
-                    return;
-                } else if ("disable".equals(arg)) {
-                    synchronized (this) {
-                        long token = Binder.clearCallingIdentity();
-                        try {
-                            if (mEnabled) {
-                                mEnabled = false;
-                                becomeActiveLocked("disabled", Process.myUid());
-                                pw.println("Idle mode disabled");
-                            }
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
-                        }
-                    }
-                    return;
-                } else if ("enable".equals(arg)) {
-                    synchronized (this) {
-                        long token = Binder.clearCallingIdentity();
-                        try {
-                            exitForceIdleLocked();
-                            if (!mEnabled) {
-                                mEnabled = true;
-                                becomeInactiveIfAppropriateLocked();
-                                pw.println("Idle mode enabled");
-                            }
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
-                        }
-                    }
-                    return;
-                } else if ("enabled".equals(arg)) {
-                    synchronized (this) {
-                        pw.println(mEnabled ? "1" : " 0");
-                    }
-                    return;
-                } else if ("whitelist".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        i++;
-                        if (i < args.length) {
-                            while (i < args.length) {
-                                arg = args[i];
-                                i++;
-                                if (arg.length() < 1 || (arg.charAt(0) != '-'
-                                        && arg.charAt(0) != '+')) {
-                                    pw.println("Package must be prefixed with + or -: " + arg);
-                                    return;
-                                }
-                                char op = arg.charAt(0);
-                                String pkg = arg.substring(1);
-                                if (op == '+') {
-                                    if (addPowerSaveWhitelistAppInternal(pkg)) {
-                                        pw.println("Added: " + pkg);
-                                    } else {
-                                        pw.println("Unknown package: " + pkg);
-                                    }
-                                } else {
-                                    if (removePowerSaveWhitelistAppInternal(pkg)) {
-                                        pw.println("Removed: " + pkg);
-                                    }
-                                }
-                            }
-                        } else {
-                            synchronized (this) {
-                                for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
-                                    pw.print("system-excidle,");
-                                    pw.print(mPowerSaveWhitelistAppsExceptIdle.keyAt(j));
-                                    pw.print(",");
-                                    pw.println(mPowerSaveWhitelistAppsExceptIdle.valueAt(j));
-                                }
-                                for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
-                                    pw.print("system,");
-                                    pw.print(mPowerSaveWhitelistApps.keyAt(j));
-                                    pw.print(",");
-                                    pw.println(mPowerSaveWhitelistApps.valueAt(j));
-                                }
-                                for (int j=0; j<mPowerSaveWhitelistUserApps.size(); j++) {
-                                    pw.print("user,");
-                                    pw.print(mPowerSaveWhitelistUserApps.keyAt(j));
-                                    pw.print(",");
-                                    pw.println(mPowerSaveWhitelistUserApps.valueAt(j));
-                                }
-                            }
-                        }
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
-                } else if ("tempwhitelist".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        i++;
-                        if (i >= args.length) {
-                            pw.println("At least one package name must be specified");
-                            return;
-                        }
-                        while (i < args.length) {
-                            arg = args[i];
-                            i++;
-                            addPowerSaveTempWhitelistAppInternal(0, arg, 10000L, userId, true,
-                                    "shell");
-                            pw.println("Added: " + arg);
-                        }
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
                 } else {
-                    pw.println("Unknown command: " + arg);
+                    Shell shell = new Shell();
+                    shell.userId = userId;
+                    String[] newArgs = new String[args.length-i];
+                    System.arraycopy(args, i, newArgs, 0, args.length-i);
+                    shell.exec(mBinderService, null, fd, null, newArgs, new ResultReceiver(null));
                     return;
                 }
             }
@@ -1977,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);
@@ -1988,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) {
@@ -2006,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/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 087ddd6..9405d8e0 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -341,8 +341,10 @@
             gpsProvider.disable();
         }
 
-        FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
-        if (FlpHardwareProvider.isSupported() && flpHardwareProvider != null) {
+        // it is needed to check if FLP HW provider is supported before accessing the instance, this
+        // avoids an exception to be thrown by the singleton factory method
+        if (FlpHardwareProvider.isSupported()) {
+            FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
             flpHardwareProvider.cleanup();
         }
     }
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 617264c..9834757 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -24,6 +24,7 @@
 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.app.ActivityManager.PINNED_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -36,6 +37,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.*;
 import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
@@ -78,7 +80,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;
@@ -204,7 +205,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;
@@ -369,17 +369,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;
@@ -418,6 +411,9 @@
     // Lower delay than APP_BOOST_MESSAGE_DELAY to disable the boost
     static final int APP_BOOST_TIMEOUT = 2500;
 
+    // Used to indicate that a task is removed it should also be removed from recents.
+    private static final boolean REMOVE_FROM_RECENTS = true;
+
     private static native int nativeMigrateToBoost();
     private static native int nativeMigrateFromBoost();
     private boolean mIsBoosted = false;
@@ -501,6 +497,8 @@
      */
     String mDeviceOwnerName;
 
+    final UserController mUserController;
+
     public class PendingAssistExtras extends Binder implements Runnable {
         public final ActivityRecord activity;
         public final Bundle extras;
@@ -703,32 +701,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.
      */
@@ -1225,7 +1197,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;
@@ -1297,19 +1268,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 {
@@ -1439,7 +1397,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) {
@@ -1826,15 +1784,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: {
@@ -1863,7 +1821,7 @@
             }
             case START_PROFILES_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    startProfilesLocked();
+                    mUserController.startProfilesLocked();
                 }
                 break;
             }
@@ -2054,14 +2012,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;
@@ -2383,10 +2341,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);
@@ -2735,7 +2690,7 @@
         synchronized (ActivityManagerService.this) {
             ActivityStack stack = mStackSupervisor.getStack(stackId);
             if (stack != null) {
-                ActivityRecord r = stack.topRunningActivityLocked(null);
+                ActivityRecord r = stack.topRunningActivityLocked();
                 if (r != null) {
                     setFocusedActivityLocked(r, "setFocusedStack");
                     mStackSupervisor.resumeTopActivitiesLocked(stack, null, null);
@@ -2752,7 +2707,7 @@
             synchronized (ActivityManagerService.this) {
                 TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
                 if (task != null) {
-                    ActivityRecord r = task.topRunningActivityLocked(null);
+                    ActivityRecord r = task.topRunningActivityLocked();
                     if (r != null) {
                         setFocusedActivityLocked(r, "setFocusedTask");
                         mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null);
@@ -4188,27 +4143,39 @@
     }
 
     @Override
-    public final int startActivityFromRecents(int taskId, Bundle options) {
+    public final int startActivityFromRecents(int taskId, int launchStackId, Bundle options) {
         if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: startActivityFromRecents called without " +
                     START_TASKS_FROM_RECENTS;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        return startActivityFromRecentsInner(taskId, options);
+        return startActivityFromRecentsInner(taskId, launchStackId, options);
     }
 
-    final int startActivityFromRecentsInner(int taskId, Bundle options) {
+    final int startActivityFromRecentsInner(int taskId, int launchStackId, Bundle options) {
         final TaskRecord task;
         final int callingUid;
         final String callingPackage;
         final Intent intent;
         final int userId;
         synchronized (this) {
-            task = mStackSupervisor.anyTaskForIdLocked(taskId);
-            if (task == null) {
-                throw new IllegalArgumentException("Task " + taskId + " not found.");
+            if (launchStackId == HOME_STACK_ID) {
+                throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+                        + taskId + " can't be launch in the home stack.");
             }
+
+            task = mStackSupervisor.anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId);
+            if (task == null) {
+                throw new IllegalArgumentException(
+                        "startActivityFromRecentsInner: Task " + taskId + " not found.");
+            }
+
+            if (launchStackId != INVALID_STACK_ID && task.stack.mStackId != launchStackId) {
+                mStackSupervisor.moveTaskToStackUncheckedLocked(
+                        task, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+            }
+
             if (task.getRootActivity() != null) {
                 moveTaskToFrontLocked(task.taskId, 0, options);
                 return ActivityManager.START_TASK_TO_FRONT;
@@ -4369,12 +4336,16 @@
             final long origId = Binder.clearCallingIdentity();
             try {
                 boolean res;
+                final boolean finishWithRootActivity =
+                        finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                 if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
-                        || (finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY && r == rootR)) {
+                        || (finishWithRootActivity && r == rootR)) {
                     // If requested, remove the task that is associated to this activity only if it
                     // was the root activity in the task. The result code and data is ignored
-                    // because we don't support returning them across task boundaries.
-                    res = removeTaskByIdLocked(tr.taskId, false);
+                    // because we don't support returning them across task boundaries. Also, to
+                    // keep backwards compatibility we remove the task from recents when finishing
+                    // task with root activity.
+                    res = removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
                     if (!res) {
                         Slog.i(TAG, "Removing task failed to finish activity");
                     }
@@ -5193,7 +5164,7 @@
                             tr.getBaseIntent().getComponent().getPackageName();
                     if (tr.userId != userId) continue;
                     if (!taskPackageName.equals(packageName)) continue;
-                    removeTaskByIdLocked(tr.taskId, false);
+                    removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
                 }
             }
 
@@ -5547,16 +5518,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,
@@ -5720,7 +5681,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;
@@ -5775,7 +5736,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(
@@ -6091,7 +6052,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);
@@ -6141,11 +6102,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;
@@ -6179,9 +6135,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);
@@ -6446,32 +6402,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();
             }
         }
@@ -8550,7 +8492,8 @@
         synchronized (this) {
             enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
                     "getTaskThumbnail()");
-            TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id, false);
+            final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
+                    id, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
             if (tr != null) {
                 return tr.getTaskThumbnailLocked();
             }
@@ -8663,7 +8606,8 @@
     @Override
     public void setTaskResizeable(int taskId, boolean resizeable) {
         synchronized (this) {
-            TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, false);
+            final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
+                    taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
             if (task == null) {
                 Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
                 return;
@@ -8695,9 +8639,10 @@
                 // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
                 //   that task to freeform
                 // - otherwise the task is not moved
-                // Note it's not allowed to resize a home stack task, or a docked task.
+                // Note it's not allowed to resize a home, docked, or pinned stack task.
                 int stackId = task.stack.mStackId;
-                if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID) {
+                if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID
+                        || stackId == PINNED_STACK_ID) {
                     throw new IllegalArgumentException("trying to resizeTask on a "
                             + "home or docked task");
                 }
@@ -8764,9 +8709,12 @@
         mWindowManager.executeAppTransition();
     }
 
-    private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) {
-        mRecentTasks.remove(tr);
-        tr.removedFromRecents();
+    private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess,
+            boolean removeFromRecents) {
+        if (removeFromRecents) {
+            mRecentTasks.remove(tr);
+            tr.removedFromRecents();
+        }
         ComponentName component = tr.getBaseIntent().getComponent();
         if (component == null) {
             Slog.w(TAG, "No component for base intent of task: " + tr);
@@ -8843,7 +8791,7 @@
             ComponentName cn = tr.intent.getComponent();
             if (cn != null && cn.getPackageName().equals(packageName)) {
                 // If the package name matches, remove the task.
-                removeTaskByIdLocked(tr.taskId, true);
+                removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
             }
         }
     }
@@ -8861,7 +8809,7 @@
             final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
                     && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
             if (sameComponent) {
-                removeTaskByIdLocked(tr.taskId, false);
+                removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
             }
         }
     }
@@ -8871,13 +8819,16 @@
      *
      * @param taskId Identifier of the task to be removed.
      * @param killProcess Kill any process associated with the task if possible.
+     * @param removeFromRecents Whether to also remove the task from recents.
      * @return Returns true if the given task was found and removed.
      */
-    private boolean removeTaskByIdLocked(int taskId, boolean killProcess) {
-        TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false);
+    private boolean removeTaskByIdLocked(int taskId, boolean killProcess,
+            boolean removeFromRecents) {
+        final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
+                taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
         if (tr != null) {
             tr.removeTaskActivitiesLocked();
-            cleanUpRemovedTaskLocked(tr, killProcess);
+            cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             if (tr.isPersistable) {
                 notifyTaskPersisterLocked(null, true);
             }
@@ -8894,7 +8845,7 @@
                     "removeTask()");
             long ident = Binder.clearCallingIdentity();
             try {
-                return removeTaskByIdLocked(taskId, true);
+                return removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9132,14 +9083,38 @@
         }
     }
 
+    /**
+     * Moves the top activity in the input stackId to the pinned stack.
+     *
+     * @param stackId Id of stack to move the top activity to pinned stack.
+     * @param bounds Bounds to use for pinned stack.
+     *
+     * @return True if the top activity of the input stack was successfully moved to the pinned
+     *          stack.
+     */
     @Override
-    public void resizeStack(int stackId, Rect bounds) {
+    public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "moveTopActivityToPinnedStack()");
+        synchronized (this) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mStackSupervisor.moveTopStackActivityToPinnedStackLocked(stackId, bounds);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) {
         enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                 "resizeStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mStackSupervisor.resizeStackLocked(stackId, bounds, !PRESERVE_WINDOWS);
+                mStackSupervisor.resizeStackLocked(
+                        stackId, bounds, !PRESERVE_WINDOWS, allowResizeInDockedMode);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -9203,7 +9178,8 @@
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false);
+                final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
+                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
                 return tr != null && tr.stack != null && tr.stack.isHomeStack();
             }
         } finally {
@@ -9468,7 +9444,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;
@@ -10314,7 +10290,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();
@@ -10779,19 +10757,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"));
@@ -10994,8 +10959,9 @@
 
     @Override
     public boolean isAssistDataAllowedOnCurrentActivity() {
-        int userId = mCurrentUserId;
+        int userId;
         synchronized (this) {
+            userId = mUserController.mCurrentUserId;
             ActivityRecord activity = getFocusedStack().topActivity();
             if (activity == null) {
                 return false;
@@ -11336,7 +11302,7 @@
     public boolean isTopActivityImmersive() {
         enforceNotIsolatedCaller("startActivity");
         synchronized (this) {
-            ActivityRecord r = getFocusedStack().topRunningActivityLocked(null);
+            ActivityRecord r = getFocusedStack().topRunningActivityLocked();
             return (r != null) ? r.immersive : false;
         }
     }
@@ -11918,7 +11884,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(
@@ -12025,18 +11991,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) {
@@ -12064,7 +12032,7 @@
 
             // Start up initial activity.
             mBooting = true;
-            startHomeActivityLocked(mCurrentUserId, "systemReady");
+            startHomeActivityLocked(currentUserId, "systemReady");
 
             try {
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
@@ -12085,13 +12053,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
@@ -12108,7 +12077,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeTopActivitiesLocked();
-            sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
+            sendUserSwitchBroadcastsLocked(-1, currentUserId);
         }
     }
 
@@ -12307,7 +12276,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);
@@ -13764,38 +13733,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))) {
@@ -13906,15 +13844,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) {
@@ -14024,8 +13953,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,
@@ -14806,7 +14735,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);
@@ -14882,7 +14811,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);
         }
     }
@@ -14916,6 +14845,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;
@@ -15269,8 +15218,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)");
@@ -15289,11 +15238,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()
@@ -15302,24 +15256,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(",");
@@ -15330,23 +15285,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)");
                     }
@@ -15384,12 +15339,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)");
         }
     }
 
@@ -15543,11 +15498,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");
@@ -15559,47 +15514,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());
@@ -16063,9 +16025,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,
@@ -16081,7 +16043,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;
@@ -16102,14 +16064,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);
             }
@@ -16728,7 +16683,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,
@@ -17045,7 +17000,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};
@@ -17555,6 +17510,25 @@
     }
 
     @Override
+    public void removeStack(int stackId) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "detahStack()");
+        if (stackId == HOME_STACK_ID) {
+            throw new IllegalArgumentException("Removing home stack is not allowed.");
+        }
+        synchronized (this) {
+            ActivityStack stack = mStackSupervisor.getStack(stackId);
+            if (stack != null) {
+                ArrayList<TaskRecord> tasks = stack.getAllTasks();
+                for (int i = tasks.size() - 1; i >= 0; i--) {
+                    removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
+                            !REMOVE_FROM_RECENTS);
+                }
+            }
+        }
+    }
+
+    @Override
     public void updatePersistentConfiguration(Configuration values) {
         enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                 "updateConfiguration()");
@@ -17613,6 +17587,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) {
@@ -17658,7 +17638,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);
@@ -17727,7 +17708,7 @@
                 // If the configuration changed, and the caller is not already
                 // in the process of starting an activity, then find the top
                 // activity to check if its configuration needs to change.
-                starting = mainStack.topRunningActivityLocked(null);
+                starting = mainStack.topRunningActivityLocked();
             }
 
             if (starting != null) {
@@ -18003,6 +17984,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) {
@@ -18023,6 +18005,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) {
@@ -18063,6 +18051,9 @@
                     }
                 }
             }
+            if (adj == ProcessList.VISIBLE_APP_ADJ) {
+                adj += minLayer;
+            }
         }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
@@ -18272,11 +18263,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) {
@@ -19132,7 +19123,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);
@@ -19314,6 +19305,8 @@
             uidRec.reset();
         }
 
+        mStackSupervisor.rankTaskLayersIfNeeded();
+
         mAdjSeq++;
         mNewNumServiceProcs = 0;
         mNewNumAServiceProcs = 0;
@@ -20010,44 +20003,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(
@@ -20073,7 +20040,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));
@@ -20087,186 +20054,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 {
@@ -20315,150 +20102,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),
@@ -20466,229 +20109,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) {
@@ -20697,25 +20120,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
@@ -20735,7 +20140,7 @@
     }
 
     boolean isUserRunningLocked(int userId, boolean orStopped) {
-        UserState state = mStartedUsers.get(userId);
+        UserState state = mUserController.getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -20758,50 +20163,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() {
@@ -21040,7 +20413,8 @@
             synchronized (ActivityManagerService.this) {
                 long origId = Binder.clearCallingIdentity();
                 try {
-                    if (!removeTaskByIdLocked(mTaskId, false)) {
+                    // We remove the task from recents to preserve backwards
+                    if (!removeTaskByIdLocked(mTaskId, false, REMOVE_FROM_RECENTS)) {
                         throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                     }
                 } finally {
@@ -21071,7 +20445,7 @@
         public void moveToFront() {
             checkCaller();
             // Will bring task to front if it already has a root activity.
-            startActivityFromRecentsInner(mTaskId, null);
+            startActivityFromRecentsInner(mTaskId, INVALID_STACK_ID, null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d4e798f..4671cb0 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -774,7 +774,7 @@
         boolean unsent = true;
         if ((state == ActivityState.RESUMED
                 || (service.isSleeping() && task.stack != null
-                    && task.stack.topRunningActivityLocked(null) == this))
+                    && task.stack.topRunningActivityLocked() == this))
                 && app != null && app.thread != null) {
             try {
                 ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f549af8..99539e4 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -22,6 +22,7 @@
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
 import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
+import static android.app.ActivityManager.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -253,6 +254,9 @@
 
     private final LaunchingTaskPositioner mTaskPositioner;
 
+    // If the bounds of task contained in this stack should be persisted across power cycles.
+    final boolean mPersistTaskBounds;
+
     static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
     static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
     static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
@@ -354,10 +358,6 @@
         return count;
     }
 
-    int numTasks() {
-        return mTaskHistory.size();
-    }
-
     ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
             RecentTasks recentTasks) {
         mActivityContainer = activityContainer;
@@ -366,10 +366,11 @@
         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;
+        mPersistTaskBounds = mStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID;
     }
 
     void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
@@ -403,9 +404,9 @@
         return mStackSupervisor.okToShowLocked(r);
     }
 
-    final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+    final ActivityRecord topRunningActivityLocked() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked(notTop);
+            ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked();
             if (r != null) {
                 return r;
             }
@@ -689,7 +690,7 @@
             // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is
             // okay to show the activity when locked.
             if (mStackSupervisor.isCurrentProfileLocked(task.userId)
-                    || task.topRunningActivityLocked(null) != null) {
+                    || task.topRunningActivityLocked() != null) {
                 if (DEBUG_TASKS) Slog.d(TAG_TASKS, "switchUserLocked: stack=" + getStackId() +
                         " moving " + task + " to top");
                 mTaskHistory.remove(i);
@@ -901,14 +902,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 +994,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);
+                    }
                 }
             }
         }
@@ -1105,7 +1103,7 @@
                 mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);
             } else {
                 mStackSupervisor.checkReadyForSleepLocked();
-                ActivityRecord top = topStack.topRunningActivityLocked(null);
+                ActivityRecord top = topStack.topRunningActivityLocked();
                 if (top == null || (prev != null && top != prev)) {
                     // If there are no more activities available to run,
                     // do resume anyway to start something.  Also if the top
@@ -1309,6 +1307,11 @@
             return true;
         }
 
+        if (mStackId == PINNED_STACK_ID) {
+            // Pinned stack is always visible if it exist.
+            return true;
+        }
+
         final int stackIndex = mStacks.indexOf(this);
 
         if (stackIndex == mStacks.size() - 1) {
@@ -1326,13 +1329,14 @@
             if (focusedStackId != HOME_STACK_ID) {
                 return true;
             }
-            ActivityRecord topHomeActivity = focusedStack.topRunningActivityLocked(null);
+            ActivityRecord topHomeActivity = focusedStack.topRunningActivityLocked();
             return topHomeActivity == null || !topHomeActivity.isHomeActivity();
         }
 
         final int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
-        if (focusedStackId == DOCKED_STACK_ID && stackIndex == belowFocusedIndex) {
-            // Stacks directly behind the docked stack are always visible.
+        if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
+                && stackIndex == belowFocusedIndex) {
+            // Stacks directly behind the docked or pinned stack are always visible.
             return true;
         }
 
@@ -1346,9 +1350,10 @@
             }
             if (belowFocusedIndex >= 0) {
                 final ActivityStack stack = mStacks.get(belowFocusedIndex);
-                if (stack.mStackId == DOCKED_STACK_ID && stackIndex == (belowFocusedIndex - 1)) {
-                    // The stack behind the docked stack is also visible so we can have a complete
-                    // backdrop to the translucent activity when the docked stack is up.
+                if ((stack.mStackId == DOCKED_STACK_ID || stack.mStackId == PINNED_STACK_ID)
+                        && stackIndex == (belowFocusedIndex - 1)) {
+                    // The stack behind the docked or pinned stack is also visible so we can have a
+                    // complete backdrop to the translucent activity when the docked stack is up.
                     return true;
                 }
             }
@@ -1381,13 +1386,27 @@
         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.
      */
     final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
-        ActivityRecord top = topRunningActivityLocked(null);
+        ActivityRecord top = topRunningActivityLocked();
         if (top == null) {
             return;
         }
@@ -1643,7 +1662,7 @@
      * starting window displayed then remove that starting window. It is possible that the activity
      * in this state will never resumed in which case that starting window will be orphaned. */
     void cancelInitializingActivities() {
-        final ActivityRecord topActivity = topRunningActivityLocked(null);
+        final ActivityRecord topActivity = topRunningActivityLocked();
         boolean aboveTop = true;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
@@ -1719,7 +1738,7 @@
         cancelInitializingActivities();
 
         // Find the first activity that is not finishing.
-        final ActivityRecord next = topRunningActivityLocked(null);
+        final ActivityRecord next = topRunningActivityLocked();
 
         // Remember how we'll process this pause/resume situation, and ensure
         // that the state is reset however we wind up proceeding.
@@ -1808,7 +1827,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();
@@ -2037,7 +2056,7 @@
                 // We should be all done, but let's just make sure our activity
                 // is still at the top and schedule another run if something
                 // weird happened.
-                ActivityRecord nextNext = topRunningActivityLocked(null);
+                ActivityRecord nextNext = topRunningActivityLocked();
                 if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
                         "Activity config changed during resume: " + next
                         + ", new next: " + nextNext);
@@ -2170,12 +2189,12 @@
         // Calculate maximum possible position for this task.
         int maxPosition = mTaskHistory.size();
         if (!mStackSupervisor.isCurrentProfileLocked(task.userId)
-                && task.topRunningActivityLocked(null) == null) {
+                && task.topRunningActivityLocked() == null) {
             // Put non-current user tasks below current user tasks.
             while (maxPosition > 0) {
                 final TaskRecord tmpTask = mTaskHistory.get(maxPosition - 1);
                 if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
-                        || tmpTask.topRunningActivityLocked(null) == null) {
+                        || tmpTask.topRunningActivityLocked() == null) {
                     break;
                 }
                 maxPosition--;
@@ -2217,13 +2236,13 @@
         int taskNdx = mTaskHistory.size();
         final boolean notShownWhenLocked =
                 (newActivity != null && (newActivity.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0)
-                || (newActivity == null && task.topRunningActivityLocked(null) == null);
+                || (newActivity == null && task.topRunningActivityLocked() == null);
         if (!mStackSupervisor.isCurrentProfileLocked(task.userId) && notShownWhenLocked) {
             // Put non-current user tasks below current user tasks.
             while (--taskNdx >= 0) {
                 final TaskRecord tmpTask = mTaskHistory.get(taskNdx);
                 if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
-                        || tmpTask.topRunningActivityLocked(null) == null) {
+                        || tmpTask.topRunningActivityLocked() == null) {
                     break;
                 }
             }
@@ -2769,13 +2788,14 @@
 
     private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
         if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
-            ActivityRecord next = topRunningActivityLocked(null);
+            ActivityRecord next = topRunningActivityLocked();
             final String myReason = reason + " adjustFocus";
             if (next != r) {
                 if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID
-                        || mStackId == DOCKED_STACK_ID)) {
-                    // For freeform and docked stacks we always keep the focus within the stack as
-                    // long as there is a running activity in the stack that we can adjust focus to.
+                        || mStackId == DOCKED_STACK_ID || mStackId == PINNED_STACK_ID)) {
+                    // For freeform, docked, and pinned stacks we always keep the focus within the
+                    // stack as long as there is a running activity in the stack that we can adjust
+                    // focus to.
                     mService.setFocusedActivityLocked(next, myReason);
                     return;
                 } else {
@@ -2812,7 +2832,7 @@
         if (stack == null) {
             return false;
         }
-        final ActivityRecord top = stack.topRunningActivityLocked(null);
+        final ActivityRecord top = stack.topRunningActivityLocked();
         if (top == null) {
             return false;
         }
@@ -2913,7 +2933,7 @@
     }
 
     final void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
-        ActivityRecord r = topRunningActivityLocked(null);
+        ActivityRecord r = topRunningActivityLocked();
         if (r != null && r.app == app) {
             // If the top running activity is from this crashing
             // process, then terminate it to avoid getting in a loop.
@@ -3618,7 +3638,7 @@
     void releaseBackgroundResources(ActivityRecord r) {
         if (hasVisibleBehindActivity() &&
                 !mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) {
-            if (r == topRunningActivityLocked(null)) {
+            if (r == topRunningActivityLocked()) {
                 // Don't release the top activity if it has requested to run behind the next
                 // activity.
                 return;
@@ -3767,7 +3787,7 @@
 
     final void updateTransitLocked(int transit, Bundle options) {
         if (options != null) {
-            ActivityRecord r = topRunningActivityLocked(null);
+            ActivityRecord r = topRunningActivityLocked();
             if (r != null && r.state != ActivityState.RESUMED) {
                 r.updateOptionsLocked(options);
             } else {
@@ -3787,6 +3807,7 @@
                 task.mLastTimeMoved *= -1;
             }
         }
+        mStackSupervisor.invalidateTaskLayers();
     }
 
     void moveHomeStackTaskToTop(int homeStackTaskType) {
@@ -3840,7 +3861,7 @@
         }
 
         // Set focus to the top running activity of this stack.
-        ActivityRecord r = topRunningActivityLocked(null);
+        ActivityRecord r = topRunningActivityLocked();
         mService.setFocusedActivityLocked(r, reason);
 
         if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
@@ -4473,7 +4494,7 @@
     }
 
     ActivityRecord restartPackage(String packageName) {
-        ActivityRecord starting = topRunningActivityLocked(null);
+        ActivityRecord starting = topRunningActivityLocked();
 
         // All activities that came from the package must be
         // restarted as if there was a config change.
@@ -4545,7 +4566,8 @@
 
         if (mTaskHistory.isEmpty()) {
             if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
-            if (isOnHomeDisplay()) {
+            // We only need to adjust focused stack if this stack is in focus.
+            if (isOnHomeDisplay() && mStackSupervisor.isFrontStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
                 if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
@@ -4571,7 +4593,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);
         }
@@ -4620,6 +4642,51 @@
         r.taskConfigOverride = task.mOverrideConfig;
     }
 
+    void setFocusAndResumeStateIfNeeded(
+            ActivityRecord r, boolean setFocus, boolean setResume, String reason) {
+        // If the activity had focus before move focus to this stack.
+        if (setFocus) {
+            // If the activity owns the last resumed activity, transfer that together,
+            // so that we don't resume the same activity again in the new stack.
+            // Apps may depend on onResume()/onPause() being called in pairs.
+            if (setResume) {
+                mResumedActivity = r;
+                // Move the stack in which we are placing the activity to the front. We don't use
+                // ActivityManagerService.setFocusedActivityLocked, because if the activity is
+                // already focused, the call will short-circuit and do nothing.
+                moveToFront(reason);
+            } else {
+                // We need to not only move the stack to the front, but also have the activity
+                // focused. This will achieve both goals.
+                mService.setFocusedActivityLocked(r, reason);
+            }
+        }
+    }
+
+    /**
+     * Moves the input activity from its current stack to this one.
+     * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is
+     * created on this stack which the activity is added to.
+     * */
+    void moveActivityToStack(ActivityRecord r) {
+        final ActivityStack prevStack = r.task.stack;
+        if (prevStack.mStackId == mStackId) {
+            // You are already in the right stack silly...
+            return;
+        }
+
+        final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack)
+                && (mStackSupervisor.topRunningActivityLocked() == r);
+        final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
+
+        final TaskRecord task = createTaskRecord(
+                mStackSupervisor.getNextTaskId(), r.info, r.intent, null, null, true);
+        r.setTask(task, null);
+        task.addActivityToTop(r);
+        setAppTask(r, task);
+        setFocusAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+    }
+
     private void setAppTask(ActivityRecord r, TaskRecord task) {
         final Rect bounds = task.getLaunchBounds();
         task.updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index ac7b9b1..7e47006 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -66,7 +66,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -191,6 +190,9 @@
     // Force the focus to change to the stack we are moving a task to..
     static final boolean FORCE_FOCUS = true;
 
+    // Restore task from the saved recents if it can't be found in any live stack.
+    static final boolean RESTORE_FROM_RECENTS = true;
+
     // Activity actions an app cannot start if it uses a permission which is not granted.
     private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
             new ArrayMap<>();
@@ -293,10 +295,6 @@
     /** Set when we have taken too long waiting to go to sleep. */
     boolean mSleepTimeout = false;
 
-    /** Indicates if we are running on a Leanback-only (TV) device. Only initialized after
-     * setWindowManager is called. **/
-    private boolean mLeanbackOnlyDevice;
-
     /**
      * We don't want to allow the device to go to sleep while in the process
      * of launching an activity.  This is primarily to allow alarm intent
@@ -350,6 +348,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.
@@ -430,15 +435,13 @@
                     throw new IllegalStateException("Default Display does not exist");
                 }
                 mActivityDisplays.put(displayId, activityDisplay);
+                calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
 
             createStackOnDisplay(HOME_STACK_ID, Display.DEFAULT_DISPLAY, true);
             mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID);
 
             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
-
-            // Initialize this here, now that we can get a valid reference to PackageManager.
-            mLeanbackOnlyDevice = isLeanbackOnlyDevice();
         }
     }
 
@@ -533,7 +536,8 @@
 
         mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
         ActivityRecord r = getHomeActivity();
-        if (r != null) {
+        // Only resume home activity if isn't finishing.
+        if (r != null && !r.finishing) {
             mService.setFocusedActivityLocked(r, reason);
             return resumeTopActivitiesLocked(mHomeStack, prev, null);
         }
@@ -541,7 +545,7 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id) {
-        return anyTaskForIdLocked(id, true);
+        return anyTaskForIdLocked(id, RESTORE_FROM_RECENTS, INVALID_STACK_ID);
     }
 
     /**
@@ -549,8 +553,10 @@
      * @param id Id of the task we would like returned.
      * @param restoreFromRecents If the id was not in the active list, but was found in recents,
      *                           restore the task from recents to the active list.
+     * @param stackId The stack to restore the task to (default launch stack will be used
+     *                if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
      */
-    TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents) {
+    TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {
         int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -575,7 +581,7 @@
             return task;
         }
 
-        if (!restoreRecentTaskLocked(task, INVALID_STACK_ID)) {
+        if (!restoreRecentTaskLocked(task, stackId)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -610,7 +616,7 @@
             if (mCurTaskId <= 0) {
                 mCurTaskId = 1;
             }
-        } while (anyTaskForIdLocked(mCurTaskId, false) != null);
+        } while (anyTaskForIdLocked(mCurTaskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID) != null);
         return mCurTaskId;
     }
 
@@ -623,7 +629,7 @@
         if (resumedActivity == null || resumedActivity.app == null) {
             resumedActivity = stack.mPausingActivity;
             if (resumedActivity == null || resumedActivity.app == null) {
-                resumedActivity = stack.topRunningActivityLocked(null);
+                resumedActivity = stack.topRunningActivityLocked();
             }
         }
         return resumedActivity;
@@ -639,7 +645,7 @@
                 if (!isFrontStack(stack)) {
                     continue;
                 }
-                ActivityRecord hr = stack.topRunningActivityLocked(null);
+                ActivityRecord hr = stack.topRunningActivityLocked();
                 if (hr != null) {
                     if (hr.app == null && app.uid == hr.info.applicationInfo.uid
                             && processName.equals(hr.processName)) {
@@ -823,7 +829,7 @@
 
     ActivityRecord topRunningActivityLocked() {
         final ActivityStack focusedStack = mFocusedStack;
-        ActivityRecord r = focusedStack.topRunningActivityLocked(null);
+        ActivityRecord r = focusedStack.topRunningActivityLocked();
         if (r != null) {
             return r;
         }
@@ -833,7 +839,7 @@
         for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = stacks.get(stackNdx);
             if (stack != focusedStack && isFrontStack(stack)) {
-                r = stack.topRunningActivityLocked(null);
+                r = stack.topRunningActivityLocked();
                 if (r != null) {
                     return r;
                 }
@@ -911,10 +917,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);
                 }
@@ -1096,7 +1098,7 @@
                         }
                     } while (!outResult.timeout && outResult.who == null);
                 } else if (res == ActivityManager.START_TASK_TO_FRONT) {
-                    ActivityRecord r = stack.topRunningActivityLocked(null);
+                    ActivityRecord r = stack.topRunningActivityLocked();
                     if (r.nowVisible && r.state == RESUMED) {
                         outResult.timeout = false;
                         outResult.who = new ComponentName(r.info.packageName, r.info.name);
@@ -1771,66 +1773,68 @@
     ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
         final TaskRecord task = r.task;
 
-        // On leanback only devices we should keep all activities in the same stack.
-        if (!mLeanbackOnlyDevice &&
-                (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
+        if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
+            return mHomeStack;
+        }
 
-            ActivityStack stack;
+        ActivityStack stack;
 
-            if (task != null && task.stack != null) {
-                stack = task.stack;
-                if (stack.isOnHomeDisplay()) {
-                    if (mFocusedStack != stack) {
-                        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                                "computeStackFocus: Setting " + "focused stack to r=" + r
-                                + " task=" + task);
-                    } else {
-                        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Focused stack already=" + mFocusedStack);
-                    }
-                }
-                return stack;
-            }
-
-            final ActivityContainer container = r.mInitialActivityContainer;
-            if (container != null) {
-                // The first time put it on the desired stack, after this put on task stack.
-                r.mInitialActivityContainer = null;
-                return container.mStack;
-            }
-
-            // The fullscreen stack is the only stack that can contain any task regardless of if
-            // the task is resizeable or not. So, we let the task go in the fullscreen task if it
-            // is the focus stack.
-            if (mFocusedStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
-                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                        "computeStackFocus: Have a focused stack=" + mFocusedStack);
-                return mFocusedStack;
-            }
-
-            // We first try to put the task in the first dynamic stack.
-            final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
-            for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stack = homeDisplayStacks.get(stackNdx);
-                final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
-                if (isDynamicStack) {
+        if (task != null && task.stack != null) {
+            stack = task.stack;
+            if (stack.isOnHomeDisplay()) {
+                if (mFocusedStack != stack) {
                     if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Setting focused stack=" + stack);
-                    return stack;
+                            "computeStackFocus: Setting " + "focused stack to r=" + r
+                            + " task=" + task);
+                } else {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                        "computeStackFocus: Focused stack already=" + mFocusedStack);
                 }
             }
-
-            // If there is no suitable dynamic stack then we figure out which static stack to use.
-            final int stackId = task != null ? task.getLaunchStackId() :
-                        bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
-                                         FULLSCREEN_WORKSPACE_STACK_ID;
-            stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
-            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
-                    + r + " stackId=" + stack.mStackId);
             return stack;
         }
-        return mHomeStack;
+
+        final ActivityContainer container = r.mInitialActivityContainer;
+        if (container != null) {
+            // The first time put it on the desired stack, after this put on task stack.
+            r.mInitialActivityContainer = null;
+            return container.mStack;
+        }
+
+        // The fullscreen stack can contain any task regardless of if the task is resizeable
+        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
+        // If the freeform stack has focus, and the activity to be launched is resizeable,
+        // we can also put it in the focused stack.
+        final boolean canUseFocusedStack =
+                mFocusedStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID
+                || mFocusedStack.mStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable;
+        if (canUseFocusedStack
+                && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
+            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                    "computeStackFocus: Have a focused stack=" + mFocusedStack);
+            return mFocusedStack;
+        }
+
+        // We first try to put the task in the first dynamic stack.
+        final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
+        for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            stack = homeDisplayStacks.get(stackNdx);
+            final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
+            if (isDynamicStack) {
+                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                        "computeStackFocus: Setting focused stack=" + stack);
+                return stack;
+            }
+        }
+
+        // If there is no suitable dynamic stack then we figure out which static stack to use.
+        final int stackId = task != null ? task.getLaunchStackId() :
+                    bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
+                                     FULLSCREEN_WORKSPACE_STACK_ID;
+        stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+                + r + " stackId=" + stack.mStackId);
+        return stack;
     }
 
     boolean setFocusedStack(ActivityRecord r, String reason) {
@@ -1855,17 +1859,11 @@
 
         boolean overrideBounds = false;
         Rect newBounds = null;
-        if (r.info.resizeable || (inTask != null && inTask.mResizeable)) {
-            if (intent.hasExtra(ActivityOptions.KEY_BOUNDS)) {
+        if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) {
+            ActivityOptions opts = new ActivityOptions(options);
+            if (opts.hasBounds()) {
                 overrideBounds = true;
-                newBounds = Rect.unflattenFromString(
-                        intent.getStringExtra(ActivityOptions.KEY_BOUNDS));
-            } else if (options != null) {
-                ActivityOptions opts = new ActivityOptions(options);
-                if (opts.hasBounds()) {
-                    overrideBounds = true;
-                    newBounds = opts.getBounds();
-                }
+                newBounds = opts.getBounds();
             }
         }
 
@@ -2534,8 +2532,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);
+            }
         }
     }
 
@@ -2680,7 +2683,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
@@ -2688,7 +2691,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));
                 }
             }
         }
@@ -2873,13 +2876,16 @@
         }
 
         if (stackId != task.stack.mStackId) {
-            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason);
-        } else {
-            task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
-                task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker,
-                reason);
+            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+
+            // moveTaskToStackUncheckedLocked() should already placed the task on top,
+            // still need moveTaskToFrontLocked() below for any transition settings.
         }
 
+        final ActivityRecord r = task.getTopActivity();
+        task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+                r == null ? null : r.appTimeTracker, reason);
+
         if (DEBUG_STACK) Slog.d(TAG_STACK,
                 "findTaskToMoveToFront: moved to front of stack=" + task.stack);
     }
@@ -2970,16 +2976,24 @@
         }
     }
 
-    void resizeStackLocked(int stackId, Rect bounds, boolean preserveWindows) {
+    void resizeStackLocked(int stackId, Rect bounds, boolean preserveWindows,
+            boolean allowResizeInDockedMode) {
         final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
             return;
         }
 
+        if (!allowResizeInDockedMode
+                && stackId != DOCKED_STACK_ID && getStack(DOCKED_STACK_ID) != null) {
+            // If the docked stack exist we don't allow resizes of stacks not caused by the docked
+            // stack size changing so things don't get out of sync.
+            return;
+        }
+
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
 
-        ActivityRecord r = stack.topRunningActivityLocked(null);
+        ActivityRecord r = stack.topRunningActivityLocked();
 
         mTmpBounds.clear();
         mTmpConfigs.clear();
@@ -3010,8 +3024,8 @@
                 // In this case we make all other static stacks fullscreen and move all
                 // docked stack tasks to the fullscreen stack.
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i != DOCKED_STACK_ID) {
-                        resizeStackLocked(i, null, preserveWindows);
+                    if (i != DOCKED_STACK_ID && getStack(i) != null) {
+                        resizeStackLocked(i, null, preserveWindows, true);
                     }
                 }
 
@@ -3026,23 +3040,15 @@
             } else {
                 // Docked stacks occupy a dedicated region on screen so the size of all other
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
-                final int leftChange = stack.mBounds.left - bounds.left;
-                final int rightChange = stack.mBounds.right - bounds.right;
-                final int topChange = stack.mBounds.top - bounds.top;
-                final int bottomChange = stack.mBounds.bottom - bounds.bottom;
+                // We get the bounds to use from window manager which has been adjusted for any
+                // screen controls and is also the same for all stacks.
+                mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
 
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
                     if (i != DOCKED_STACK_ID) {
                         ActivityStack otherStack = getStack(i);
                         if (otherStack != null) {
-                            tempRect.set(otherStack.mBounds);
-                            // We adjust the opposing sides of the other stacks to
-                            // the side in the dock stack that changed.
-                            tempRect.left -= rightChange;
-                            tempRect.right -= leftChange;
-                            tempRect.top -= bottomChange;
-                            tempRect.bottom -= topChange;
-                            resizeStackLocked(i, tempRect, PRESERVE_WINDOWS);
+                            resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
                         }
                     }
                 }
@@ -3072,6 +3078,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;
@@ -3104,7 +3112,7 @@
         // to be relaunched due to configuration change.
         boolean kept = true;
         if (overrideConfig != null) {
-            ActivityRecord r = task.topRunningActivityLocked(null);
+            ActivityRecord r = task.topRunningActivityLocked();
             if (r != null) {
                 final ActivityStack stack = task.stack;
                 kept = stack.ensureActivityConfigurationLocked(r, 0, preserveWindow);
@@ -3120,6 +3128,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) {
@@ -3147,12 +3187,12 @@
      * Restores a recent task to a stack
      * @param task The recent task to be restored.
      * @param stackId The stack to restore the task to (default launch stack will be used
-     *                if stackId is invalid).
+     *                if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
      * @return true if the task has been restored successfully.
      */
     private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
         if (stackId == INVALID_STACK_ID) {
-            stackId = mLeanbackOnlyDevice ? mHomeStack.mStackId : task.getLaunchStackId();
+            stackId = task.getLaunchStackId();
         }
         if (task.stack != null) {
             // Task has already been restored once. See if we need to do anything more
@@ -3219,16 +3259,8 @@
 
         // If the task had focus before (or we're requested to move focus),
         // move focus to the new stack.
-        if (forceFocus || wasFocused) {
-            // If the task owns the last resumed activity, transfer that together,
-            // so that we don't resume the same activity again in the new stack.
-            // Apps may depend on onResume()/onPause() being called in pairs.
-            if (wasResumed) {
-                stack.mResumedActivity = r;
-            }
-            // move the stack in which we are placing the task to the front.
-            stack.moveToFront(reason);
-        }
+        stack.setFocusAndResumeStateIfNeeded(
+                r, forceFocus || wasFocused, wasResumed, reason);
 
         return stack;
     }
@@ -3240,13 +3272,14 @@
             return;
         }
         final String reason = "moveTaskToStack";
-        if (stackId == DOCKED_STACK_ID || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+        if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
+                || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             // We are about to relaunch the activity because its configuration changed due to
             // being maximized, i.e. size change. The activity will first remove the old window
             // and then add a new one. This call will tell window manager about this, so it can
             // preserve the old window until the new one is drawn. This prevents having a gap
             // between the removal and addition, in which no window is visible. We also want the
-            // entrace of the new window to be properly animated.
+            // entrance of the new window to be properly animated.
             ActivityRecord r = task.getTopActivity();
             mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
         }
@@ -3261,7 +3294,7 @@
                 && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
             resizeTaskLocked(task, task.mLastNonFullscreenBounds,
                     RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
-        } else if (stackId == DOCKED_STACK_ID) {
+        } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
             resizeTaskLocked(task, stack.mBounds,
                     RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         }
@@ -3272,6 +3305,46 @@
         resumeTopActivitiesLocked();
     }
 
+    boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
+        final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        if (stack == null) {
+            throw new IllegalArgumentException(
+                    "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
+        }
+
+        final ActivityRecord r = stack.topRunningActivityLocked();
+        if (r == null) {
+            Slog.w(TAG, "moveTopStackActivityToPinnedStackLocked: No top running activity"
+                    + " in stack=" + stack);
+            return false;
+        }
+
+        if (!r.info.supportsPip) {
+            Slog.w(TAG,
+                    "moveTopStackActivityToPinnedStackLocked: Picture-In-Picture not supported for "
+                    + " r=" + r);
+            return false;
+        }
+
+        final TaskRecord task = r.task;
+        if (task.mActivities.size() == 1) {
+            // There is only one activity in the task. So, we can just move the task over to the
+            // pinned stack without re-parenting the activity in a different task.
+            moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS);
+        } else {
+            final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+            pinnedStack.moveActivityToStack(r);
+        }
+
+        resizeStackLocked(PINNED_STACK_ID, bounds, PRESERVE_WINDOWS, true);
+
+        // The task might have already been running and its visibility needs to be synchronized with
+        // the visibility of the stack / windows.
+        ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+        resumeTopActivitiesLocked();
+        return true;
+    }
+
     void positionTaskInStackLocked(int taskId, int stackId, int position) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
@@ -3562,6 +3635,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;
@@ -3684,10 +3775,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. */
@@ -3735,7 +3823,7 @@
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = stacks.get(stackNdx);
-                final ActivityRecord r = stack.topRunningActivityLocked(null);
+                final ActivityRecord r = stack.topRunningActivityLocked();
                 final ActivityState state = r == null ? DESTROYED : r.state;
                 if (isFrontStack(stack)) {
                     if (r == null) Slog.e(TAG,
@@ -4023,6 +4111,7 @@
                     return;
                 }
                 mActivityDisplays.put(displayId, activityDisplay);
+                calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
         }
         if (newDisplay) {
@@ -4030,6 +4119,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);
@@ -4190,7 +4289,7 @@
         mLockTaskModeTasks.add(task);
 
         if (task.mLockTaskUid == -1) {
-            task.mLockTaskUid = task.mCallingUid;
+            task.mLockTaskUid = task.effectiveUid;
         }
 
         if (andResume) {
@@ -4845,18 +4944,6 @@
         }
     }
 
-    private boolean isLeanbackOnlyDevice() {
-        boolean onLeanbackOnly = false;
-        try {
-            onLeanbackOnly = AppGlobals.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_LEANBACK_ONLY);
-        } catch (RemoteException e) {
-            // noop
-        }
-
-        return onLeanbackOnly;
-    }
-
     /**
      * Adjust bounds to stay within stack bounds.
      *
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/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index c36fd06..26264e5 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -196,7 +196,7 @@
     }
 
     public boolean getFrontActivityAskCompatModeLocked() {
-        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r == null) {
             return false;
         }
@@ -208,7 +208,7 @@
     }
 
     public void setFrontActivityAskCompatModeLocked(boolean ask) {
-        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r != null) {
             setPackageAskCompatModeLocked(r.packageName, ask);
         }
@@ -230,7 +230,7 @@
     }
 
     public int getFrontActivityScreenCompatModeLocked() {
-        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r == null) {
             return ActivityManager.COMPAT_MODE_UNKNOWN;
         }
@@ -238,7 +238,7 @@
     }
 
     public void setFrontActivityScreenCompatModeLocked(int mode) {
-        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r == null) {
             Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
             return;
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/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index b216114..edd16ef 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -465,7 +465,7 @@
                     if (!sameActivity) {
                         continue;
                     }
-                    if (maxRecents > 0) {
+                    if (maxRecents > 0 && !doTrim) {
                         --maxRecents;
                         continue;
                     }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 7e14b2b..090a342 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -20,6 +20,7 @@
 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.PINNED_STACK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -217,6 +218,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 +243,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 +272,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 +320,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 +490,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
      */
@@ -557,11 +561,11 @@
         return null;
     }
 
-    ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+    ActivityRecord topRunningActivityLocked() {
         if (stack != null) {
             for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
                 ActivityRecord r = mActivities.get(activityNdx);
-                if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
+                if (!r.finishing && stack.okToShowLocked(r)) {
                     return r;
                 }
             }
@@ -1186,14 +1190,14 @@
 
         mFullscreen = bounds == null;
         if (mFullscreen) {
-            if (mBounds != null && stack.mStackId != DOCKED_STACK_ID) {
+            if (mBounds != null && stack.mPersistTaskBounds) {
                 mLastNonFullscreenBounds = mBounds;
             }
             mBounds = null;
             mOverrideConfig = Configuration.EMPTY;
         } else {
             mBounds = new Rect(bounds);
-            if (stack == null || stack.mStackId != DOCKED_STACK_ID) {
+            if (stack == null || stack.mPersistTaskBounds) {
                 mLastNonFullscreenBounds = mBounds;
             }
 
@@ -1232,11 +1236,12 @@
 
     /** Returns the bounds that should be used to launch this task. */
     Rect getLaunchBounds() {
+        final int stackId = stack.mStackId;
         if (stack == null
-                || stack.mStackId == HOME_STACK_ID
-                || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                || stackId == HOME_STACK_ID
+                || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             return (mResizeable && stack != null) ? stack.mBounds : null;
-        } else if (stack.mStackId == DOCKED_STACK_ID) {
+        } else if (!stack.mPersistTaskBounds) {
             return stack.mBounds;
         }
         return mLastNonFullscreenBounds;
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/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index f03e6c7..359cc36 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -192,16 +192,18 @@
         }
 
         public void run() {
-            int newRotation;
             while (mWaitCounter < WAIT_TIMES_MS.length) {
-                updateOrientation();
                 int waitTimeMs;
                 synchronized(mCounterLock) {
-                    waitTimeMs = WAIT_TIMES_MS[mWaitCounter];
+                    waitTimeMs = mWaitCounter < WAIT_TIMES_MS.length ?
+                            WAIT_TIMES_MS[mWaitCounter] : 0;
                     mWaitCounter++;
                 }
                 try {
-                    sleep(waitTimeMs);
+                    if (waitTimeMs > 0) {
+                        sleep(waitTimeMs);
+                        updateOrientation();
+                    }
                 } catch (InterruptedException e) { }
             }
         }
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/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index bb4dbc3..835ba17 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -74,6 +74,7 @@
 
     // Set to true when the animation context has been fully prepared.
     private boolean mPrepared;
+    private boolean mCreatedResources;
     private int mMode;
 
     private final DisplayManagerInternal mDisplayManagerInternal;
@@ -169,6 +170,7 @@
         }
 
         // Done.
+        mCreatedResources = true;
         mPrepared = true;
 
         // Dejanking optimization.
@@ -313,6 +315,34 @@
     }
 
     /**
+     * Dismisses the color fade animation resources.
+     *
+     * This function destroys the resources that are created for the color fade
+     * animation but does not clean up the surface.
+     */
+    public void dismissResources() {
+        if (DEBUG) {
+            Slog.d(TAG, "dismissResources");
+        }
+
+        if (mCreatedResources) {
+            attachEglContext();
+            try {
+                destroyScreenshotTexture();
+                destroyGLShaders();
+                destroyGLBuffers();
+                destroyEglSurface();
+            } finally {
+                detachEglContext();
+            }
+            // This is being called with no active context so shouldn't be
+            // needed but is safer to not change for now.
+            GLES20.glFlush();
+            mCreatedResources = false;
+        }
+    }
+
+    /**
      * Dismisses the color fade animation surface and cleans up.
      *
      * To prevent stray photons from leaking out after the color fade has been
@@ -325,17 +355,8 @@
         }
 
         if (mPrepared) {
-            attachEglContext();
-            try {
-                destroyScreenshotTexture();
-                destroyGLShaders();
-                destroyGLBuffers();
-                destroyEglSurface();
-            } finally {
-                detachEglContext();
-            }
+            dismissResources();
             destroySurface();
-            GLES20.glFlush();
             mPrepared = false;
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 452378f..7b49530 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -837,6 +837,7 @@
         if (mPendingScreenOff && target != Display.STATE_OFF) {
             setScreenState(Display.STATE_OFF);
             mPendingScreenOff = false;
+            mPowerState.dismissColorFadeResources();
         }
 
         if (target == Display.STATE_ON) {
@@ -910,6 +911,7 @@
                 // A black surface is already hiding the contents of the screen.
                 setScreenState(Display.STATE_OFF);
                 mPendingScreenOff = false;
+                mPowerState.dismissColorFadeResources();
             } else if (performScreenOffTransition
                     && mPowerState.prepareColorFade(mContext,
                             mColorFadeFadesConfig ?
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2eabd32..9862516 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -187,7 +187,7 @@
     }
 
     /**
-     * Dismisses the electron beam surface.
+     * Dismisses the color fade surface.
      */
     public void dismissColorFade() {
         mColorFade.dismiss();
@@ -195,6 +195,13 @@
         mColorFadeReady = true;
     }
 
+   /**
+     * Dismisses the color fade resources.
+     */
+    public void dismissColorFadeResources() {
+        mColorFade.dismissResources();
+    }
+
     /**
      * Sets the level of the electron beam steering current.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 96cb51c..37afd08 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -177,6 +177,8 @@
         return mTvInputs.containsValue(deviceId);
     }
 
+    private SelectRequestBuffer mSelectRequestBuffer;
+
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_TV);
         mPrevPortId = Constants.INVALID_PORT_ID;
@@ -205,6 +207,7 @@
         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
         mLocalDeviceAddresses = initLocalDeviceAddresses();
+        resetSelectRequestBuffer();
         launchDeviceDiscovery();
     }
 
@@ -219,6 +222,19 @@
         return Collections.unmodifiableList(addresses);
     }
 
+
+    @ServiceThreadOnly
+    public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
+        assertRunOnServiceThread();
+        mSelectRequestBuffer = requestBuffer;
+    }
+
+    @ServiceThreadOnly
+    private void resetSelectRequestBuffer() {
+        assertRunOnServiceThread();
+        setSelectRequestBuffer(SelectRequestBuffer.EMPTY_BUFFER);
+    }
+
     @Override
     protected int getPreferredAddress() {
         return Constants.ADDR_TV;
@@ -777,6 +793,9 @@
                             addCecDevice(device.getDeviceInfo());
                         }
 
+                        mSelectRequestBuffer.process();
+                        resetSelectRequestBuffer();
+
                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
                         addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index cfc5f7d..11fdb92 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -77,6 +77,8 @@
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
+import com.android.server.hdmi.SelectRequestBuffer.DeviceSelectRequest;
+import com.android.server.hdmi.SelectRequestBuffer.PortSelectRequest;
 
 import libcore.util.EmptyArray;
 
@@ -360,7 +362,9 @@
         }
     }
 
-    private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
+    private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
+
+    private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
 
     public HdmiControlService(Context context) {
         super(context);
@@ -582,6 +586,12 @@
         final int[] finished = new int[1];
         mAddressAllocated = allocatingDevices.isEmpty();
 
+        // For TV device, select request can be invoked while address allocation or device
+        // discovery is in progress. Initialize the request here at the start of allocation,
+        // and process the collected requests later when the allocation and device discovery
+        // is all completed.
+        mSelectRequestBuffer.clear();
+
         for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
             mCecController.allocateLogicalAddress(localDevice.getType(),
                     localDevice.getPreferredAddress(), new AllocateAddressCallback() {
@@ -623,6 +633,9 @@
             int address = device.getDeviceInfo().getLogicalAddress();
             device.handleAddressAllocated(address, initiatedBy);
         }
+        if (isTvDeviceEnabled()) {
+            tv().setSelectRequestBuffer(mSelectRequestBuffer);
+        }
     }
 
     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
@@ -1228,6 +1241,11 @@
                     }
                     HdmiCecLocalDeviceTv tv = tv();
                     if (tv == null) {
+                        if (!mAddressAllocated) {
+                            mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
+                                    HdmiControlService.this, deviceId, callback));
+                            return;
+                        }
                         Slog.w(TAG, "Local tv device not available");
                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
                         return;
@@ -1262,6 +1280,11 @@
                     }
                     HdmiCecLocalDeviceTv tv = tv();
                     if (tv == null) {
+                        if (!mAddressAllocated) {
+                            mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
+                                    HdmiControlService.this, portId, callback));
+                            return;
+                        }
                         Slog.w(TAG, "Local tv device not available");
                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
                         return;
@@ -1912,7 +1935,7 @@
         }
     }
 
-    private HdmiCecLocalDeviceTv tv() {
+    public HdmiCecLocalDeviceTv tv() {
         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
     }
 
diff --git a/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java b/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java
new file mode 100644
index 0000000..75986c7
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java
@@ -0,0 +1,141 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Buffer for portSelect/deviceSelect requests. Requests made before the address allocation
+ * are stored in this buffer, and processed when the allocation is completed.
+ *
+ * <p>This is put into action only if we are a TV device.
+ */
+public class SelectRequestBuffer {
+    private static final String TAG = "SelectRequestBuffer";
+
+    public static final SelectRequestBuffer EMPTY_BUFFER = new SelectRequestBuffer() {
+        @Override
+        public void process() {
+            // Do nothing.
+        }
+    };
+
+    /**
+     * Parent class from which buffer for select requests are inherited. Keeps the callback
+     * and the device/port ID.
+     */
+    public static abstract class SelectRequest {
+        protected final HdmiControlService mService;
+        protected final IHdmiControlCallback mCallback;
+        protected final int mId;
+
+        public SelectRequest(HdmiControlService service, int id, IHdmiControlCallback callback) {
+            mService = service;
+            mId = id;
+            mCallback = callback;
+        }
+
+        protected HdmiCecLocalDeviceTv tv() {
+            return mService.tv();
+        }
+
+        protected boolean isLocalDeviceReady() {
+            if (tv() == null) {
+                Slog.e(TAG, "Local tv device not available");
+                invokeCallback(HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
+                return false;
+            }
+            return true;
+        }
+
+        private void invokeCallback(int reason) {
+            try {
+                if (mCallback != null) {
+                    mCallback.onComplete(reason);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Invoking callback failed:" + e);
+            }
+        }
+
+        /**
+         * Implement this method with a customize action to perform when the request gets
+         * invoked in a deferred manner.
+         */
+        public abstract void process();
+    }
+
+    public static class DeviceSelectRequest extends SelectRequest {
+        private DeviceSelectRequest(HdmiControlService srv, int id, IHdmiControlCallback callback) {
+            super(srv, id, callback);
+        }
+
+        @Override
+        public void process() {
+            if (isLocalDeviceReady()) {
+                Slog.v(TAG, "calling delayed deviceSelect id:" + mId);
+                tv().deviceSelect(mId, mCallback);
+            }
+        }
+    }
+
+    public static class PortSelectRequest extends SelectRequest {
+        private PortSelectRequest(HdmiControlService srv, int id, IHdmiControlCallback callback) {
+            super(srv, id, callback);
+        }
+
+        @Override
+        public void process() {
+            if (isLocalDeviceReady()) {
+                Slog.v(TAG, "calling delayed portSelect id:" + mId);
+                tv().doManualPortSwitching(mId, mCallback);
+            }
+        }
+    }
+
+    public static DeviceSelectRequest newDeviceSelect(HdmiControlService srv, int id,
+            IHdmiControlCallback callback) {
+        return new DeviceSelectRequest(srv, id, callback);
+    }
+
+    public static PortSelectRequest newPortSelect(HdmiControlService srv, int id,
+            IHdmiControlCallback callback) {
+        return new PortSelectRequest(srv, id, callback);
+    }
+
+    // The last select request made by system/app. Note that we do not manage a list of requests
+    // but just keep only the last one since it already invalidates the older ones.
+    private SelectRequest mRequest;
+
+    public void set(SelectRequest request) {
+        mRequest = request;
+    }
+
+    public void process() {
+        if (mRequest != null) {
+            mRequest.process();
+            clear();
+        }
+    }
+
+    public void clear() {
+        mRequest = null;
+    }
+}
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 ecda36a..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);
@@ -443,13 +448,16 @@
     }
 
     /**
-     * A job is rescheduled with exponential back-off if the client requests this from their
-     * execution logic.
-     * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
-     * timeliness of the reschedule. For an idle-mode job, no deadline is given.
+     * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
+     * specify an override deadline on a failed job (the failed job will run even though it's not
+     * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
+     * ready job with {@link JobStatus#numFailures} > 0 will be executed.
+     *
      * @param failureToReschedule Provided job status that we will reschedule.
      * @return A newly instantiated JobStatus with the same constraints as the last job except
      * with adjusted timing constraints.
+     *
+     * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
      */
     private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
         final long elapsedNowMillis = SystemClock.elapsedRealtime();
@@ -479,8 +487,9 @@
     }
 
     /**
-     * Called after a periodic has executed so we can to re-add it. We take the last execution time
-     * of the job to be the time of completion (i.e. the time at which this function is called).
+     * Called after a periodic has executed so we can reschedule it. We take the last execution
+     * time of the job to be the time of completion (i.e. the time at which this function is
+     * called).
      * This could be inaccurate b/c the job can run for as long as
      * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
      * to underscheduling at least, rather than if we had taken the last execution time to be the
@@ -491,7 +500,12 @@
     private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
         final long elapsedNow = SystemClock.elapsedRealtime();
         // Compute how much of the period is remaining.
-        long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
+        long runEarly = 0L;
+
+        // If this periodic was rescheduled it won't have a deadline.
+        if (periodicToReschedule.hasDeadlineConstraint()) {
+            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
+        }
         long newEarliestRunTimeElapsed = elapsedNow + runEarly;
         long period = periodicToReschedule.getJob().getIntervalMillis();
         long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d26319b..5376043 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -62,13 +62,13 @@
  *
  */
 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 =
             ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
-    private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;
+    private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
     private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
 
@@ -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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 3991489..17ae6dc 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -39,17 +39,17 @@
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
-import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
-import static android.net.NetworkPolicyManager.dumpPolicy;
-import static android.net.NetworkPolicyManager.dumpRules;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -108,6 +108,7 @@
 import android.net.NetworkIdentity;
 import android.net.NetworkInfo;
 import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.NetworkTemplate;
@@ -138,6 +139,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.Pair;
@@ -147,8 +149,6 @@
 import android.util.TrustedTime;
 import android.util.Xml;
 
-import com.android.server.DeviceIdleController;
-import com.android.server.EventLogTags;
 import libcore.io.IoUtils;
 
 import com.android.internal.R;
@@ -156,6 +156,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.google.android.collect.Lists;
 
@@ -280,6 +282,12 @@
     /** Currently derived rules for each UID. */
     final SparseIntArray mUidRules = new SparseIntArray();
 
+    final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
+    final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
+
+    /** Set of states for the child firewall chains. True if the chain is active. */
+    final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
+
     /**
      * UIDs that have been white-listed to always be able to have network access
      * in power save mode, except device idle (doze) still applies.
@@ -444,14 +452,8 @@
             // read policy from disk
             readPolicyLocked();
 
-            if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
-                updateRulesForGlobalChangeLocked(false);
-                updateNotificationsLocked();
-            } else {
-                // If we are not in any special mode, we just need to make sure the current
-                // app idle state is updated.
-                updateRulesForAppIdleLocked();
-            }
+            updateRulesForGlobalChangeLocked(false);
+            updateNotificationsLocked();
         }
 
         updateScreenOn();
@@ -1796,7 +1798,9 @@
             if (mDeviceIdleMode != enabled) {
                 mDeviceIdleMode = enabled;
                 if (mSystemReady) {
-                    updateRulesForDeviceIdleLocked();
+                    // Device idle change means we need to rebuild rules for all
+                    // known apps, so do a global refresh.
+                    updateRulesForGlobalChangeLocked(false);
                 }
                 if (enabled) {
                     EventLogTags.writeDeviceIdleOnPhase("net");
@@ -1934,7 +1938,7 @@
                 fout.print("UID=");
                 fout.print(uid);
                 fout.print(" policy=");
-                dumpPolicy(fout, policy);
+                fout.print(DebugUtils.flagsToString(NetworkPolicyManager.class, "POLICY_", policy));
                 fout.println();
             }
             fout.decreaseIndent();
@@ -1979,18 +1983,14 @@
                 fout.print("UID=");
                 fout.print(uid);
 
-                int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+                final int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
                 fout.print(" state=");
                 fout.print(state);
                 fout.print(state <= ActivityManager.PROCESS_STATE_TOP ? " (fg)" : " (bg)");
 
-                fout.print(" rules=");
-                final int rulesIndex = mUidRules.indexOfKey(uid);
-                if (rulesIndex < 0) {
-                    fout.print("UNKNOWN");
-                } else {
-                    dumpRules(fout, mUidRules.valueAt(rulesIndex));
-                }
+                final int rule = mUidRules.get(uid, RULE_UNKNOWN);
+                fout.print(" rule=");
+                fout.print(DebugUtils.valueToString(NetworkPolicyManager.class, "RULE_", rule));
 
                 fout.println();
             }
@@ -2025,7 +2025,7 @@
             updateRulesForUidStateChangeLocked(uid, oldUidState, uidState);
             if (mDeviceIdleMode && isProcStateAllowedWhileIdle(oldUidState)
                     != isProcStateAllowedWhileIdle(uidState)) {
-                updateRulesForDeviceIdleLocked();
+                updateRuleForDeviceIdleLocked(uid);
             }
         }
     }
@@ -2039,7 +2039,7 @@
                 updateRulesForUidStateChangeLocked(uid, oldUidState,
                         ActivityManager.PROCESS_STATE_CACHED_EMPTY);
                 if (mDeviceIdleMode) {
-                    updateRulesForDeviceIdleLocked();
+                    updateRuleForDeviceIdleLocked(uid);
                 }
             }
         }
@@ -2086,7 +2086,8 @@
         if (mDeviceIdleMode) {
             // sync the whitelists before enable dozable chain.  We don't care about the rules if
             // we are disabling the chain.
-            SparseIntArray uidRules = new SparseIntArray();
+            final SparseIntArray uidRules = mUidFirewallDozableRules;
+            uidRules.clear();
             final List<UserInfo> users = mUserManager.getUsers();
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 UserInfo user = users.get(ui);
@@ -2110,6 +2111,7 @@
             }
             setUidFirewallRules(FIREWALL_CHAIN_DOZABLE, uidRules);
         }
+
         enableFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mDeviceIdleMode);
     }
 
@@ -2123,11 +2125,15 @@
                 setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_DEFAULT);
             }
         }
+
+        updateRulesForUidLocked(uid);
     }
 
     void updateRulesForAppIdleLocked() {
+        final SparseIntArray uidRules = mUidFirewallStandbyRules;
+        uidRules.clear();
+
         // Fully update the app idle firewall chain.
-        SparseIntArray uidRules = new SparseIntArray();
         final List<UserInfo> users = mUserManager.getUsers();
         for (int ui = users.size() - 1; ui >= 0; ui--) {
             UserInfo user = users.get(ui);
@@ -2138,6 +2144,7 @@
                 }
             }
         }
+
         setUidFirewallRules(FIREWALL_CHAIN_STANDBY, uidRules);
     }
 
@@ -2150,11 +2157,14 @@
         } else {
             setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
         }
+
+        updateRulesForUidLocked(uid);
     }
 
     void updateRulesForAppIdleParoleLocked() {
         boolean enableChain = !mUsageStats.isAppIdleParoleOn();
         enableFirewallChainLocked(FIREWALL_CHAIN_STANDBY, enableChain);
+        updateRulesForUidsLocked(mUidFirewallStandbyRules);
     }
 
     /**
@@ -2224,6 +2234,12 @@
         return true;
     }
 
+    void updateRulesForUidsLocked(SparseIntArray uids) {
+        for (int i = 0; i < uids.size(); i++) {
+            updateRulesForUidLocked(uids.keyAt(i));
+        }
+    }
+
     /**
      * Applies network rules to bandwidth and firewall controllers based on uid policy.
      * @param uid The uid for which to apply the latest policy
@@ -2245,8 +2261,7 @@
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
         final boolean uidForeground = isUidForegroundLocked(uid);
 
-        // derive active rules based on policy and active state
-
+        // Derive active rules based on policy and active state
         int appId = UserHandle.getAppId(uid);
         int uidRules = RULE_ALLOW_ALL;
         if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
@@ -2269,20 +2284,27 @@
             }
         }
 
-        final int oldRules = mUidRules.get(uid);
+        // Check dozable state, which is whitelist
+        if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)
+                && mUidFirewallDozableRules.get(uid, FIREWALL_RULE_DEFAULT) != FIREWALL_RULE_ALLOW) {
+            uidRules = RULE_REJECT_ALL;
+        }
 
+        // Check standby state, which is blacklist
+        if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)
+                && mUidFirewallStandbyRules.get(uid, FIREWALL_RULE_DEFAULT) == FIREWALL_RULE_DENY) {
+            uidRules = RULE_REJECT_ALL;
+        }
+
+        final int oldRules = mUidRules.get(uid);
         if (uidRules == RULE_ALLOW_ALL) {
             mUidRules.delete(uid);
         } else {
             mUidRules.put(uid, uidRules);
         }
 
-        // Update bandwidth rules if necessary
-        final boolean oldRejectMetered = (oldRules & RULE_REJECT_METERED) != 0;
-        final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0;
-        if (oldRejectMetered != rejectMetered) {
-            setUidNetworkRules(uid, rejectMetered);
-        }
+        final boolean rejectMetered = (uidRules == RULE_REJECT_METERED);
+        setUidNetworkRules(uid, rejectMetered);
 
         // dispatch changed rule to existing listeners
         if (oldRules != uidRules) {
@@ -2468,6 +2490,12 @@
      * Add or remove a uid to the firewall blacklist for all network ifaces.
      */
     private void setUidFirewallRule(int chain, int uid, int rule) {
+        if (chain == FIREWALL_CHAIN_DOZABLE) {
+            mUidFirewallDozableRules.put(uid, rule);
+        } else if (chain == FIREWALL_CHAIN_STANDBY) {
+            mUidFirewallStandbyRules.put(uid, rule);
+        }
+
         try {
             mNetworkManager.setFirewallUidRule(chain, uid, rule);
         } catch (IllegalStateException e) {
@@ -2481,6 +2509,12 @@
      * Add or remove a uid to the firewall blacklist for all network ifaces.
      */
     private void enableFirewallChainLocked(int chain, boolean enable) {
+        if (mFirewallChainStates.indexOfKey(chain) >= 0 &&
+                mFirewallChainStates.get(chain) == enable) {
+            // All is the same, nothing to do.
+            return;
+        }
+        mFirewallChainStates.put(chain, enable);
         try {
             mNetworkManager.setFirewallChainEnabled(chain, enable);
         } catch (IllegalStateException e) {
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/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 8f24b46..a54a61a 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -90,6 +90,10 @@
             = new ArraySet<ComponentName>();
     // Just the packages from mEnabledServicesForCurrentProfiles
     private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
+    // List of packages in restored setting across all mUserProfiles, for quick
+    // filtering upon package updates.
+    private ArraySet<String> mRestoredPackages = new ArraySet<>();
+
 
     // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
     // user change).
@@ -108,6 +112,7 @@
         mRestoreReceiver = new SettingRestoredReceiver();
         IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
         context.registerReceiver(mRestoreReceiver, filter);
+        rebuildRestoredPackages();
     }
 
     class SettingRestoredReceiver extends BroadcastReceiver {
@@ -166,7 +171,7 @@
 
     // By convention, restored settings are replicated to another settings
     // entry, named similarly but with a disambiguation suffix.
-    public static final String restoredSettingName(Config config) {
+    public static String restoredSettingName(Config config) {
         return config.secureSettingName + ":restored";
     }
 
@@ -184,7 +189,8 @@
                         restoredSettingName(mConfig),
                         newValue,
                         userid);
-                disableNonexistentServices(userid);
+                updateSettingsAccordingToInstalledServices(userid);
+                rebuildRestoredPackages();
             }
         }
     }
@@ -198,9 +204,11 @@
                 + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
                 + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
         boolean anyServicesInvolved = false;
+
         if (pkgList != null && (pkgList.length > 0)) {
             for (String pkgName : pkgList) {
-                if (mEnabledServicesPackageNames.contains(pkgName)) {
+                if (mEnabledServicesPackageNames.contains(pkgName) ||
+                        mRestoredPackages.contains(pkgName)) {
                     anyServicesInvolved = true;
                 }
             }
@@ -209,7 +217,8 @@
         if (anyServicesInvolved) {
             // if we're not replacing a package, clean up orphaned bits
             if (!queryReplace) {
-                disableNonexistentServices();
+                updateSettingsAccordingToInstalledServices();
+                rebuildRestoredPackages();
             }
             // make sure we're still bound to any of our services who may have just upgraded
             rebindServices();
@@ -218,6 +227,7 @@
 
     public void onUserSwitched(int user) {
         if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
+        rebuildRestoredPackages();
         if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) {
             if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices().");
             return;
@@ -229,7 +239,7 @@
         checkNotNull(service);
         final IBinder token = service.asBinder();
         final int N = mServices.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             final ManagedServiceInfo info = mServices.get(i);
             if (info.service.asBinder() == token) return info;
         }
@@ -252,91 +262,141 @@
         }
     }
 
-    /**
-     * Remove access for any services that no longer exist.
-     */
-    private void disableNonexistentServices() {
+
+    private void rebuildRestoredPackages() {
+        mRestoredPackages.clear();
+        String settingName = restoredSettingName(mConfig);
         int[] userIds = mUserProfiles.getCurrentProfileIds();
         final int N = userIds.length;
-        for (int i = 0 ; i < N; ++i) {
-            disableNonexistentServices(userIds[i]);
+        for (int i = 0; i < N; ++i) {
+            ArraySet<ComponentName> names = loadComponentNamesFromSetting(settingName, userIds[i]);
+            if (names == null)
+                continue;
+            for (ComponentName name: names) {
+                mRestoredPackages.add(name.getPackageName());
+            }
         }
     }
 
-    private void disableNonexistentServices(int userId) {
+
+    private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, int userId) {
         final ContentResolver cr = mContext.getContentResolver();
-        boolean restoredChanged = false;
-        if (mRestored == null) {
-            String restoredSetting = Settings.Secure.getStringForUser(
-                    cr,
-                    restoredSettingName(mConfig),
-                    userId);
-            if (!TextUtils.isEmpty(restoredSetting)) {
-                if (DEBUG) Slog.d(TAG, "restored: " + restoredSetting);
-                String[] restored = restoredSetting.split(ENABLED_SERVICES_SEPARATOR);
-                mRestored = new ArraySet<String>(Arrays.asList(restored));
-            } else {
-                mRestored = new ArraySet<String>();
+        String settingValue = Settings.Secure.getStringForUser(
+                cr,
+                settingName,
+                userId);
+        if (TextUtils.isEmpty(settingValue))
+            return null;
+        String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
+        ArraySet<ComponentName> result = new ArraySet<>(restored.length);
+        for (int i = 0; i < restored.length; i++) {
+            ComponentName value = ComponentName.unflattenFromString(restored[i]);
+            if (null != value) {
+                result.add(value);
             }
         }
-        String flatIn = Settings.Secure.getStringForUser(
-                cr,
-                mConfig.secureSettingName,
-                userId);
-        if (!TextUtils.isEmpty(flatIn)) {
-            if (DEBUG) Slog.v(TAG, "flat before: " + flatIn);
-            PackageManager pm = mContext.getPackageManager();
-            List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
-                    new Intent(mConfig.serviceInterface),
-                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
-                    userId);
-            if (DEBUG) Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
-            Set<ComponentName> installed = new ArraySet<ComponentName>();
-            for (int i = 0, count = installedServices.size(); i < count; i++) {
-                ResolveInfo resolveInfo = installedServices.get(i);
-                ServiceInfo info = resolveInfo.serviceInfo;
+        return result;
+    }
 
-                ComponentName component = new ComponentName(info.packageName, info.name);
-                if (!mConfig.bindPermission.equals(info.permission)) {
-                    Slog.w(TAG, "Skipping " + getCaption() + " service "
-                            + info.packageName + "/" + info.name
-                            + ": it does not require the permission "
-                            + mConfig.bindPermission);
-                    restoredChanged |= mRestored.remove(component.flattenToString());
+    private void storeComponentsToSetting(Set<ComponentName> components,
+                                          String settingName,
+                                          int userId) {
+        String[] componentNames = null;
+        if (null != components) {
+            componentNames = new String[components.size()];
+            int index = 0;
+            for (ComponentName c: components) {
+                componentNames[index++] = c.flattenToString();
+            }
+        }
+        final String value = (componentNames == null) ? "" :
+                TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames);
+        final ContentResolver cr = mContext.getContentResolver();
+        Settings.Secure.putStringForUser(
+                cr,
+                settingName,
+                value,
+                userId);
+    }
+
+
+    /**
+     * Remove access for any services that no longer exist.
+     */
+    private void updateSettingsAccordingToInstalledServices() {
+        int[] userIds = mUserProfiles.getCurrentProfileIds();
+        final int N = userIds.length;
+        for (int i = 0; i < N; ++i) {
+            updateSettingsAccordingToInstalledServices(userIds[i]);
+        }
+        rebuildRestoredPackages();
+    }
+
+    private void updateSettingsAccordingToInstalledServices(int userId) {
+        boolean restoredChanged = false;
+        boolean currentChanged = false;
+        Set<ComponentName> restored =
+                loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
+        Set<ComponentName> current =
+                loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+        Set<ComponentName> installed = new ArraySet<>();
+
+        final PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+                new Intent(mConfig.serviceInterface),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                userId);
+        if (DEBUG)
+            Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
+
+        for (int i = 0, count = installedServices.size(); i < count; i++) {
+            ResolveInfo resolveInfo = installedServices.get(i);
+            ServiceInfo info = resolveInfo.serviceInfo;
+
+            ComponentName component = new ComponentName(info.packageName, info.name);
+            if (!mConfig.bindPermission.equals(info.permission)) {
+                Slog.w(TAG, "Skipping " + getCaption() + " service "
+                        + info.packageName + "/" + info.name
+                        + ": it does not require the permission "
+                        + mConfig.bindPermission);
+                continue;
+            }
+            installed.add(component);
+        }
+
+        ArraySet<ComponentName> retained = new ArraySet<>();
+
+        for (ComponentName component : installed) {
+            if (null != restored) {
+                boolean wasRestored = restored.remove(component);
+                if (wasRestored) {
+                    // Freshly installed package has service that was mentioned in restored setting.
+                    if (DEBUG)
+                        Slog.v(TAG, "Restoring " + component + " for user " + userId);
+                    restoredChanged = true;
+                    currentChanged = true;
+                    retained.add(component);
                     continue;
                 }
-                installed.add(component);
             }
 
-            String flatOut = "";
-            if (!installed.isEmpty()) {
-                String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR);
-                ArrayList<String> remaining = new ArrayList<String>(enabled.length);
-                for (int i = 0; i < enabled.length; i++) {
-                    ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
-                    if (installed.contains(enabledComponent)) {
-                        remaining.add(enabled[i]);
-                        restoredChanged |= mRestored.remove(enabled[i]);
-                    }
-                }
-                remaining.addAll(mRestored);
-                flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
+            if (null != current) {
+                if (current.contains(component))
+                    retained.add(component);
             }
-            if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
-            if (!flatIn.equals(flatOut)) {
-                Settings.Secure.putStringForUser(cr,
-                        mConfig.secureSettingName,
-                        flatOut, userId);
-            }
-            if (restoredChanged) {
-                if (DEBUG) Slog.d(TAG, "restored changed; rewriting");
-                final String flatRestored = TextUtils.join(ENABLED_SERVICES_SEPARATOR,
-                        mRestored.toArray());
-                Settings.Secure.putStringForUser(cr,
-                        restoredSettingName(mConfig),
-                        flatRestored,
-                        userId);
-            }
+        }
+
+        currentChanged |= ((current == null ? 0 : current.size()) != retained.size());
+
+        if (currentChanged) {
+            if (DEBUG) Slog.v(TAG, "List of  " + getCaption() + " services was updated " + current);
+            storeComponentsToSetting(retained, mConfig.secureSettingName, userId);
+        }
+
+        if (restoredChanged) {
+            if (DEBUG) Slog.v(TAG,
+                    "List of  " + getCaption() + " restored services was updated " + restored);
+            storeComponentsToSetting(restored, restoredSettingName(mConfig), userId);
         }
     }
 
@@ -349,18 +409,15 @@
         final int[] userIds = mUserProfiles.getCurrentProfileIds();
         final int nUserIds = userIds.length;
 
-        final SparseArray<String> flat = new SparseArray<String>();
+        final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>();
 
         for (int i = 0; i < nUserIds; ++i) {
-            flat.put(userIds[i], Settings.Secure.getStringForUser(
-                    mContext.getContentResolver(),
-                    mConfig.secureSettingName,
-                    userIds[i]));
+            componentsByUser.put(userIds[i],
+                    loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i]));
         }
 
-        ArrayList<ManagedServiceInfo> toRemove = new ArrayList<ManagedServiceInfo>();
-        final SparseArray<ArrayList<ComponentName>> toAdd
-                = new SparseArray<ArrayList<ComponentName>>();
+        final ArrayList<ManagedServiceInfo> toRemove = new ArrayList<>();
+        final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<>();
 
         synchronized (mMutex) {
             // Unbind automatically bound services, retain system services.
@@ -370,27 +427,25 @@
                 }
             }
 
-            final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
-            final ArraySet<String> newPackages = new ArraySet<String>();
+            final ArraySet<ComponentName> newEnabled = new ArraySet<>();
+            final ArraySet<String> newPackages = new ArraySet<>();
 
             for (int i = 0; i < nUserIds; ++i) {
-                final ArrayList<ComponentName> add = new ArrayList<ComponentName>();
+                // decode the list of components
+                final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]);
+                if (null == userComponents) {
+                    toAdd.put(userIds[i], new ArrayList<ComponentName>());
+                    continue;
+                }
+
+                final ArrayList<ComponentName> add = new ArrayList<>(userComponents);
                 toAdd.put(userIds[i], add);
 
-                // decode the list of components
-                String toDecode = flat.get(userIds[i]);
-                if (toDecode != null) {
-                    String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
-                    for (int j = 0; j < components.length; j++) {
-                        final ComponentName component
-                                = ComponentName.unflattenFromString(components[j]);
-                        if (component != null) {
-                            newEnabled.add(component);
-                            add.add(component);
-                            newPackages.add(component.getPackageName());
-                        }
-                    }
+                newEnabled.addAll(userComponents);
 
+                for (int j = 0; j < userComponents.size(); j++) {
+                    final ComponentName component = userComponents.valueAt(i);
+                    newPackages.add(component.getPackageName());
                 }
             }
             mEnabledServicesForCurrentProfiles = newEnabled;
@@ -416,7 +471,7 @@
             }
         }
 
-        mLastSeenProfileIds = mUserProfiles.getCurrentProfileIds();
+        mLastSeenProfileIds = userIds;
     }
 
     /**
@@ -434,7 +489,7 @@
             mServicesBinding.add(servicesBindingTag);
 
             final int N = mServices.size();
-            for (int i=N-1; i>=0; i--) {
+            for (int i = N - 1; i >= 0; i--) {
                 final ManagedServiceInfo info = mServices.get(i);
                 if (name.equals(info.component)
                         && info.userid == userid) {
@@ -469,39 +524,39 @@
 
             try {
                 if (DEBUG) Slog.v(TAG, "binding: " + intent);
+                ServiceConnection serviceConnection = new ServiceConnection() {
+                    IInterface mService;
+
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder binder) {
+                        boolean added = false;
+                        ManagedServiceInfo info = null;
+                        synchronized (mMutex) {
+                            mServicesBinding.remove(servicesBindingTag);
+                            try {
+                                mService = asInterface(binder);
+                                info = newServiceInfo(mService, name,
+                                        userid, false /*isSystem*/, this, targetSdkVersion);
+                                binder.linkToDeath(info, 0);
+                                added = mServices.add(info);
+                            } catch (RemoteException e) {
+                                // already dead
+                            }
+                        }
+                        if (added) {
+                            onServiceAdded(info);
+                        }
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        Slog.v(TAG, getCaption() + " connection lost: " + name);
+                    }
+                };
                 if (!mContext.bindServiceAsUser(intent,
-                        new ServiceConnection() {
-                            IInterface mService;
-
-                            @Override
-                            public void onServiceConnected(ComponentName name, IBinder binder) {
-                                boolean added = false;
-                                ManagedServiceInfo info = null;
-                                synchronized (mMutex) {
-                                    mServicesBinding.remove(servicesBindingTag);
-                                    try {
-                                        mService = asInterface(binder);
-                                        info = newServiceInfo(mService, name,
-                                                userid, false /*isSystem*/, this, targetSdkVersion);
-                                        binder.linkToDeath(info, 0);
-                                        added = mServices.add(info);
-                                    } catch (RemoteException e) {
-                                        // already dead
-                                    }
-                                }
-                                if (added) {
-                                    onServiceAdded(info);
-                                }
-                            }
-
-                            @Override
-                            public void onServiceDisconnected(ComponentName name) {
-                                Slog.v(TAG, getCaption() + " connection lost: " + name);
-                            }
-                        },
+                        serviceConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                        new UserHandle(userid)))
-                {
+                        new UserHandle(userid))) {
                     mServicesBinding.remove(servicesBindingTag);
                     Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
                     return;
@@ -519,7 +574,7 @@
     private void unregisterService(ComponentName name, int userid) {
         synchronized (mMutex) {
             final int N = mServices.size();
-            for (int i=N-1; i>=0; i--) {
+            for (int i = N - 1; i >= 0; i--) {
                 final ManagedServiceInfo info = mServices.get(i);
                 if (name.equals(info.component)
                         && info.userid == userid) {
@@ -548,7 +603,7 @@
         ManagedServiceInfo serviceInfo = null;
         synchronized (mMutex) {
             final int N = mServices.size();
-            for (int i=N-1; i>=0; i--) {
+            for (int i = N - 1; i >= 0; i--) {
                 final ManagedServiceInfo info = mServices.get(i);
                 if (info.service.asBinder() == service.asBinder()
                         && info.userid == userid) {
@@ -622,6 +677,7 @@
                 if (DEBUG) Slog.d(TAG, "Setting changed: mSecureSettingsUri=" + mSecureSettingsUri +
                         " / uri=" + uri);
                 rebindServices();
+                rebuildRestoredPackages();
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7b15aad..e8e46ef 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1602,41 +1602,45 @@
         }
 
         @Override
-        public AutomaticZenRule getAutomaticZenRule(String name) throws RemoteException {
+        public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
+            Preconditions.checkNotNull(id, "Id is null");
             enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
-            return mZenModeHelper.getAutomaticZenRule(name);
+            return mZenModeHelper.getAutomaticZenRule(id);
         }
 
         @Override
-        public boolean addOrUpdateAutomaticZenRule(AutomaticZenRule automaticZenRule)
+        public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule)
                 throws RemoteException {
             Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
             Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
             Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
             Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
-            enforcePolicyAccess(Binder.getCallingUid(), "addOrUpdateZenModeRule");
+            enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            return mZenModeHelper.addOrUpdateAutomaticZenRule(automaticZenRule,
-                    "addOrUpdateAutomaticZenRule");
+            return mZenModeHelper.addAutomaticZenRule(automaticZenRule,
+                    "addAutomaticZenRule");
         }
 
         @Override
-        public boolean renameAutomaticZenRule(String oldName, String newName) {
-            Preconditions.checkNotNull(oldName, "oldName is null");
-            Preconditions.checkNotNull(newName, "newName is null");
-            enforcePolicyAccess(Binder.getCallingUid(), "renameAutomaticZenRule");
+        public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule)
+                throws RemoteException {
+            Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
+            Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
+            Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
+            Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
+            enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
 
-            return mZenModeHelper.renameAutomaticZenRule(
-                    oldName, newName, "renameAutomaticZenRule");
+            return mZenModeHelper.updateAutomaticZenRule(automaticZenRule,
+                    "updateAutomaticZenRule");
         }
 
         @Override
-        public boolean removeAutomaticZenRule(String name) throws RemoteException {
-            Preconditions.checkNotNull(name, "Name is null");
+        public boolean removeAutomaticZenRule(String id) throws RemoteException {
+            Preconditions.checkNotNull(id, "Id is null");
             // Verify that they can modify zen rules.
             enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
 
-            return mZenModeHelper.removeAutomaticZenRule(name, "removeAutomaticZenRule");
+            return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule");
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index b89a654..c2e4349 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -104,7 +104,7 @@
     public void onServiceAdded(ComponentName component) {
         if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
         if (isAutomaticActive(component)) {
-            mHelper.setConfig(mHelper.getConfig(), "zmc.onServiceAdded");
+            mHelper.setConfigAsync(mHelper.getConfig(), "zmc.onServiceAdded");
         }
     }
 
@@ -120,7 +120,7 @@
             updated |= updateSnoozing(automaticRule);
         }
         if (updated) {
-            mHelper.setConfig(config, "conditionChanged");
+            mHelper.setConfigAsync(config, "conditionChanged");
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4d41e3a..76c6443 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -49,6 +49,7 @@
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenModeConfig.ZenRule;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -211,82 +212,69 @@
         return rules;
     }
 
-    public AutomaticZenRule getAutomaticZenRule(String name) {
+    public AutomaticZenRule getAutomaticZenRule(String id) {
         if (mConfig == null) return null;
-        for(ZenRule rule : mConfig.automaticRules.values()) {
-            if (canManageAutomaticZenRule(rule) && rule.name.equals(name)) {
-                return createAutomaticZenRule(rule);
-            }
+        ZenRule rule = mConfig.automaticRules.get(id);
+        if (rule == null) return null;
+        if (canManageAutomaticZenRule(rule)) {
+             return createAutomaticZenRule(rule);
         }
         return null;
     }
 
-    public boolean addOrUpdateAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
+    public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
+        if (mConfig == null) return null;
+        if (DEBUG) {
+          Log.d(TAG, "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" +reason);
+        }
+        if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+            throw new IllegalArgumentException("Rule already exists");
+        }
+        final ZenModeConfig newConfig = mConfig.copy();
+        ZenRule rule = new ZenRule();
+        populateZenRule(automaticZenRule, rule, true);
+        newConfig.automaticRules.put(rule.id, rule);
+        if (setConfig(newConfig, reason, true)) {
+            return createAutomaticZenRule(rule);
+        } else {
+            return null;
+        }
+    }
+
+    public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
         if (mConfig == null) return false;
         if (DEBUG) {
-            Log.d(TAG, "addOrUpdateAutomaticZenRule zenRule=" + automaticZenRule
+            Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
                     + " reason=" + reason);
         }
         final ZenModeConfig newConfig = mConfig.copy();
-        String ruleId = findMatchingRuleId(newConfig, automaticZenRule.getName());
+        final String ruleId = automaticZenRule.getId();
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         if (ruleId == null) {
-            ruleId = newConfig.newRuleId();
-            rule.name = automaticZenRule.getName();
-            rule.component = automaticZenRule.getOwner();
+            throw new IllegalArgumentException("Rule doesn't exist");
         } else {
             rule = newConfig.automaticRules.get(ruleId);
-            if (!canManageAutomaticZenRule(rule)) {
+            if (rule == null || !canManageAutomaticZenRule(rule)) {
                 throw new SecurityException(
                         "Cannot update rules not owned by your condition provider");
             }
         }
-        if (rule.enabled != automaticZenRule.isEnabled()) {
-            rule.snoozing = false;
-        }
-        rule.condition = null;
-        rule.conditionId = automaticZenRule.getConditionId();
-        rule.enabled = automaticZenRule.isEnabled();
-        rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
-                automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+        populateZenRule(automaticZenRule, rule, false);
         newConfig.automaticRules.put(ruleId, rule);
         return setConfig(newConfig, reason, true);
     }
 
-    public boolean renameAutomaticZenRule(String oldName, String newName, String reason) {
+    public boolean removeAutomaticZenRule(String id, String reason) {
         if (mConfig == null) return false;
-        if (DEBUG) {
-            Log.d(TAG, "renameAutomaticZenRule oldName=" + oldName + "  newName=" + newName
-                    + " reason=" + reason);
-        }
         final ZenModeConfig newConfig = mConfig.copy();
-        String ruleId = findMatchingRuleId(newConfig, oldName);
-        if (ruleId == null) {
-            return false;
+        ZenRule rule = newConfig.automaticRules.get(id);
+        if (rule == null) return false;
+        if (canManageAutomaticZenRule(rule)) {
+            newConfig.automaticRules.remove(id);
+            if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
         } else {
-            ZenRule rule = newConfig.automaticRules.get(ruleId);
-            if (!canManageAutomaticZenRule(rule)) {
-                throw new SecurityException(
-                        "Cannot update rules not owned by your condition provider");
-            }
-            rule.name = newName;
-            return setConfig(newConfig, reason, true);
-        }
-    }
-
-    public boolean removeAutomaticZenRule(String name, String reason) {
-        if (mConfig == null) return false;
-        final ZenModeConfig newConfig = mConfig.copy();
-        String ruleId = findMatchingRuleId(newConfig, name);
-        if (ruleId != null) {
-            ZenRule rule = newConfig.automaticRules.get(ruleId);
-            if (canManageAutomaticZenRule(rule)) {
-                newConfig.automaticRules.remove(ruleId);
-                if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + name + " reason=" + reason);
-            } else {
-                throw new SecurityException(
-                        "Cannot delete rules not owned by your condition provider");
-            }
+            throw new SecurityException(
+                     "Cannot delete rules not owned by your condition provider");
         }
         return setConfig(newConfig, reason, true);
     }
@@ -310,9 +298,28 @@
         }
     }
 
+    private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) {
+        if (isNew) {
+            rule.id = ZenModeConfig.newRuleId();
+            rule.creationTime = System.currentTimeMillis();
+            rule.component = automaticZenRule.getOwner();
+        }
+
+        if (rule.enabled != automaticZenRule.isEnabled()) {
+            rule.snoozing = false;
+        }
+        rule.name = automaticZenRule.getName();
+        rule.condition = null;
+        rule.conditionId = automaticZenRule.getConditionId();
+        rule.enabled = automaticZenRule.isEnabled();
+        rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+                automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+    }
+
     private AutomaticZenRule createAutomaticZenRule(ZenRule rule) {
         return new AutomaticZenRule(rule.name, rule.component, rule.conditionId,
-                NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled);
+                NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
+                rule.id, rule.creationTime);
     }
 
     public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
@@ -344,15 +351,6 @@
         setConfig(newConfig, reason, setRingerMode);
     }
 
-    private String findMatchingRuleId(ZenModeConfig config, String ruleName) {
-        for (String ruleId : config.automaticRules.keySet()) {
-            if (config.automaticRules.get(ruleId).name.equals(ruleName)) {
-                return ruleId;
-            }
-        }
-        return null;
-    }
-
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mZenMode=");
         pw.println(Global.zenModeToString(mZenMode));
@@ -446,6 +444,10 @@
         return setConfig(config, reason, true /*setRingerMode*/);
     }
 
+    public void setConfigAsync(ZenModeConfig config, String reason) {
+        mHandler.postSetConfig(config, reason);
+    }
+
     private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -640,7 +642,9 @@
         rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
         rule1.zenMode = Global.ZEN_MODE_ALARMS;
         rule1.component = ScheduleConditionProvider.COMPONENT;
-        config.automaticRules.put(config.newRuleId(), rule1);
+        rule1.id = ZenModeConfig.newRuleId();
+        rule1.creationTime = System.currentTimeMillis();
+        config.automaticRules.put(rule1.id, rule1);
 
         final ScheduleInfo weekends = new ScheduleInfo();
         weekends.days = ZenModeConfig.WEEKEND_DAYS;
@@ -654,7 +658,9 @@
         rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
         rule2.zenMode = Global.ZEN_MODE_ALARMS;
         rule2.component = ScheduleConditionProvider.COMPONENT;
-        config.automaticRules.put(config.newRuleId(), rule2);
+        rule2.id = ZenModeConfig.newRuleId();
+        rule2.creationTime = System.currentTimeMillis();
+        config.automaticRules.put(rule2.id, rule2);
     }
 
     private void appendDefaultEventRules(ZenModeConfig config) {
@@ -669,7 +675,9 @@
         rule.conditionId = ZenModeConfig.toEventConditionId(events);
         rule.zenMode = Global.ZEN_MODE_ALARMS;
         rule.component = EventConditionProvider.COMPONENT;
-        config.automaticRules.put(config.newRuleId(), rule);
+        rule.id = ZenModeConfig.newRuleId();
+        rule.creationTime = System.currentTimeMillis();
+        config.automaticRules.put(rule.id, rule);
     }
 
     private static int zenSeverity(int zen) {
@@ -710,7 +718,7 @@
                 rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
                         : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
                 rule.component = ScheduleConditionProvider.COMPONENT;
-                rt.automaticRules.put(rt.newRuleId(), rule);
+                rt.automaticRules.put(ZenModeConfig.newRuleId(), rule);
             } else {
                 Log.i(TAG, "No existing V1 downtime found, generating default schedules");
                 appendDefaultScheduleRules(rt);
@@ -883,6 +891,17 @@
     private final class H extends Handler {
         private static final int MSG_DISPATCH = 1;
         private static final int MSG_METRICS = 2;
+        private static final int MSG_SET_CONFIG = 3;
+
+        private final class ConfigMessageData {
+            public final ZenModeConfig config;
+            public final String reason;
+
+            ConfigMessageData(ZenModeConfig config, String reason) {
+                this.config = config;
+                this.reason = reason;
+            }
+        }
 
         private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
 
@@ -900,6 +919,10 @@
             sendEmptyMessageDelayed(MSG_METRICS, METRICS_PERIOD_MS);
         }
 
+        private void postSetConfig(ZenModeConfig config, String reason) {
+            sendMessage(obtainMessage(MSG_SET_CONFIG, new ConfigMessageData(config, reason)));
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -909,6 +932,10 @@
                 case MSG_METRICS:
                     mMetrics.emit();
                     break;
+                case MSG_SET_CONFIG:
+                    ConfigMessageData configData = (ConfigMessageData)msg.obj;
+                    setConfig(configData.config, configData.reason);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index b504605..8abc8fc 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -571,6 +571,26 @@
                 grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
             }
 
+            // Android Wear Home
+            if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+                homeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+                PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
+                        homeIntent, userId);
+
+                if (wearHomePackage != null
+                        && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+                    grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
+                            userId);
+                    grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+                    grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+                            userId);
+                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
+                            userId);
+                }
+            }
+
             mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
         }
     }
@@ -578,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 99562f3..aed2f0b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2276,7 +2276,7 @@
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
-                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, UserHandle.SYSTEM);
+                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
                         } catch (PackageManagerException e) {
                             Slog.e(TAG, "Failed to parse original system package: "
                                     + e.getMessage());
@@ -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;
@@ -5690,7 +5685,7 @@
             }
             try {
                 scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
-                        scanFlags, currentTime, UserHandle.SYSTEM);
+                        scanFlags, currentTime, null);
             } catch (PackageManagerException e) {
                 Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
 
@@ -5796,8 +5791,6 @@
      */
     private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
             long currentTime, UserHandle user) throws PackageManagerException {
-        Preconditions.checkNotNull(user);
-
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
         parseFlags |= mDefParseFlags;
         PackageParser pp = new PackageParser();
@@ -7685,8 +7678,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 +7960,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 +8358,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 +8434,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);
         }
     }
 
@@ -8471,6 +8477,7 @@
 
         final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
 
+        boolean runtimePermissionsRevoked = false;
         int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
 
         boolean changedInstallPermission = false;
@@ -8480,6 +8487,17 @@
             if (!ps.isSharedUser()) {
                 origPermissions = new PermissionsState(permissionsState);
                 permissionsState.reset();
+            } else {
+                // We need to know only about runtime permission changes since the
+                // calling code always writes the install permissions state but
+                // the runtime ones are written only if changed. The only cases of
+                // changed runtime permissions here are promotion of an install to
+                // runtime and revocation of a runtime from a shared user.
+                changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
+                        ps.sharedUser, UserManagerService.getInstance().getUserIds());
+                if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
+                    runtimePermissionsRevoked = true;
+                }
             }
         }
 
@@ -8695,9 +8713,11 @@
             ps.installPermissionsFixed = true;
         }
 
-        // Persist the runtime permissions state for users with changes.
+        // Persist the runtime permissions state for users with changes. If permissions
+        // were revoked because no app in the shared user declares them we have to
+        // write synchronously to avoid losing runtime permissions state.
         for (int userId : changedRuntimePermissionUserIds) {
-            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
         }
 
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -10073,6 +10093,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);
 
@@ -12115,7 +12139,7 @@
                 int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME;
                 try {
                     scanPackageTracedLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime,
-                            UserHandle.SYSTEM);
+                            null);
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: "
                             + e.getMessage());
@@ -12248,6 +12272,66 @@
         }
     }
 
+    private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
+        // Collect all used permissions in the UID
+        ArraySet<String> usedPermissions = new ArraySet<>();
+        final int packageCount = su.packages.size();
+        for (int i = 0; i < packageCount; i++) {
+            PackageSetting ps = su.packages.valueAt(i);
+            if (ps.pkg == null) {
+                continue;
+            }
+            final int requestedPermCount = ps.pkg.requestedPermissions.size();
+            for (int j = 0; j < requestedPermCount; j++) {
+                String permission = ps.pkg.requestedPermissions.get(j);
+                BasePermission bp = mSettings.mPermissions.get(permission);
+                if (bp != null) {
+                    usedPermissions.add(permission);
+                }
+            }
+        }
+
+        PermissionsState permissionsState = su.getPermissionsState();
+        // Prune install permissions
+        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+        final int installPermCount = installPermStates.size();
+        for (int i = installPermCount - 1; i >= 0;  i--) {
+            PermissionState permissionState = installPermStates.get(i);
+            if (!usedPermissions.contains(permissionState.getName())) {
+                BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
+                if (bp != null) {
+                    permissionsState.revokeInstallPermission(bp);
+                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                            PackageManager.MASK_PERMISSION_FLAGS, 0);
+                }
+            }
+        }
+
+        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+        // Prune runtime permissions
+        for (int userId : allUserIds) {
+            List<PermissionState> runtimePermStates = permissionsState
+                    .getRuntimePermissionStates(userId);
+            final int runtimePermCount = runtimePermStates.size();
+            for (int i = runtimePermCount - 1; i >= 0; i--) {
+                PermissionState permissionState = runtimePermStates.get(i);
+                if (!usedPermissions.contains(permissionState.getName())) {
+                    BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
+                    if (bp != null) {
+                        permissionsState.revokeRuntimePermission(bp, userId);
+                        permissionsState.updatePermissionFlags(bp, userId,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+                                runtimePermissionChangedUserIds, userId);
+                    }
+                }
+            }
+        }
+
+        return runtimePermissionChangedUserIds;
+    }
+
     private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
             String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res,
             UserHandle user) {
@@ -12338,6 +12422,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) {
@@ -12353,7 +12438,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);
@@ -12383,7 +12469,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;
@@ -12393,6 +12478,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();
@@ -12570,7 +12665,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);
@@ -12772,6 +12867,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)) {
@@ -12860,9 +12967,11 @@
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
         try {
             if (dpm != null) {
-                if (dpm.isDeviceOwner(packageName)) {
+                // Does the package contains the device owner?
+                if (dpm.isDeviceOwnerPackage(packageName)) {
                     return true;
                 }
+                // Does it contain a device admin for any user?
                 int[] users;
                 if (userId == UserHandle.USER_ALL) {
                     users = sUserManager.getUserIds();
@@ -13165,8 +13274,7 @@
 
         final PackageParser.Package newPkg;
         try {
-            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0,
-                    UserHandle.SYSTEM);
+            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage());
             return false;
@@ -13786,7 +13894,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());
@@ -15640,7 +15748,7 @@
         if (isMounted) {
             if (DEBUG_SD_INSTALL)
                 Log.i(TAG, "Loading packages");
-            loadMediaPackages(processCids, uidArr);
+            loadMediaPackages(processCids, uidArr, externalStorage);
             startCleaningPackages();
             mInstallerService.onSecureContainersAvailable();
         } else {
@@ -15695,7 +15803,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();
 
@@ -15729,8 +15838,7 @@
                 synchronized (mInstallLock) {
                     PackageParser.Package pkg = null;
                     try {
-                        pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0,
-                                UserHandle.SYSTEM);
+                        pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, null);
                     } catch (PackageManagerException e) {
                         Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
                     }
@@ -15768,7 +15876,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) {
@@ -15776,7 +15887,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();
@@ -15890,8 +16001,7 @@
             synchronized (mInstallLock) {
                 final PackageParser.Package pkg;
                 try {
-                    pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0,
-                            UserHandle.SYSTEM);
+                    pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null);
                     loaded.add(pkg.applicationInfo);
                 } catch (PackageManagerException e) {
                     Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
@@ -15910,7 +16020,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/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 66170d4..5d8b1d2 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -100,6 +100,9 @@
     private static final String SEAPP_HASH_FILE =
             Environment.getDataDirectory().toString() + "/system/seapp_hash";
 
+    // Append privapp to existing seinfo label
+    private static final String PRIVILEGED_APP_STR = ":privapp";
+
     /**
      * Load the mac_permissions.xml file containing all seinfo assignments used to
      * label apps. The loaded mac_permissions.xml file is determined by the
@@ -313,6 +316,9 @@
             }
         }
 
+        if (pkg.applicationInfo.isPrivilegedApp())
+            pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
+
         if (DEBUG_POLICY_INSTALL) {
             Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
                     "seinfo=" + pkg.applicationInfo.seinfo);
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 31fc24b..b36a22e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -59,8 +59,6 @@
 import android.util.TimeUtils;
 import android.util.Xml;
 
-import com.google.android.collect.Sets;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.FastXmlSerializer;
@@ -82,7 +80,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 
 import libcore.io.IoUtils;
 
@@ -147,10 +144,6 @@
      */
     private static final boolean CONFIG_PROFILES_SHARE_CREDENTIAL = true;
 
-    // Set of user restrictions, which can only be enforced by the system
-    private static final Set<String> SYSTEM_CONTROLLED_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO);
-
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
@@ -363,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) {
@@ -584,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();
@@ -596,7 +608,7 @@
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
         synchronized (mPackagesLock) {
-            if (!SYSTEM_CONTROLLED_RESTRICTIONS.contains(key)) {
+            if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
                 Bundle restrictions = getUserRestrictions(userId);
                 restrictions.putBoolean(key, value);
                 setUserRestrictionsInternalLocked(restrictions, userId);
@@ -622,7 +634,7 @@
         synchronized (mPackagesLock) {
             final Bundle oldUserRestrictions = mUserRestrictions.get(userId);
             // Restore the original state of system controlled restrictions from oldUserRestrictions
-            for (String key : SYSTEM_CONTROLLED_RESTRICTIONS) {
+            for (String key : UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS) {
                 restrictions.remove(key);
                 if (oldUserRestrictions.containsKey(key)) {
                     restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key));
@@ -815,7 +827,8 @@
                                 && type != XmlPullParser.END_TAG) {
                             if (type == XmlPullParser.START_TAG) {
                                 if (parser.getName().equals(TAG_RESTRICTIONS)) {
-                                    readRestrictionsLocked(parser, mGuestRestrictions);
+                                    UserRestrictionsUtils
+                                            .readRestrictions(parser, mGuestRestrictions);
                                 }
                                 break;
                             }
@@ -978,7 +991,7 @@
             serializer.endTag(null, TAG_NAME);
             Bundle restrictions = mUserRestrictions.get(userInfo.id);
             if (restrictions != null) {
-                writeRestrictionsLocked(serializer, restrictions);
+                UserRestrictionsUtils.writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1016,7 +1029,8 @@
             serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
 
             serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-            writeRestrictionsLocked(serializer, mGuestRestrictions);
+            UserRestrictionsUtils
+                    .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
@@ -1036,45 +1050,6 @@
         }
     }
 
-    private void writeRestrictionsLocked(XmlSerializer serializer, Bundle restrictions)
-            throws IOException {
-        serializer.startTag(null, TAG_RESTRICTIONS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_INSTALL_APPS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
-        writeBoolean(serializer, restrictions,
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_REMOVE_USER);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_DEBUGGING_FEATURES);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_VPN);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_TETHERING);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_NETWORK_RESET);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_FACTORY_RESET);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADD_USER);
-        writeBoolean(serializer, restrictions, UserManager.ENSURE_VERIFY_APPS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_APPS_CONTROL);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADJUST_VOLUME);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_CALLS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_SMS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_FUN);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_WALLPAPER);
-        writeBoolean(serializer, restrictions, UserManager.DISALLOW_SAFE_BOOT);
-        writeBoolean(serializer, restrictions, UserManager.ALLOW_PARENT_PROFILE_APP_LINKING);
-        serializer.endTag(null, TAG_RESTRICTIONS);
-    }
-
     private UserInfo readUserLocked(int id) {
         int flags = 0;
         int serialNumber = id;
@@ -1143,7 +1118,7 @@
                             name = parser.getText();
                         }
                     } else if (TAG_RESTRICTIONS.equals(tag)) {
-                        readRestrictionsLocked(parser, restrictions);
+                        UserRestrictionsUtils.readRestrictions(parser, restrictions);
                     }
                 }
             }
@@ -1172,60 +1147,6 @@
         return null;
     }
 
-    private void readRestrictionsLocked(XmlPullParser parser, Bundle restrictions)
-            throws IOException {
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_INSTALL_APPS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
-        readBoolean(parser, restrictions,
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_REMOVE_USER);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_DEBUGGING_FEATURES);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_VPN);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_TETHERING);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_NETWORK_RESET);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_FACTORY_RESET);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_ADD_USER);
-        readBoolean(parser, restrictions, UserManager.ENSURE_VERIFY_APPS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_APPS_CONTROL);
-        readBoolean(parser, restrictions,
-                UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_ADJUST_VOLUME);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_CALLS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_SMS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_FUN);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_WALLPAPER);
-        readBoolean(parser, restrictions, UserManager.DISALLOW_SAFE_BOOT);
-        readBoolean(parser, restrictions, UserManager.ALLOW_PARENT_PROFILE_APP_LINKING);
-    }
-
-    private void readBoolean(XmlPullParser parser, Bundle restrictions,
-            String restrictionKey) {
-        String value = parser.getAttributeValue(null, restrictionKey);
-        if (value != null) {
-            restrictions.putBoolean(restrictionKey, Boolean.parseBoolean(value));
-        }
-    }
-
-    private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
-            throws IOException {
-        if (restrictions.containsKey(restrictionKey)) {
-            xml.attribute(null, restrictionKey,
-                    Boolean.toString(restrictions.getBoolean(restrictionKey)));
-        }
-    }
-
     private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
         String valueString = parser.getAttributeValue(null, attr);
         if (valueString == null) return defaultValue;
@@ -1665,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
@@ -1676,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);
@@ -1700,7 +1618,7 @@
 
     @Override
     public void removeRestrictions() {
-        checkManageUsersPermission("Only system can remove restrictions");
+        checkManageUsersPermission("remove restrictions");
         final int userHandle = UserHandle.getCallingUserId();
         removeRestrictionsForUser(userHandle, true);
     }
@@ -2142,7 +2060,13 @@
                     sb.append(" ago");
                     pw.println(sb);
                 }
+                pw.println("    Restrictions:");
+                UserRestrictionsUtils.dumpRestrictions(
+                        pw, "      ", mUserRestrictions.get(user.id));
             }
+            pw.println();
+            pw.println("Guest restrictions:");
+            UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
new file mode 100644
index 0000000..db1fd2e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -0,0 +1,122 @@
+/*
+ * 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.pm;
+
+import com.google.android.collect.Sets;
+
+import com.android.internal.util.Preconditions;
+
+import android.os.Bundle;
+import android.os.UserManager;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Set;
+
+public class UserRestrictionsUtils {
+    private UserRestrictionsUtils() {
+    }
+
+    public static final String[] USER_RESTRICTIONS = {
+            UserManager.DISALLOW_CONFIG_WIFI,
+            UserManager.DISALLOW_MODIFY_ACCOUNTS,
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_UNINSTALL_APPS,
+            UserManager.DISALLOW_SHARE_LOCATION,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_CONFIG_BLUETOOTH,
+            UserManager.DISALLOW_USB_FILE_TRANSFER,
+            UserManager.DISALLOW_CONFIG_CREDENTIALS,
+            UserManager.DISALLOW_REMOVE_USER,
+            UserManager.DISALLOW_DEBUGGING_FEATURES,
+            UserManager.DISALLOW_CONFIG_VPN,
+            UserManager.DISALLOW_CONFIG_TETHERING,
+            UserManager.DISALLOW_NETWORK_RESET,
+            UserManager.DISALLOW_FACTORY_RESET,
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.ENSURE_VERIFY_APPS,
+            UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+            UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+            UserManager.DISALLOW_APPS_CONTROL,
+            UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+            UserManager.DISALLOW_UNMUTE_MICROPHONE,
+            UserManager.DISALLOW_ADJUST_VOLUME,
+            UserManager.DISALLOW_OUTGOING_CALLS,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_FUN,
+            UserManager.DISALLOW_CREATE_WINDOWS,
+            UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
+            UserManager.DISALLOW_OUTGOING_BEAM,
+            UserManager.DISALLOW_WALLPAPER,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
+            UserManager.DISALLOW_RECORD_AUDIO,
+    };
+
+    /**
+     * Set of user restrictions, which can only be enforced by the system.
+     */
+    public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO);
+
+    /**
+     * Set of user restriction which we don't want to persist.
+     */
+    public static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO);
+
+    public static void writeRestrictions(XmlSerializer serializer, Bundle restrictions,
+            String tag) throws IOException {
+        serializer.startTag(null, tag);
+        for (String key : USER_RESTRICTIONS) {
+            //
+            if (restrictions.getBoolean(key)
+                    && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
+                serializer.attribute(null, key, "true");
+            }
+        }
+        serializer.endTag(null, tag);
+    }
+
+    public static void readRestrictions(XmlPullParser parser, Bundle restrictions)
+            throws IOException {
+        for (String key : USER_RESTRICTIONS) {
+            final String value = parser.getAttributeValue(null, key);
+            if (value != null) {
+                restrictions.putBoolean(key, Boolean.parseBoolean(value));
+            }
+        }
+    }
+
+    public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
+        boolean noneSet = true;
+        if (restrictions != null) {
+            for (String key : restrictions.keySet()) {
+                if (restrictions.getBoolean(key, false)) {
+                    pw.println(prefix + key);
+                    noneSet = false;
+                }
+            }
+        }
+        if (noneSet) {
+            pw.println(prefix + "none");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4f30a15..b38b9ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,6 +16,16 @@
 
 package com.android.server.policy;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.view.WindowManager.LayoutParams.*;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
+
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerInternal.SleepToken;
@@ -40,7 +50,6 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -130,14 +139,6 @@
 import java.util.HashSet;
 import java.util.List;
 
-import static android.view.WindowManager.LayoutParams.*;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
-
 /**
  * WindowManagerPolicy implementation for the Android phone UI.  This
  * introduces a new method suffix, Lp, for an internal lock of the
@@ -3015,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;
@@ -4613,7 +4614,8 @@
                 final int fl = PolicyControl.getWindowFlags(null, lp);
                 if (localLOGV) {
                     Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
-                            + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw());
+                            + " shown position: "
+                            + mTopFullscreenOpaqueWindowState.getShownPositionLw());
                     Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
                             + " lp.flags=0x" + Integer.toHexString(fl));
                 }
@@ -4627,7 +4629,9 @@
                     if (mStatusBarController.setBarShowingLw(true)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
                     }
-                } else if (topIsFullscreen) {
+                } else if (topIsFullscreen
+                        && !mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)
+                        && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID)) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
                     if (mStatusBarController.setBarShowingLw(false)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -6714,6 +6718,15 @@
     }
 
     private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
+        final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+        final boolean freeformStackVisible =
+                mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID);
+        final boolean forceShowSystemBars = dockedStackVisible || freeformStackVisible;
+        // TODO(multi-window): Update to force opaque independently for status bar and nav bar.
+        // This will require refactoring the code to have separate vis flag for each bar so it can
+        // be adjusted independently.
+        final boolean forceOpaqueSystemBars = forceShowSystemBars;
+
         // apply translucent bar vis flags
         WindowState transWin = isStatusBarKeyguard() && !mHideLockScreen
                 ? mStatusBar
@@ -6736,7 +6749,8 @@
             vis = (vis & ~flags) | (oldVis & flags);
         }
 
-        if (!areTranslucentBarsAllowed() && transWin != mStatusBar) {
+        if ((!areTranslucentBarsAllowed() && transWin != mStatusBar)
+                || forceOpaqueSystemBars) {
             vis &= ~(View.NAVIGATION_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSLUCENT
                     | View.SYSTEM_UI_TRANSPARENT);
         }
@@ -6744,24 +6758,21 @@
         // update status bar
         boolean immersiveSticky =
                 (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
-        boolean hideStatusBarWM =
-                mTopFullscreenOpaqueWindowState != null &&
-                (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
+        final boolean hideStatusBarWM =
+                mTopFullscreenOpaqueWindowState != null
+                && (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
                         & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
-        boolean hideStatusBarSysui =
+        final boolean hideStatusBarSysui =
                 (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
-        boolean hideNavBarSysui =
+        final boolean hideNavBarSysui =
                 (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
 
-        boolean transientStatusBarAllowed =
-                mStatusBar != null && (
-                hideStatusBarWM
-                || (hideStatusBarSysui && immersiveSticky)
-                || statusBarHasFocus);
+        final boolean transientStatusBarAllowed = mStatusBar != null
+                && (statusBarHasFocus || (!forceShowSystemBars
+                        && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
 
-        boolean transientNavBarAllowed =
-                mNavigationBar != null &&
-                hideNavBarSysui && immersiveSticky;
+        final boolean transientNavBarAllowed = mNavigationBar != null
+                && !forceShowSystemBars && hideNavBarSysui && immersiveSticky;
 
         final long now = SystemClock.uptimeMillis();
         final boolean pendingPanic = mPendingPanicGestureUptime != 0
@@ -6774,11 +6785,11 @@
             mNavigationBarController.showTransient();
         }
 
-        boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
+        final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
                 && !transientStatusBarAllowed && hideStatusBarSysui;
-        boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
+        final boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
                 && !transientNavBarAllowed;
-        if (denyTransientStatus || denyTransientNav) {
+        if (denyTransientStatus || denyTransientNav || forceShowSystemBars) {
             // clear the clearable flags instead
             clearClearableFlagsLw();
             vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
index 4ae3154..b06fe58 100644
--- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -22,6 +22,7 @@
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.GestureDetector;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicy.PointerEventListener;
 import android.widget.OverScroller;
@@ -130,8 +131,7 @@
                 }
                 break;
             case MotionEvent.ACTION_HOVER_MOVE:
-                if (event.getPointerCount() == 1
-                        && event.getToolType(0) == MotionEvent.TOOL_TYPE_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 a0a31c0..6498dd9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -397,6 +397,10 @@
     // Set this to false to disable.
     private boolean mUserInactiveOverrideFromWindowManager;
 
+    // The next possible user activity timeout after being explicitly told the user is inactive.
+    // Set to -1 when not told the user is inactive since the last period spent dozing or asleep.
+    private long mOverriddenTimeout = -1;
+
     // The user activity timeout override from the window manager
     // to allow the current foreground activity to override the user activity timeout.
     // Use -1 to disable.
@@ -440,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];
 
@@ -1034,6 +1041,7 @@
 
             if (mUserInactiveOverrideFromWindowManager) {
                 mUserInactiveOverrideFromWindowManager = false;
+                mOverriddenTimeout = -1;
             }
 
             if (mWakefulness == WAKEFULNESS_ASLEEP
@@ -1251,12 +1259,28 @@
         }
     }
 
+    /**
+     * Logs the time the device would have spent awake before user activity timeout,
+     * had the system not been told the user was inactive.
+     */
+    private void logSleepTimeoutRecapturedLocked() {
+        final long now = SystemClock.uptimeMillis();
+        final long savedWakeTimeMs = mOverriddenTimeout - now;
+        if (savedWakeTimeMs >= 0) {
+            EventLog.writeEvent(EventLogTags.POWER_SOFT_SLEEP_REQUESTED, savedWakeTimeMs);
+            mOverriddenTimeout = -1;
+        }
+    }
+
     private void finishWakefulnessChangeIfNeededLocked() {
         if (mWakefulnessChanging && mDisplayReady) {
             if (mWakefulness == WAKEFULNESS_DOZING
                     && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
                 return; // wait until dream has enabled dozing
             }
+            if (mWakefulness == WAKEFULNESS_DOZING || mWakefulness == WAKEFULNESS_ASLEEP) {
+                logSleepTimeoutRecapturedLocked();
+            }
             mWakefulnessChanging = false;
             mNotifier.onWakefulnessChangeFinished();
         }
@@ -1579,10 +1603,11 @@
                 if (mUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM && userInactiveOverride) {
                     if ((mUserActivitySummary &
                             (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0) {
-                      // Device is being kept awake by recent user activity
-                      long savedWakeTimeMs = now - nextTimeout;
-                      EventLog.writeEvent(
-                              EventLogTags.POWER_SOFT_SLEEP_REQUESTED, savedWakeTimeMs);
+                        // Device is being kept awake by recent user activity
+                        if (nextTimeout >= now && mOverriddenTimeout == -1) {
+                            // Save when the next timeout would have occurred
+                            mOverriddenTimeout = nextTimeout;
+                        }
                     }
                     mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
                     nextTimeout = -1;
@@ -2270,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();
@@ -2346,7 +2377,7 @@
         }
     }
 
-    void setDeviceIdleModeInternal(boolean enabled) {
+    boolean setDeviceIdleModeInternal(boolean enabled) {
         synchronized (mLock) {
             if (mDeviceIdleMode != enabled) {
                 mDeviceIdleMode = enabled;
@@ -2356,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;
         }
     }
 
@@ -2649,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));
@@ -3286,6 +3330,16 @@
             }
         }
 
+        @Override // Binder call
+        public boolean isLightDeviceIdleMode() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return isLightDeviceIdleModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Reboots the device.
          *
@@ -3554,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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 66ae9ef..d713751 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -206,8 +206,8 @@
         sTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
         sTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
         sTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
-        sTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
-        sTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
+        sTempFloats[Matrix.MTRANS_X] = windowState.mShownPosition.x;
+        sTempFloats[Matrix.MTRANS_Y] = windowState.mShownPosition.y;
         sTempFloats[Matrix.MPERSP_0] = 0;
         sTempFloats[Matrix.MPERSP_1] = 0;
         sTempFloats[Matrix.MPERSP_2] = 1;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index cc51d20..dc34904 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,
@@ -456,10 +456,12 @@
         return -startPos / denom;
     }
 
-    private Animation createScaleUpAnimationLocked(
-            int transit, boolean enter, int appWidth, int appHeight) {
-        Animation a = null;
+    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+            Rect containingFrame) {
+        Animation a;
         getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
         if (enter) {
             // Entering app zooms out from the center of the initial rect.
             float scaleW = mTmpStartRect.width() / (float) appWidth;
@@ -543,14 +545,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 +565,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 +589,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);
@@ -740,10 +748,11 @@
      * activity that is leaving, and the activity that is entering.
      */
     Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
-            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
-            int taskId) {
+            int orientation, int transit, Rect containingFrame, Rect contentInsets,
+            @Nullable Rect surfaceInsets, boolean freeform, int taskId) {
         Animation a;
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
         getDefaultNextAppTransitionStartRect(mTmpStartRect);
         final int thumbWidthI = mTmpStartRect.width();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -756,30 +765,34 @@
 
         switch (thumbTransitState) {
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
-                if (resizedWindow) {
+                if (freeform) {
                     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);
+                        // In landscape, we scale the height and clip to the top/left square. We
+                        // only scale the part that is not covered by status bar and the nav bar.
+                        scale = thumbHeight / (appHeight - contentInsets.top
+                                - contentInsets.bottom);
                         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;
+                        // This removes the navigation bar from the first frame, so it better
+                        // matches the thumbnail. We need to do this explicitly in landscape,
+                        // because in portrait we already crop vertically.
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.bottom - contentInsets.bottom;
                     }
-                    // exclude top screen decor (status bar) region from the source clip.
-                    mTmpFromClipRect.top = contentInsets.top;
 
                     mNextAppTransitionInsets.set(contentInsets);
 
@@ -821,25 +834,28 @@
             }
             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);
+                    // In landscape, we scale the height and clip to the top/left square. We only
+                    // scale the part that is not covered by status bar and the nav bar.
+                    scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
                     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;
+                    // This removes the navigation bar from the last frame, so it better matches the
+                    // thumbnail. We need to do this explicitly in landscape, because in portrait we
+                    // already crop vertically.
+                    mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
                 }
-                // exclude top screen decor (status bar) region from the destination clip.
-                mTmpToClipRect.top = contentInsets.top;
 
                 mNextAppTransitionInsets.set(contentInsets);
 
@@ -940,8 +956,10 @@
      * This animation is created when we are doing a thumbnail transition, for the activity that is
      * leaving, and the activity that is entering.
      */
-    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
-            int appHeight, int transit, int taskId) {
+    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
+            int transit, int taskId) {
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
         Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
         Animation a;
         getDefaultNextAppTransitionStartRect(mTmpStartRect);
@@ -1003,18 +1021,22 @@
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
     }
 
-    private Animation createRelaunchAnimation(int appWidth, int appHeight,
-            Rect containingFrame) {
+    private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
         getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
         final int left = mTmpFromClipRect.left;
         final int top = mTmpFromClipRect.top;
         mTmpFromClipRect.offset(-left, -top);
-        mTmpToClipRect.set(0, 0, appWidth, appHeight);
+        // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+        mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
         AnimationSet set = new AnimationSet(true);
         float fromWidth = mTmpFromClipRect.width();
         float toWidth = mTmpToClipRect.width();
         float fromHeight = mTmpFromClipRect.height();
-        float toHeight = mTmpToClipRect.height();
+        // While the window might span the whole display, the actual content will be cropped to the
+        // system decoration frame, for example when the window is docked. We need to take into
+        // account the visible height when constructing the animation.
+        float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
+        int translateAdjustment = 0;
         if (fromWidth <= toWidth && fromHeight <= toHeight) {
             // The final window is larger in both dimensions than current window (e.g. we are
             // maximizing), so we can simply unclip the new window and there will be no disappearing
@@ -1024,12 +1046,17 @@
             // The disappearing window has one larger dimension. We need to apply scaling, so the
             // first frame of the entry animation matches the old window.
             set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+            // We might not be going exactly full screen, but instead be aligned under the status
+            // bar using cropping. We still need to account for the cropped part, which will also
+            // be scaled.
+            translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
         }
 
-        // We might not be going exactly full screen, but instead be aligned under the status bar.
-        // We need to take this into account when creating the translate animation.
+        // We animate the translation from the old position of the removed window, to the new
+        // position of the added window. The latter might not be full screen, for example docked for
+        // docked windows.
         TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
-                0, top - containingFrame.top, 0);
+                0, top - containingFrame.top - translateAdjustment, 0);
         set.addAnimation(translate);
         set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
         set.setZAdjustment(Animation.ZORDER_TOP);
@@ -1047,10 +1074,30 @@
                 && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL;
     }
 
+    /**
+     *
+     * @param frame These are the bounds of the window when it finishes the animation. This is where
+     *              the animation must usually finish in entrance animation, as the next frame will
+     *              display the window at these coordinates. In case of exit animation, this is
+     *              where the animation must start, as the frame before the animation is displaying
+     *              the window at these bounds.
+     * @param insets Knowing where the window will be positioned is not enough. Some parts of the
+     *               window might be obscured, usually by the system windows (status bar and
+     *               navigation bar) and we use content insets to convey that information. This
+     *               usually affects the animation aspects vertically, as the system decoration is
+     *               at the top and the bottom. For example when we animate from full screen to
+     *               recents, we want to exclude the covered parts, because they won't match the
+     *               thumbnail after the last frame is executed.
+     * @param surfaceInsets In rare situation the surface is larger than the content and we need to
+     *                      know about this to make the animation frames match. We currently use
+     *                      this for freeform windows, which have larger surfaces to display
+     *                      shadows. When we animate them from recents, we want to match the content
+     *                      to the recents thumbnail and hence need to account for the surface being
+     *                      bigger.
+     */
     Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
-            @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
-            boolean resizedWindow, int taskId) {
+            int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
+            boolean isVoiceInteraction, boolean freeform, int taskId) {
         Animation a;
         if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                 || transit == TRANSIT_TASK_OPEN
@@ -1073,7 +1120,7 @@
                     + " anim=" + a + " transit=" + appTransitionToString(transit)
                     + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
         } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
-            a = createRelaunchAnimation(appWidth, appHeight, containingFrame);
+            a = createRelaunchAnimation(frame, insets);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                     + " anim=" + a + " nextAppTransition=" + mNextAppTransition
@@ -1095,14 +1142,14 @@
                     + " transit=" + appTransitionToString(transit)
                     + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = createClipRevealAnimationLocked(transit, enter, appFrame);
+            a = createClipRevealAnimationLocked(transit, enter, frame);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                             + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
                             + " transit=" + appTransitionToString(transit)
                             + " Callers=" + Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
-            a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+            a = createScaleUpAnimationLocked(transit, enter, frame);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                     "applyAnimation:"
                     + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
@@ -1113,7 +1160,7 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    appWidth, appHeight, transit, taskId);
+                    frame, transit, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1127,8 +1174,8 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             a = createAspectScaledThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
-                    containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
+                    getThumbnailTransitionState(enter), orientation, transit, frame,
+                    insets, surfaceInsets, freeform, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2828cd0..a61e83f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -300,7 +300,8 @@
                 return false;
             }
 
-            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+            if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface
+                    || animating || mAppToken.startingDisplayed)
                     && animation != null) {
                 if (!animating) {
                     if (DEBUG_ANIM) Slog.v(TAG,
@@ -367,6 +368,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/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5a1ed58..943e3ea 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -52,6 +52,13 @@
 
     final boolean voiceInteraction;
 
+    // Whether the window has a saved surface from last pause, which can be
+    // used to start an entering animation earlier.
+    boolean mHasSavedSurface;
+
+    // Whether we're performing an entering animation with a saved surface.
+    boolean mAnimatingWithSavedSurface;
+
     Task mTask;
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -114,11 +121,14 @@
     // This application will have its window replaced due to relaunch. This allows window manager
     // to differentiate between simple removal of a window and replacement. In the latter case it
     // will preserve the old window until the new one is drawn.
-    boolean mReplacingWindow;
+    boolean mWillReplaceWindow;
     // If true, the replaced window was already requested to be removed.
     boolean mReplacingRemoveRequested;
     // Whether the replacement of the window should trigger app transition animation.
     boolean mAnimateReplacingWindow;
+    // If not null, the window that will be used to replace the old one. This is being set when
+    // the window is added and unset when this window reports its first draw.
+    WindowState mReplacingWindow;
 
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
@@ -257,10 +267,13 @@
         final int N = allAppWindows.size();
         for (int i=0; i<N; i++) {
             WindowState win = allAppWindows.get(i);
+            // If we're animating with a saved surface, we're already visible.
+            // Return true so that the alpha doesn't get cleared.
             if (!win.mAppFreezing
-                    && (win.mViewVisibility == View.VISIBLE ||
-                        (win.mWinAnimator.isAnimating() &&
-                                !service.mAppTransition.isTransitionSet()))
+                    && (win.mViewVisibility == View.VISIBLE
+                    || mAnimatingWithSavedSurface
+                    || (win.mWinAnimator.isAnimating() &&
+                            !service.mAppTransition.isTransitionSet()))
                     && !win.mDestroying && win.isDrawnLw()) {
                 return true;
             }
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/DimBehindController.java b/services/core/java/com/android/server/wm/DimBehindController.java
index c56af39..8870dd1 100644
--- a/services/core/java/com/android/server/wm/DimBehindController.java
+++ b/services/core/java/com/android/server/wm/DimBehindController.java
@@ -159,24 +159,32 @@
 
     boolean animateDimLayers() {
         int fullScreen = -1;
+        int fullScreenAndDimming = -1;
+        boolean result = false;
+
         for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
-            if (dimLayerUser.isFullscreen()) {
+            DimLayer.DimLayerUser user = mState.keyAt(i);
+            if (user.isFullscreen()) {
                 fullScreen = i;
                 if (mState.valueAt(i).continueDimming) {
-                    break;
+                    fullScreenAndDimming = i;
                 }
+            } else {
+                // We always want to animate the non fullscreen windows, they don't share their
+                // dim layers.
+                result |= animateDimLayers(user);
             }
         }
-        if (fullScreen != -1) {
-            return animateDimLayers(mState.keyAt(fullScreen));
-        } else {
-            boolean result = false;
-            for (int i = mState.size() - 1; i >= 0; i--) {
-                result |= animateDimLayers(mState.keyAt(i));
-            }
-            return result;
+        // For the shared, full screen dim layer, we prefer the animation that is causing it to
+        // appear.
+        if (fullScreenAndDimming != -1) {
+            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
+        } else if (fullScreen != -1) {
+            // If there is no animation for the full screen dim layer to appear, we can use any of
+            // the animators that will cause it to disappear.
+            result |= animateDimLayers(mState.keyAt(fullScreen));
         }
+        return result;
     }
 
     private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 8c479d8..bc31274 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -29,6 +29,10 @@
     private static final String TAG = "DimLayer";
     private static final boolean DEBUG = false;
 
+    public static final float RESIZING_HINT_ALPHA = 0.5f;
+
+    public static final int RESIZING_HINT_DURATION_MS = 0;
+
     /** Actual surface that dims */
     SurfaceControl mDimSurface;
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f13f350..39479c1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -191,6 +191,7 @@
     void updateDisplayInfo() {
         mDisplay.getDisplayInfo(mDisplayInfo);
         mDisplay.getMetrics(mDisplayMetrics);
+        mDividerControllerLocked.updateDisplayInfo();
         for (int i = mStacks.size() - 1; i >= 0; --i) {
             mStacks.get(i).updateDisplayInfo(null);
         }
@@ -266,9 +267,16 @@
             final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 final Task task = tasks.get(taskNdx);
-                task.getBounds(mTmpRect);
-                if (mTmpRect.contains(x, y)) {
-                    return task.mTaskId;
+                // We need to use the visible frame on the window for any touch-related tests.
+                // Can't use the task's bounds because the original task bounds might be adjusted
+                // to fit the content frame. For example, the presence of the IME adjusting the
+                // windows frames when the app window is the IME target.
+                final WindowState win = task.getTopAppMainWindow();
+                if (win != null) {
+                    win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
+                    if (mTmpRect.contains(x, y)) {
+                        return task.mTaskId;
+                    }
                 }
             }
         }
@@ -298,7 +306,7 @@
                 // might be adjusted to fit the content frame. (One example is when the
                 // task is put to top-left quadrant, the actual visible frame would not
                 // start at (0,0) after it's adjusted for the status bar.)
-                WindowState win = task.getTopAppMainWindow();
+                final WindowState win = task.getTopAppMainWindow();
                 if (win != null) {
                     win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
                     mTmpRect.inset(-delta, -delta);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 8c5d319..2d87123 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -24,17 +25,22 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static com.android.server.wm.DimLayer.RESIZING_HINT_ALPHA;
+import static com.android.server.wm.DimLayer.RESIZING_HINT_DURATION_MS;
+import static com.android.server.wm.TaskPositioner.SIDE_MARGIN_DIP;
 import static com.android.server.wm.TaskStack.DOCKED_BOTTOM;
-import static com.android.server.wm.TaskStack.DOCKED_INVALID;
 import static com.android.server.wm.TaskStack.DOCKED_LEFT;
 import static com.android.server.wm.TaskStack.DOCKED_RIGHT;
 import static com.android.server.wm.TaskStack.DOCKED_TOP;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.view.DisplayInfo;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -44,11 +50,15 @@
 /**
  * Controls showing and hiding of a docked stack divider on the display.
  */
-public class DockedStackDividerController implements View.OnTouchListener {
+public class DockedStackDividerController implements View.OnTouchListener, DimLayer.DimLayerUser {
     private static final String TAG = "DockedStackDivider";
     private final Context mContext;
     private final int mDividerWidth;
     private final DisplayContent mDisplayContent;
+    private final int mSideMargin;
+    private final DimLayer mDimLayer;
+    private int mDisplayWidth;
+    private int mDisplayHeight;
     private View mView;
     private Rect mTmpRect = new Rect();
     private Rect mLastResizeRect = new Rect();
@@ -57,22 +67,28 @@
     private TaskStack mTaskStack;
     private Rect mOriginalRect = new Rect();
     private int mDockSide;
-
+    private boolean mDimLayerVisible;
 
     DockedStackDividerController(Context context, DisplayContent displayContent) {
         mContext = context;
         mDisplayContent = displayContent;
+        updateDisplayInfo();
         mDividerWidth = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
+        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayContent.getDisplayMetrics());
+        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId());
     }
 
-    private void addDivider() {
+    private void addDivider(Configuration configuration) {
         View view = LayoutInflater.from(mContext).inflate(
                 com.android.internal.R.layout.docked_stack_divider, null);
         view.setOnTouchListener(this);
         WindowManagerGlobal manager = WindowManagerGlobal.getInstance();
+        final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
+        final int width = landscape ? mDividerWidth : MATCH_PARENT;
+        final int height = landscape ? MATCH_PARENT : mDividerWidth;
         WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                mDividerWidth, MATCH_PARENT, TYPE_DOCK_DIVIDER,
+                width, height, TYPE_DOCK_DIVIDER,
                 FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
                         | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
                 PixelFormat.OPAQUE);
@@ -92,10 +108,19 @@
         return mView != null;
     }
 
-    void update() {
+    void updateDisplayInfo() {
+        final DisplayInfo info = mDisplayContent.getDisplayInfo();
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+    }
+
+    void update(Configuration configuration, boolean forceUpdate) {
+        if (forceUpdate && mView != null) {
+            removeDivider();
+        }
         TaskStack stack = mDisplayContent.getDockedStackLocked();
         if (stack != null && mView == null) {
-            addDivider();
+            addDivider(configuration);
         } else if (stack == null && mView != null) {
             removeDivider();
         }
@@ -143,28 +168,184 @@
                 mStartY = (int) event.getRawY();
                 synchronized (mDisplayContent.mService.mWindowMap) {
                     mTaskStack = mDisplayContent.getDockedStackLocked();
-                    mTaskStack.getBounds(mOriginalRect);
-                    mDockSide = mTaskStack.getDockSide();
+                    if (mTaskStack != null) {
+                        mTaskStack.getBounds(mOriginalRect);
+                        mDockSide = mTaskStack.getDockSide();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
                 if (mTaskStack != null) {
-                    resizeStack(event);
+                    final int x = (int) event.getRawX();
+                    final int y = (int) event.getRawY();
+                    resizeStack(x, y);
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mTaskStack = null;
+                if (mTaskStack != null) {
+                    final int x = (int) event.getRawX();
+                    final int y = (int) event.getRawY();
+                    // At most one of these will be executed, the other one will exit early.
+                    maybeDismissTaskStack(x, y);
+                    maybeMaximizeTaskStack(x, y);
+                    mTaskStack = null;
+                }
+                setDimLayerVisible(false);
                 mDockSide = TaskStack.DOCKED_INVALID;
                 break;
         }
         return true;
     }
 
-    private void resizeStack(MotionEvent event) {
+    private void maybeMaximizeTaskStack(int x, int y) {
+        final int distance = distanceFromFullScreen(mDockSide, x, y);
+        if (distance == -1) {
+            Slog.wtf(TAG, "maybeMaximizeTaskStack: Unknown dock side=" + mDockSide);
+            return;
+        }
+        if (distance <= mSideMargin) {
+            try {
+                mDisplayContent.mService.mActivityManager.resizeStack(
+                        mTaskStack.mStackId, null, true);
+            } catch (RemoteException e) {
+                // This can't happen because we are in the same process.
+            }
+        }
+    }
+
+    private void maybeDismissTaskStack(int x, int y) {
+        final int distance = distanceFromDockSide(mDockSide, mOriginalRect, x, y);
+        if (distance == -1) {
+            Slog.wtf(TAG, "maybeDismissTaskStack: Unknown dock side=" + mDockSide);
+            return;
+        }
+        if (distance <= mSideMargin) {
+            try {
+                mDisplayContent.mService.mActivityManager.removeStack(mTaskStack.mStackId);
+            } catch (RemoteException e) {
+                // This can't happen because we are in the same process.
+            }
+        }
+    }
+
+    private void updateDimLayer(int x, int y) {
+        final int dismissDistance = distanceFromDockSide(mDockSide, mOriginalRect, x, y);
+        final int maximizeDistance = distanceFromFullScreen(mDockSide, x, y);
+        if (dismissDistance == -1 || maximizeDistance == -1) {
+            Slog.wtf(TAG, "updateDimLayer: Unknown dock side=" + mDockSide);
+            return;
+        }
+        if (dismissDistance <= mSideMargin && maximizeDistance <= mSideMargin) {
+            Slog.wtf(TAG, "Both dismiss and maximize distances would trigger dim layer.");
+            return;
+        }
+        if (dismissDistance <= mSideMargin) {
+            setDismissDimLayerVisible(x, y);
+        } else if (maximizeDistance <= mSideMargin) {
+            setMaximizeDimLayerVisible(x, y);
+        } else {
+            setDimLayerVisible(false);
+        }
+    }
+
+    /**
+     * Provides the distance from the point to the docked side of a rectangle.
+     *
+     * @return non negative distance or -1 on error
+     */
+    private static int distanceFromDockSide(int dockSide, Rect bounds, int x, int y) {
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                return x - bounds.left;
+            case DOCKED_TOP:
+                return y - bounds.top;
+            case DOCKED_RIGHT:
+                return bounds.right - x;
+            case DOCKED_BOTTOM:
+                return bounds.bottom - y;
+        }
+        return -1;
+    }
+
+    private int distanceFromFullScreen(int dockSide, int x, int y) {
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                return mDisplayWidth - x;
+            case DOCKED_TOP:
+                return mDisplayHeight - y;
+            case DOCKED_RIGHT:
+                return x;
+            case DOCKED_BOTTOM:
+                return y;
+        }
+        return -1;
+    }
+
+    private void setDismissDimLayerVisible(int x, int y) {
         mTmpRect.set(mOriginalRect);
-        final int deltaX = (int) event.getRawX() - mStartX;
-        final int deltaY = (int) event.getRawY() - mStartY;
+        switch (mDockSide) {
+            case DOCKED_LEFT:
+                mTmpRect.right = x;
+                break;
+            case DOCKED_TOP:
+                mTmpRect.bottom = y;
+                break;
+            case DOCKED_RIGHT:
+                mTmpRect.left = x;
+                break;
+            case DOCKED_BOTTOM:
+                mTmpRect.top = y;
+                break;
+            default:
+                Slog.wtf(TAG, "setDismissDimLayerVisible: Unknown dock side when setting dim "
+                        + "layer=" + mDockSide);
+                return;
+        }
+        mDimLayer.setBounds(mTmpRect);
+        setDimLayerVisible(true);
+    }
+
+    private void setMaximizeDimLayerVisible(int x, int y) {
+        mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
+        switch (mDockSide) {
+            case DOCKED_LEFT:
+                mTmpRect.left = x;
+                break;
+            case DOCKED_TOP:
+                mTmpRect.top = y;
+                break;
+            case DOCKED_RIGHT:
+                mTmpRect.right = x;
+                break;
+            case DOCKED_BOTTOM:
+                mTmpRect.top = y;
+                break;
+            default:
+                Slog.wtf(TAG, "setMaximizeDimLayerVisible: Unknown dock side when setting dim "
+                        + "layer=" + mDockSide);
+        }
+        mDimLayer.setBounds(mTmpRect);
+        setDimLayerVisible(true);
+    }
+
+    private void setDimLayerVisible(boolean visible) {
+        if (mDimLayerVisible == visible) {
+            return;
+        }
+        mDimLayerVisible = visible;
+        if (mDimLayerVisible) {
+            mDimLayer.show(mDisplayContent.mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
+                    RESIZING_HINT_DURATION_MS);
+        } else {
+            mDimLayer.hide();
+        }
+    }
+
+    private void resizeStack(int x, int y) {
+        mTmpRect.set(mOriginalRect);
+        final int deltaX = x - mStartX;
+        final int deltaY = y - mStartY;
         switch (mDockSide) {
             case DOCKED_LEFT:
                 mTmpRect.right += deltaX;
@@ -184,9 +365,11 @@
         }
         mLastResizeRect.set(mTmpRect);
         try {
-            mDisplayContent.mService.mActivityManager.resizeStack(DOCKED_STACK_ID, mTmpRect);
+            mDisplayContent.mService.mActivityManager.resizeStack(DOCKED_STACK_ID, mTmpRect, true);
         } catch (RemoteException e) {
+            // This can't happen because we are in the same process.
         }
+        updateDimLayer(x, y);
     }
 
     boolean isResizing() {
@@ -196,4 +379,24 @@
     int getWidthAdjustment() {
         return getWidth() / 2;
     }
+
+    @Override
+    public boolean isFullscreen() {
+        return false;
+    }
+
+    @Override
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    @Override
+    public void getBounds(Rect outBounds) {
+        // This dim layer user doesn't need this.
+    }
+
+    @Override
+    public String toShortString() {
+        return TAG;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3521682..f35ea66 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -77,7 +77,6 @@
         mSurfaceControl = surface;
         mFlags = flags;
         mLocalWin = localWin;
-        mUid = Binder.getCallingUid();
         mNotifiedWindows = new ArrayList<WindowState>();
     }
 
@@ -265,21 +264,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/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 7dd716e..283d498 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -18,8 +18,9 @@
 
 import java.io.PrintWriter;
 
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowStateAnimator.SurfaceTrace;
-
+import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
@@ -42,7 +43,15 @@
     static final boolean TWO_PHASE_ANIMATION = false;
     static final boolean USE_CUSTOM_BLACK_FRAME = false;
 
-    static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
+    /*
+     * Layers for screen rotation animation. We put these layers above
+     * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows.
+     */
+    static final int SCREEN_FREEZE_LAYER_BASE       = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
+    static final int SCREEN_FREEZE_LAYER_ENTER      = SCREEN_FREEZE_LAYER_BASE;
+    static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1;
+    static final int SCREEN_FREEZE_LAYER_EXIT       = SCREEN_FREEZE_LAYER_BASE + 2;
+    static final int SCREEN_FREEZE_LAYER_CUSTOM     = SCREEN_FREEZE_LAYER_BASE + 3;
 
     final Context mContext;
     final DisplayContent mDisplayContent;
@@ -265,7 +274,7 @@
                 SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                         SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
                 mSurfaceControl.setLayerStack(display.getLayerStack());
-                mSurfaceControl.setLayer(FREEZE_LAYER + 1);
+                mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
                 mSurfaceControl.setAlpha(0);
                 mSurfaceControl.show();
                 sur.destroy();
@@ -545,8 +554,8 @@
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
-                mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3,
-                        layerStack, false);
+                mCustomBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
                 mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -585,8 +594,8 @@
                             mOriginalWidth*2, mOriginalHeight*2);
                     inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
                 }
-                mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2,
-                        layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
                 mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -608,8 +617,8 @@
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
                         finalWidth*2, finalHeight*2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER,
-                        layerStack, false);
+                mEnteringBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c4600e0..4d11452 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -17,12 +17,15 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.HOME_STACK_ID;
+import static android.app.ActivityManager.PINNED_STACK_ID;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -187,7 +190,8 @@
                 bounds = mTmpRect;
                 mFullscreen = true;
             } else {
-                if (mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID || bounds.isEmpty()) {
+                if ((mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+                        && mStack.mStackId != PINNED_STACK_ID) || bounds.isEmpty()) {
                     // ensure bounds are entirely within the display rect
                     if (!bounds.intersect(mTmpRect)) {
                         // Can't set bounds outside the containing display...Sorry!
@@ -241,8 +245,7 @@
     private boolean useCurrentBounds() {
         final DisplayContent displayContent = mStack.getDisplayContent();
         if (mFullscreen
-                || mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID
-                || mStack.mStackId == DOCKED_STACK_ID
+                || mStack.allowTaskResize()
                 || displayContent == null
                 || displayContent.getDockedStackLocked() != null) {
             return true;
@@ -322,10 +325,18 @@
         return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
     }
 
+    boolean inHomeStack() {
+        return mStack != null && mStack.mStackId == HOME_STACK_ID;
+    }
+
     boolean inFreeformWorkspace() {
         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/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 207da47..227b3f0 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -23,8 +23,11 @@
 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static com.android.server.wm.DimLayer.RESIZING_HINT_ALPHA;
+import static com.android.server.wm.DimLayer.RESIZING_HINT_DURATION_MS;
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
 
@@ -60,7 +63,7 @@
 
     // The margin the pointer position has to be within the side of the screen to be
     // considered at the side of the screen.
-    private static final int SIDE_MARGIN_DIP = 100;
+    static final int SIDE_MARGIN_DIP = 100;
 
     @IntDef(flag = true,
             value = {
@@ -246,7 +249,7 @@
                 mDisplay.getDisplayId());
         mDragWindowHandle.name = TAG;
         mDragWindowHandle.inputChannel = mServerChannel;
-        mDragWindowHandle.layer = getDragLayerLocked();
+        mDragWindowHandle.layer = mService.getDragLayerLocked();
         mDragWindowHandle.layoutParamsFlags = 0;
         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
         mDragWindowHandle.dispatchingTimeoutNanos =
@@ -279,9 +282,9 @@
         mService.pauseRotationLocked();
 
         mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId());
-        mSideMargin = mService.dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
-        mMinVisibleWidth = mService.dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
-        mMinVisibleHeight = mService.dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
+        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
+        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
+        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
 
         mDragEnded = false;
     }
@@ -455,7 +458,8 @@
         }
 
         mDimLayer.setBounds(mTmpRect);
-        mDimLayer.show(getDragLayerLocked(), 0.5f, 0);
+        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
+                RESIZING_HINT_DURATION_MS);
     }
 
     @Override /** {@link DimLayer.DimLayerUser} */
@@ -477,10 +481,4 @@
     public String toShortString() {
         return TAG;
     }
-
-    private int getDragLayerLocked() {
-        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
-                * WindowManagerService.TYPE_LAYER_MULTIPLIER
-                + WindowManagerService.TYPE_LAYER_OFFSET;
-    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a630993..9b3d478 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -16,7 +16,13 @@
 
 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 android.app.ActivityManager.PINNED_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.TAG;
@@ -29,8 +35,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;
@@ -118,7 +124,8 @@
 
     boolean allowTaskResize() {
         return mStackId == FREEFORM_WORKSPACE_STACK_ID
-                || mStackId == DOCKED_STACK_ID;
+                || mStackId == DOCKED_STACK_ID
+                || mStackId == PINNED_STACK_ID;
     }
 
     /**
@@ -177,9 +184,9 @@
 
         if (mDisplayContent != null) {
             mDisplayContent.mDimBehindController.updateDimLayer(this);
+            mAnimationBackgroundSurface.setBounds(bounds);
         }
 
-        mAnimationBackgroundSurface.setBounds(bounds);
         mBounds.set(bounds);
         mRotation = rotation;
         return true;
@@ -197,6 +204,7 @@
     private boolean useCurrentBounds() {
         if (mFullscreen
                 || mStackId == DOCKED_STACK_ID
+                || mStackId == PINNED_STACK_ID
                 || mDisplayContent == null
                 || mDisplayContent.getDockedStackLocked() != null) {
             return true;
@@ -237,8 +245,8 @@
                     // Post message to inform activity manager of the bounds change simulating
                     // a one-way call. We do this to prevent a deadlock between window manager
                     // lock and activity manager lock been held.
-                    mService.mH.sendMessage(
-                            mService.mH.obtainMessage(RESIZE_STACK, mStackId, -1, mBounds));
+                    mService.mH.sendMessage(mService.mH.obtainMessage(
+                            RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mBounds));
                 }
             }
         }
@@ -388,7 +396,7 @@
 
         Rect bounds = null;
         final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        if (mStackId == DOCKED_STACK_ID || (dockedStack != null
+        if (mStackId == DOCKED_STACK_ID || (dockedStack != null && mStackId != PINNED_STACK_ID
                 && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
             // The existence of a docked stack affects the size of any static stack created since
             // the docked stack occupies a dedicated region on screen.
@@ -398,8 +406,11 @@
             if (dockedStack != null) {
                 dockedStack.getRawBounds(mTmpRect2);
             }
-            getInitialDockedStackBounds(mTmpRect, bounds, mStackId, mTmpRect2,
-                    mDisplayContent.mDividerControllerLocked.getWidthAdjustment());
+            final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode
+                    == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+            getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
+                    mDisplayContent.mDividerControllerLocked.getWidthAdjustment(),
+                    dockedOnTopOrLeft);
         }
 
         updateDisplayInfo(bounds);
@@ -412,28 +423,68 @@
         }
     }
 
+    void getStackDockedModeBoundsLocked(Rect outBounds) {
+        if (mStackId == DOCKED_STACK_ID
+                || mStackId == PINNED_STACK_ID
+                || mStackId > LAST_STATIC_STACK_ID
+                || mDisplayContent == null) {
+            outBounds.set(mBounds);
+            return;
+        }
+
+        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();
+        if (dockedSide == DOCKED_INVALID) {
+            // Not sure how you got here...Only thing we can do is return current bounds.
+            Slog.e(TAG, "Failed to get valid docked side for docked stack=" + dockedStack);
+            outBounds.set(mBounds);
+            return;
+        }
+
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        dockedStack.getRawBounds(mTmpRect2);
+        final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
+        getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2,
+                mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft);
+
+    }
+
     /**
-     * Outputs the initial bounds a stack should be given the presence of a docked stack on the
-     * display.
+     * Outputs the bounds a stack should be given the presence of a docked stack on the display.
      * @param displayRect The bounds of the display the docked stack is on.
      * @param outBounds Output bounds that should be used for the stack.
      * @param stackId Id of stack we are calculating the bounds for.
      * @param dockedBounds Bounds of the docked stack.
-     * @param adjustment
+     * @param adjustment Additional adjustment to make to the output bounds close to the side of the
+     *                   dock.
+     * @param dockOntopOrLeft If the docked stack is on the top or left side of the screen.
      */
-    private static void getInitialDockedStackBounds(
-            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment) {
+    private static void getStackDockedModeBounds(
+            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment,
+            boolean dockOntopOrLeft) {
         final boolean dockedStack = stackId == DOCKED_STACK_ID;
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
-        final boolean topOrLeftCreateMode =
-                WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 
         outBounds.set(displayRect);
         if (dockedStack) {
             // The initial bounds of the docked stack when it is created half the screen space and
             // its bounds can be adjusted after that. The bounds of all other stacks are adjusted
             // to occupy whatever screen space the docked stack isn't occupying.
-            if (topOrLeftCreateMode) {
+            if (dockOntopOrLeft) {
                 if (splitHorizontally) {
                     outBounds.right = displayRect.centerX() - adjustment;
                 } else {
@@ -450,7 +501,7 @@
         }
 
         // Other stacks occupy whatever space is left by the docked stack.
-        if (!topOrLeftCreateMode) {
+        if (!dockOntopOrLeft) {
             if (splitHorizontally) {
                 outBounds.right = dockedBounds.left - adjustment;
             } else {
@@ -476,19 +527,22 @@
         final Rect bounds = new Rect();
         mDisplayContent.getLogicalDisplayRect(bounds);
         if (!fullscreen) {
-            getInitialDockedStackBounds(bounds, bounds, FULLSCREEN_WORKSPACE_STACK_ID, dockedBounds,
-                    mDisplayContent.mDividerControllerLocked.getWidth());
+            final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode
+                    == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+            getStackDockedModeBounds(bounds, bounds, FULLSCREEN_WORKSPACE_STACK_ID, dockedBounds,
+                    mDisplayContent.mDividerControllerLocked.getWidth(), dockedOnTopOrLeft);
         }
 
         final int count = mService.mStackIdToStack.size();
         for (int i = 0; i < count; i++) {
             final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
             final int otherStackId = otherStack.mStackId;
-            if (otherStackId != DOCKED_STACK_ID
+            if (otherStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID
                     && otherStackId >= FIRST_STATIC_STACK_ID
                     && otherStackId <= LAST_STATIC_STACK_ID) {
                 mService.mH.sendMessage(
-                        mService.mH.obtainMessage(RESIZE_STACK, otherStackId, -1, bounds));
+                        mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
+                                1 /*allowResizeInDockedMode*/, bounds));
             }
         }
     }
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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1a946b2..7f2f2cd 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -419,7 +419,7 @@
                     winAnimator.computeShownFrameLocked();
                     // No need to lay out the windows - we can just set the wallpaper position
                     // directly.
-                    winAnimator.setWallpaperOffset(wallpaper.mShownFrame);
+                    winAnimator.setWallpaperOffset(wallpaper.mShownPosition);
                     // We only want to be synchronous with one wallpaper.
                     sync = false;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index ab1bf20..be86e2f 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -272,25 +272,6 @@
                 winAnimator.mWasAnimating = nowAnimating;
                 mAnimating |= nowAnimating;
 
-                boolean appWindowAnimating = winAnimator.mAppAnimator != null
-                        && winAnimator.mAppAnimator.animating;
-                boolean wasAppWindowAnimating = winAnimator.mAppAnimator != null
-                        && winAnimator.mAppAnimator.wasAnimating;
-                boolean anyAnimating = appWindowAnimating || nowAnimating;
-                boolean anyWasAnimating = wasAppWindowAnimating || wasAnimating;
-
-                try {
-                    if (anyAnimating && !anyWasAnimating) {
-                        win.mClient.onAnimationStarted(winAnimator.mAnimatingMove ? -1
-                                : winAnimator.mKeyguardGoingAwayAnimation ? 1
-                                : 0);
-                    } else if (!anyAnimating && anyWasAnimating) {
-                        win.mClient.onAnimationStopped();
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to dispatch window animation state change.", e);
-                }
-
                 if (WindowManagerService.DEBUG_WALLPAPER) {
                     Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
                             ", nowAnimating=" + nowAnimating);
@@ -432,7 +413,7 @@
 
             final AppWindowToken atoken = win.mAppToken;
             if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
-                if (atoken == null || atoken.allDrawn) {
+                if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) {
                     if (winAnimator.performShowLocked()) {
                         setPendingLayoutChanges(displayId,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
@@ -753,6 +734,9 @@
         if (!mAnimating && wasAnimating) {
             mWindowPlacerLocked.requestTraversal();
         }
+
+        mService.destroyPreservedSurfaceLocked();
+
         if (WindowManagerService.DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
                 + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c4201d9..8a2442a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -18,6 +18,9 @@
 
 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.FREEFORM_WORKSPACE_STACK_ID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -223,7 +226,6 @@
     static final boolean HIDE_STACK_CRAWLS = true;
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
-
     static final boolean PROFILE_ORIENTATION = false;
     static final boolean localLOGV = DEBUG;
 
@@ -398,6 +400,13 @@
     final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
 
     /**
+     * Windows with a preserved surface waiting to be destroyed. These windows
+     * are going through a surface change. We keep the old surface around until
+     * the first frame on the new surface finishes drawing.
+     */
+    final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
+
+    /**
      * Windows that have lost input focus and are waiting for the new
      * focus window to be displayed before they are told about this.
      */
@@ -467,6 +476,11 @@
 
     static int sDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 
+    int getDragLayerLocked() {
+        return mPolicy.windowTypeToLayerLw(LayoutParams.TYPE_DRAG) * TYPE_LAYER_MULTIPLIER
+                + TYPE_LAYER_OFFSET;
+    }
+
     class RotationWatcher {
         IRotationWatcher watcher;
         IBinder.DeathRecipient deathRecipient;
@@ -1005,7 +1019,7 @@
      * @param displayContent The display we are interested in.
      * @return List of windows from token that are on displayContent.
      */
-    WindowList getTokenWindowsOnDisplay(WindowToken token, DisplayContent displayContent) {
+    private WindowList getTokenWindowsOnDisplay(WindowToken token, DisplayContent displayContent) {
         final WindowList windowList = new WindowList();
         final int count = token.windows.size();
         for (int i = 0; i < count; i++) {
@@ -1039,55 +1053,19 @@
     }
 
     private int addAppWindowToListLocked(final WindowState win) {
-        final IWindow client = win.mClient;
-        final WindowToken token = win.mToken;
         final DisplayContent displayContent = win.getDisplayContent();
         if (displayContent == null) {
             // It doesn't matter this display is going away.
             return 0;
         }
+        final IWindow client = win.mClient;
+        final WindowToken token = win.mToken;
 
-        final WindowList windows = win.getWindowList();
-        final int N = windows.size();
+        final WindowList windows = displayContent.getWindowList();
         WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
         int tokenWindowsPos = 0;
-        int windowListPos = tokenWindowList.size();
         if (!tokenWindowList.isEmpty()) {
-            // If this application has existing windows, we
-            // simply place the new window on top of them... but
-            // keep the starting window on top.
-            if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
-                // Base windows go behind everything else.
-                WindowState lowestWindow = tokenWindowList.get(0);
-                placeWindowBefore(lowestWindow, win);
-                tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
-            } else {
-                AppWindowToken atoken = win.mAppToken;
-                WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
-                if (atoken != null && lastWindow == atoken.startingWindow) {
-                    placeWindowBefore(lastWindow, win);
-                    tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
-                } else {
-                    int newIdx = findIdxBasedOnAppTokens(win);
-                    //there is a window above this one associated with the same
-                    //apptoken note that the window could be a floating window
-                    //that was created later or a window at the top of the list of
-                    //windows associated with this token.
-                    if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
-                            "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " +
-                            N);
-                    windows.add(newIdx + 1, win);
-                    if (newIdx < 0) {
-                        // No window from token found on win's display.
-                        tokenWindowsPos = 0;
-                    } else {
-                        tokenWindowsPos = indexOfWinInWindowList(
-                                windows.get(newIdx), token.windows) + 1;
-                    }
-                    mWindowsChanged = true;
-                }
-            }
-            return tokenWindowsPos;
+            return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList);
         }
 
         // No windows from this token on this display
@@ -1189,19 +1167,65 @@
         // Just search for the start of this layer.
         final int myLayer = win.mBaseLayer;
         int i;
-        for (i = N - 1; i >= 0; --i) {
+        for (i = windows.size() - 1; i >= 0; --i) {
             WindowState w = windows.get(i);
-            if (w.mBaseLayer <= myLayer) {
+            // Dock divider shares the base layer with application windows, but we want to always
+            // keep it above the application windows. The sharing of the base layer is intended
+            // for window animations, which need to be above the dock divider for the duration
+            // of the animation.
+            if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
                 break;
             }
         }
         if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
-                "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + N);
+                "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
+                        + windows.size());
         windows.add(i + 1, win);
         mWindowsChanged = true;
         return tokenWindowsPos;
     }
 
+    private int addAppWindowToTokenListLocked(WindowState win, WindowToken token,
+            WindowList windows, WindowList tokenWindowList) {
+        int tokenWindowsPos;
+        // If this application has existing windows, we
+        // simply place the new window on top of them... but
+        // keep the starting window on top.
+        if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
+            // Base windows go behind everything else.
+            WindowState lowestWindow = tokenWindowList.get(0);
+            placeWindowBefore(lowestWindow, win);
+            tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
+        } else {
+            AppWindowToken atoken = win.mAppToken;
+            final int windowListPos = tokenWindowList.size();
+            WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
+            if (atoken != null && lastWindow == atoken.startingWindow) {
+                placeWindowBefore(lastWindow, win);
+                tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
+            } else {
+                int newIdx = findIdxBasedOnAppTokens(win);
+                //there is a window above this one associated with the same
+                //apptoken note that the window could be a floating window
+                //that was created later or a window at the top of the list of
+                //windows associated with this token.
+                if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                        "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
+                                + windows.size());
+                windows.add(newIdx + 1, win);
+                if (newIdx < 0) {
+                    // No window from token found on win's display.
+                    tokenWindowsPos = 0;
+                } else {
+                    tokenWindowsPos = indexOfWinInWindowList(
+                            windows.get(newIdx), token.windows) + 1;
+                }
+                mWindowsChanged = true;
+            }
+        }
+        return tokenWindowsPos;
+    }
+
     private void addFreeWindowToListLocked(final WindowState win) {
         final WindowList windows = win.getWindowList();
 
@@ -1303,21 +1327,32 @@
             addAttachedWindowToListLocked(win, addToToken);
         }
 
-        if (win.mAppToken != null && addToToken) {
-            win.mAppToken.allAppWindows.add(win);
+        final AppWindowToken appToken = win.mAppToken;
+        if (appToken != null) {
+            if (addToToken) {
+                appToken.allAppWindows.add(win);
+            }
+            if (appToken.mWillReplaceWindow) {
+                appToken.mReplacingWindow = win;
+            }
         }
     }
 
     static boolean canBeImeTarget(WindowState w) {
         final int fl = w.mAttrs.flags
                 & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
+        final int type = w.mAttrs.type;
+        // The dock divider has to sit above the application windows and so does the IME. IME also
+        // needs to sit above the dock divider, so it doesn't get cut in half. We make the dock
+        // divider be a target for IME, so this relationship can occur naturally.
         if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)
-                || w.mAttrs.type == TYPE_APPLICATION_STARTING) {
+                || type == TYPE_APPLICATION_STARTING || type == TYPE_DOCK_DIVIDER) {
             if (DEBUG_INPUT_METHOD) {
                 Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
                 if (!w.isVisibleOrAdding()) {
                     Slog.i(TAG, "  mSurface=" + w.mWinAnimator.mSurfaceControl
-                            + " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility
+                            + " relayoutCalled=" + w.mRelayoutCalled
+                            + " viewVis=" + w.mViewVisibility
                             + " policyVis=" + w.mPolicyVisibility
                             + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
                             + " attachHid=" + w.mAttachedHidden
@@ -2024,7 +2059,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -2128,6 +2163,8 @@
                 + " isAnimating=" + win.mWinAnimator.isAnimating()
                 + " app-animation="
                 + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+                + " mWillReplaceWindow="
+                + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
                 + " inPendingTransaction="
                 + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
                 + " mDisplayFrozen=" + mDisplayFrozen);
@@ -2139,8 +2176,8 @@
         // animation wouldn't be seen.
         if (win.mHasSurface && okToDisplay()) {
             final AppWindowToken appToken = win.mAppToken;
-            if (appToken != null && appToken.mReplacingWindow) {
-                // This window is going to be replaced. We need to kepp it around until the new one
+            if (appToken != null && appToken.mWillReplaceWindow) {
+                // This window is going to be replaced. We need to keep it around until the new one
                 // gets added, then we will get rid of this one.
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is "
                         + "added");
@@ -2442,7 +2479,6 @@
         boolean configChanged;
         boolean surfaceChanged = false;
         boolean dragResizing = false;
-        boolean animating;
         boolean hasStatusBarPermission =
                 mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
                         == PackageManager.PERMISSION_GRANTED;
@@ -2593,16 +2629,10 @@
 
                 // If we're starting a drag-resize, we'll be changing the surface size as well as
                 // notifying the client to render to with an offset from the surface's top-left.
-                // Do a screen freeze, and keep the old surface until the the first frame drawn to
-                // the new surface comes back, so that we avoid a flash due to mismatching surface
-                // setups on window manager side and client side.
                 if (win.isDragResizeChanged()) {
                     win.setDragResizing();
                     if (win.mHasSurface) {
-                        winAnimator.mDestroyPendingSurfaceUponRedraw = true;
-                        winAnimator.mSurfaceDestroyDeferred = true;
-                        winAnimator.destroySurfaceLocked();
-                        startFreezingDisplayLocked(false, 0, 0);
+                        winAnimator.preserveSurfaceLocked();
                         toBeDisplayed = true;
                     }
                 }
@@ -2657,9 +2687,16 @@
                 if (winAnimator.mSurfaceControl != null) {
                     if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
                             + ": mExiting=" + win.mExiting);
+                    // If we are using a saved surface to do enter animation, just let the
+                    // animation run and don't destroy the surface. This could happen when
+                    // the app sets visibility to invisible for the first time after resume,
+                    // or when the user exits immediately after a resume. In both cases, we
+                    // don't want to destroy the saved surface.
+                    final boolean isAnimatingWithSavedSurface =
+                            win.mAppToken != null && win.mAppToken.mAnimatingWithSavedSurface;
                     // If we are not currently running the exit animation, we
                     // need to see about starting one.
-                    if (!win.mExiting) {
+                    if (!win.mExiting && !isAnimatingWithSavedSurface) {
                         surfaceChanged = true;
                         // Try starting an animation; if there isn't one, we
                         // can destroy the surface right away.
@@ -2811,12 +2848,19 @@
         try {
             synchronized (mWindowMap) {
                 WindowState win = windowForClientLocked(session, client, false);
-                if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win);
+                if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState="
+                        + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                 if (win != null && win.mWinAnimator.finishDrawingLocked()) {
                     if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                         getDefaultDisplayContentLocked().pendingLayoutChanges |=
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                     }
+                    if (win.mAppToken != null) {
+                        // App has drawn something to its windows, we're no longer animating with
+                        // the saved surfaces. If the user exits now, we only want to save again
+                        // if allDrawn is true.
+                        win.mAppToken.mAnimatingWithSavedSurface = false;
+                    }
                     win.setDisplayLayoutNeeded();
                     mWindowPlacerLocked.requestTraversal();
                 }
@@ -2840,44 +2884,44 @@
                     "applyAnimation: atoken=" + atoken);
 
             // Determine the visible rect to calculate the thumbnail clip
-            WindowState win = atoken.findMainWindow();
-            Rect containingFrame = new Rect(0, 0, width, height);
-            Rect contentInsets = new Rect();
-            Rect appFrame = new Rect(0, 0, width, height);
+            final WindowState win = atoken.findMainWindow();
+            final Rect frame = new Rect(0, 0, width, height);
+            final Rect insets = new Rect();
             Rect surfaceInsets = null;
             final boolean fullscreen = win != null && win.isFullscreen(width, height);
             final boolean freeform = win != null && win.inFreeformWorkspace();
+            final boolean docked = win != null && win.inDockedWorkspace();
             if (win != null) {
                 // Containing frame will usually cover the whole screen, including dialog windows.
                 // For freeform workspace windows it will not cover the whole screen and it also
                 // won't exactly match the final freeform window frame (e.g. when overlapping with
                 // the status bar). In that case we need to use the final frame.
                 if (freeform) {
-                    containingFrame.set(win.mFrame);
+                    frame.set(win.mFrame);
                 } else {
-                    containingFrame.set(win.mContainingFrame);
+                    frame.set(win.mContainingFrame);
                 }
                 surfaceInsets = win.getAttrs().surfaceInsets;
-                if (fullscreen) {
+                if (fullscreen || docked) {
                     // For fullscreen windows use the window frames and insets to set the thumbnail
-                    // clip. For none-fullscreen windows we use the app display region so the clip
+                    // clip. For non-fullscreen windows we use the app display region so the clip
                     // isn't affected by the window insets.
-                    contentInsets.set(win.mContentInsets);
-                    appFrame.set(win.mFrame);
+                    insets.set(win.mContentInsets);
                 }
             }
 
-            final int containingWidth = containingFrame.width();
-            final int containingHeight = containingFrame.height();
             if (atoken.mLaunchTaskBehind) {
                 // Differentiate the two animations. This one which is briefly on the screen
                 // gets the !enter animation, and the other activity which remains on the
                 // screen gets the enter animation. Both appear in the mOpeningApps set.
                 enter = false;
             }
-            Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
-                    containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
-                    surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition."
+                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+            Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+                    mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
+                    freeform, atoken.mTask.mTaskId);
             if (a != null) {
                 if (DEBUG_ANIM) {
                     RuntimeException e = null;
@@ -2887,6 +2931,8 @@
                     }
                     Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
                 }
+                final int containingWidth = frame.width();
+                final int containingHeight = frame.height();
                 atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
                         mAppTransition.canSkipFirstFrame());
             }
@@ -3180,9 +3226,9 @@
 
     public int getOrientationLocked() {
         if (mDisplayFrozen) {
-            if (mLastWindowForcedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
-                if (DEBUG_ORIENTATION) Slog.v(TAG, "Display is frozen, return "
-                        + mLastWindowForcedOrientation);
+            if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Display is frozen, return " + mLastWindowForcedOrientation);
                 // If the display is frozen, some activities may be in the middle
                 // of restarting, and thus have removed their old window.  If the
                 // window has the flag to hide the lock screen, then the lock screen
@@ -3204,8 +3250,7 @@
                     continue;
                 }
                 int req = win.mAttrs.screenOrientation;
-                if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
-                        (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
+                if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND) {
                     continue;
                 }
 
@@ -3215,7 +3260,7 @@
                 }
                 return (mLastWindowForcedOrientation = req);
             }
-            mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+            mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
             if (mPolicy.isKeyguardLocked()) {
                 // The screen is locked and no top system window is requesting an orientation.
@@ -3226,7 +3271,7 @@
                         null : winShowWhenLocked.mAppToken;
                 if (appShowWhenLocked != null) {
                     int req = appShowWhenLocked.requestedOrientation;
-                    if (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+                    if (req == SCREEN_ORIENTATION_BEHIND) {
                         req = mLastKeyguardForcedOrientation;
                     }
                     if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + appShowWhenLocked
@@ -3239,8 +3284,21 @@
             }
         }
 
+        final TaskStack dockedStack = mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack freeformStack = mStackIdToStack.get(FREEFORM_WORKSPACE_STACK_ID);
+        if ((dockedStack != null && dockedStack.isVisibleLocked())
+                || (freeformStack != null && freeformStack.isVisibleLocked())) {
+            // We don't let app affect the system orientation when in freeform or docked mode since
+            // they don't occupy the entire display and their request can conflict with other apps.
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+
         // Top system windows are not requesting an orientation. Start searching from apps.
-        int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        return getAppSpecifiedOrientation();
+    }
+
+    private int getAppSpecifiedOrientation() {
+        int lastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
         boolean findingBehind = false;
         boolean lastFullscreen = false;
         // TODO: Multi window.
@@ -3257,19 +3315,16 @@
                 // if we're about to tear down this window and not seek for
                 // the behind activity, don't use it for orientation
                 if (!findingBehind && !atoken.hidden && atoken.hiddenRequested) {
-                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken
-                            + " -- going to hide");
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Skipping " + atoken + " -- going to hide");
                     continue;
                 }
 
                 if (tokenNdx == firstToken) {
-                    // If we have hit a new Task, and the bottom
-                    // of the previous group didn't explicitly say to use
-                    // the orientation behind it, and the last app was
-                    // full screen, then we'll stick with the
-                    // user's orientation.
-                    if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND
-                            && lastFullscreen) {
+                    // If we have hit a new Task, and the bottom of the previous group didn't
+                    // explicitly say to use the orientation behind it, and the last app was
+                    // full screen, then we'll stick with the user's orientation.
+                    if (lastOrientation != SCREEN_ORIENTATION_BEHIND && lastFullscreen) {
                         if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
                                 + " -- end of group, return " + lastOrientation);
                         return lastOrientation;
@@ -3278,8 +3333,8 @@
 
                 // We ignore any hidden applications on the top.
                 if (atoken.hiddenRequested) {
-                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken
-                            + " -- hidden on top");
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Skipping " + atoken + " -- hidden on top");
                     continue;
                 }
 
@@ -3293,23 +3348,22 @@
                 // to use the orientation behind it, then just take whatever
                 // orientation it has and ignores whatever is under it.
                 lastFullscreen = atoken.appFullscreen;
-                if (lastFullscreen && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
-                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
-                            + " -- full screen, return " + or);
+                if (lastFullscreen && or != SCREEN_ORIENTATION_BEHIND) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Done at " + atoken + " -- full screen, return " + or);
                     return or;
                 }
                 // If this application has requested an explicit orientation, then use it.
-                if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
-                        && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
-                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
-                            + " -- explicitly set, return " + or);
+                if (or != SCREEN_ORIENTATION_UNSPECIFIED && or != SCREEN_ORIENTATION_BEHIND) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Done at " + atoken + " -- explicitly set, return " + or);
                     return or;
                 }
-                findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
+                findingBehind |= (or == SCREEN_ORIENTATION_BEHIND);
             }
         }
-        if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation, return "
-                + mForcedAppOrientation);
+        if (DEBUG_ORIENTATION) Slog.v(TAG,
+                "No app is requesting an orientation, return " + mForcedAppOrientation);
         // The next app has not been requested to be visible, so we keep the current orientation
         // to prevent freezing/unfreezing the display too early.
         return mForcedAppOrientation;
@@ -3412,7 +3466,6 @@
         }
     }
 
-    @Override
     public void setNewConfiguration(Configuration config) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "setNewConfiguration()")) {
@@ -3420,12 +3473,20 @@
         }
 
         synchronized(mWindowMap) {
+            final boolean orientationChanged = mCurConfiguration.orientation != config.orientation;
             mCurConfiguration = new Configuration(config);
             if (mWaitingForConfig) {
                 mWaitingForConfig = false;
                 mLastFinishedFreezeSource = "new-config";
             }
             mWindowPlacerLocked.performSurfacePlacement();
+            if (orientationChanged) {
+                for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
+                    DisplayContent content = mDisplayContents.valueAt(i);
+                    Message.obtain(mH, H.UPDATE_DOCKED_STACK_DIVIDER, H.DOCK_DIVIDER_FORCE_UPDATE,
+                            H.UNUSED, content).sendToTarget();
+                }
+            }
         }
     }
 
@@ -3870,7 +3931,7 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
-                (visible && wtoken.mReplacingWindow)) {
+                (visible && wtoken.mWillReplaceWindow)) {
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -4580,6 +4641,17 @@
         }
     }
 
+    public void getStackDockedModeBounds(int stackId, Rect bounds) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(stackId);
+            if (stack != null) {
+                stack.getStackDockedModeBoundsLocked(bounds);
+                return;
+            }
+            bounds.setEmpty();
+        }
+    }
+
     public void getStackBounds(int stackId, Rect bounds) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);
@@ -5559,10 +5631,13 @@
         int retryCount = 0;
         WindowState appWin = null;
 
-        final boolean appIsImTarget = mInputMethodTarget != null
-                && mInputMethodTarget.mAppToken != null
-                && mInputMethodTarget.mAppToken.appToken != null
-                && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+        boolean appIsImTarget;
+        synchronized(mWindowMap) {
+            appIsImTarget = mInputMethodTarget != null
+                    && mInputMethodTarget.mAppToken != null
+                    && mInputMethodTarget.mAppToken.appToken != null
+                    && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+        }
 
         final int aboveAppLayer = (mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) + 1)
                 * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -6835,6 +6910,8 @@
         WindowState win = null;
         synchronized (mWindowMap) {
             win = windowForClientLocked(null, window, false);
+            // win shouldn't be null here, pass it down to startPositioningLocked
+            // to get warning if it's null.
             if (!startPositioningLocked(win, false /*resize*/, startX, startY)) {
                 return false;
             }
@@ -6849,7 +6926,7 @@
         WindowState win = null;
         synchronized (mWindowMap) {
             win = displayContent.findWindowForControlPoint(startX, startY);
-            if (!startPositioningLocked(win, true /*resize*/, startX, startY)) {
+            if (win == null || !startPositioningLocked(win, true /*resize*/, startX, startY)) {
                 return;
             }
         }
@@ -6917,7 +6994,7 @@
                     + " asbinder=" + window.asBinder());
         }
 
-        final int callerPid = Binder.getCallingPid();
+        final int callerUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         IBinder token = null;
 
@@ -6942,6 +7019,7 @@
                         final IBinder winBinder = window.asBinder();
                         token = new Binder();
                         mDragState = new DragState(this, token, surface, flags, winBinder);
+                        mDragState.mUid = callerUid;
                         token = mDragState.mToken = new Binder();
 
                         // 5 second timeout for this window to actually begin the drag
@@ -7165,6 +7243,21 @@
         public static final int RESIZE_STACK = 43;
         public static final int RESIZE_TASK = 44;
 
+        /**
+         * Used to indicate in the message that the dock divider needs to be updated only if it's
+         * necessary.
+         */
+        static final int DOCK_DIVIDER_NO_FORCE_UPDATE = 0;
+        /**
+         * Used to indicate in the message that the dock divider should be force-removed before
+         * updating, so new configuration can be applied.
+         */
+        static final int DOCK_DIVIDER_FORCE_UPDATE = 1;
+        /**
+         * Used to denote that an integer field in a message will not be used.
+         */
+        public static final int UNUSED = 0;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7706,8 +7799,9 @@
                 break;
                 case UPDATE_DOCKED_STACK_DIVIDER: {
                     DisplayContent content = (DisplayContent) msg.obj;
+                    final boolean forceUpdate = msg.arg1 == DOCK_DIVIDER_FORCE_UPDATE;
                     synchronized (mWindowMap) {
-                        content.mDividerControllerLocked.update();
+                        content.mDividerControllerLocked.update(mCurConfiguration, forceUpdate);
                     }
                 }
                 break;
@@ -7721,7 +7815,7 @@
                 break;
                 case RESIZE_STACK: {
                     try {
-                        mActivityManager.resizeStack(msg.arg1, (Rect) msg.obj);
+                        mActivityManager.resizeStack(msg.arg1, (Rect) msg.obj, msg.arg2 == 1);
                     } catch (RemoteException e) {
                         // This will not happen since we are in the same process.
                     }
@@ -7734,6 +7828,13 @@
         }
     }
 
+    void destroyPreservedSurfaceLocked() {
+        for (int i = mDestroyPreservedSurface.size() - 1; i >= 0 ; i--) {
+            final WindowState w = mDestroyPreservedSurface.get(i);
+            w.mWinAnimator.destroyPreservedSurfaceLocked();
+        }
+        mDestroyPreservedSurface.clear();
+    }
     // -------------------------------------------------------------
     // IWindowManager API
     // -------------------------------------------------------------
@@ -8268,7 +8369,7 @@
                 final int numTokens = tokens.size();
                 for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                     final AppWindowToken wtoken = tokens.get(tokenNdx);
-                    if (wtoken.mIsExiting && !wtoken.mReplacingWindow) {
+                    if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
                         continue;
                     }
                     i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8338,7 +8439,8 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) {
+                if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
+                        wtoken.mReplacingWindow != w) {
                     // We know that we will be animating a relaunching window in the near future,
                     // which will receive a z-order increase. We want the replaced window to
                     // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -9077,9 +9179,9 @@
     }
 
     // TOOD(multidisplay): StatusBar on multiple screens?
-    void updateStatusBarVisibilityLocked(int visibility) {
+    boolean updateStatusBarVisibilityLocked(int visibility) {
         if (mLastDispatchedSystemUiVisibility == visibility) {
-            return;
+            return false;
         }
         final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility)
                 // We are only interested in differences of one of the
@@ -9110,14 +9212,16 @@
                 // so sorry
             }
         }
+        return true;
     }
 
     @Override
     public void reevaluateStatusBarVisibility() {
         synchronized (mWindowMap) {
             int visibility = mPolicy.adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
-            updateStatusBarVisibilityLocked(visibility);
-            mWindowPlacerLocked.performSurfacePlacement();
+            if (updateStatusBarVisibilityLocked(visibility)) {
+                mWindowPlacerLocked.requestTraversal();
+            }
         }
     }
 
@@ -9882,7 +9986,9 @@
                 Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token);
                 return;
             }
-            appWindowToken.mReplacingWindow = true;
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken
+                    + " as replacing window.");
+            appWindowToken.mWillReplaceWindow = true;
             appWindowToken.mAnimateReplacingWindow = animate;
         }
     }
@@ -10085,5 +10191,12 @@
             }
         }
 
+        @Override
+        public boolean isStackVisible(int stackId) {
+            synchronized (mWindowMap) {
+                final TaskStack stack = mStackIdToStack.get(stackId);
+                return (stack != null && stack.isVisibleLocked());
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8ace990..e9ea3a8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,8 +28,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
-import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
@@ -38,6 +38,7 @@
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.graphics.Point;
 import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.SystemClock;
@@ -56,7 +57,6 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -159,11 +159,10 @@
     private boolean mConfigHasChanged;
 
     /**
-     * Actual frame shown on-screen (may be modified by animation).  These
-     * are in the screen's coordinate space (WITH the compatibility scale
-     * applied).
+     * Actual position of the surface shown on-screen (may be modified by animation). These are
+     * in the screen's coordinate space (WITH the compatibility scale applied).
      */
-    final RectF mShownFrame = new RectF();
+    final Point mShownPosition = new Point();
 
     /**
      * Insets that determine the actually visible area.  These are in the application's
@@ -448,7 +447,8 @@
                     + WindowManagerService.TYPE_LAYER_OFFSET;
             mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
             mAttachedWindow = attachedWindow;
-            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
+            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to "
+                    + mAttachedWindow);
 
             final WindowList childWindows = mAttachedWindow.mChildWindows;
             final int numChildWindows = childWindows.size();
@@ -554,7 +554,7 @@
     @Override
     public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
             Rect osf) {
-        if (mAppToken != null && mAppToken.mReplacingWindow
+        if (mAppToken != null && mAppToken.mWillReplaceWindow
                 && (mExiting || !mAppToken.mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
@@ -583,12 +583,6 @@
                 if (mContainingFrame.isEmpty()) {
                     mContainingFrame.set(cf);
                 }
-            } else {
-                // Make sure the containing frame is within the content frame so we don't layout
-                // resized window under screen decorations.
-                if (!mContainingFrame.intersect(cf)) {
-                    mContainingFrame.set(cf);
-                }
             }
             mDisplayFrame.set(mContainingFrame);
         } else {
@@ -713,6 +707,7 @@
             mStableFrame.set(mContentFrame);
         } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
             mDisplayContent.mDividerControllerLocked.positionDockedStackedDivider(mFrame);
+            mContentFrame.set(mFrame);
         } else {
             mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                     Math.max(mContentFrame.top, mFrame.top),
@@ -792,8 +787,8 @@
     }
 
     @Override
-    public RectF getShownFrameLw() {
-        return mShownFrame;
+    public Point getShownPositionLw() {
+        return mShownPosition;
     }
 
     @Override
@@ -1314,10 +1309,12 @@
 
     void maybeRemoveReplacedWindow() {
         AppWindowToken token = mAppToken;
-        if (token != null && token.mReplacingWindow) {
-            token.mReplacingWindow = false;
+        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) {
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
+            token.mWillReplaceWindow = false;
             token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
+            token.mReplacingWindow = null;
             for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
                 WindowState win = token.allAppWindows.get(i);
                 if (win.mExiting) {
@@ -1333,6 +1330,10 @@
         }
     }
 
+    boolean inDockedWorkspace() {
+        return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace();
+    }
+
     private class DeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
@@ -1504,6 +1505,31 @@
         return mExiting || (mService.mClosingApps.contains(mAppToken));
     }
 
+    boolean saveSurfaceIfNeeded() {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Don't save surfaces on Svelte devices.
+            return false;
+        }
+
+        Task task = getTask();
+        if (task == null || task.inHomeStack()) {
+            // Don't save surfaces for home stack apps. These usually resume and draw
+            // first frame very fast. Saving surfaces are mostly a waste of memory.
+            return false;
+        }
+
+        // We want to save surface if the app's windows are "allDrawn", or if we're
+        // currently animating with save surfaces. (If the app didn't even finish
+        // drawing when the user exits, but we have a saved surface from last time,
+        // we still want to keep that surface.)
+        if (mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface) {
+            mAppToken.mHasSavedSurface = true;
+            return true;
+        }
+
+        return false;
+    }
+
     @Override
     public boolean isDefaultDisplay() {
         final DisplayContent displayContent = getDisplayContent();
@@ -1827,7 +1853,7 @@
             }
         }
         pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
-                pw.print(" mShownFrame="); mShownFrame.printShortString(pw);
+                pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
                 pw.print(" isReadyForDisplay()="); pw.println(isReadyForDisplay());
         if (dumpAll) {
             pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ed53501..68c39ba 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -23,11 +23,12 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
-import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerService.localLOGV;
 import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerService.SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
 
@@ -37,7 +38,6 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Debug;
 import android.os.RemoteException;
@@ -66,6 +66,7 @@
  **/
 class WindowStateAnimator {
     static final String TAG = "WindowStateAnimator";
+    static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
 
     // Unchanging local convenience fields.
     final WindowManagerService mService;
@@ -108,7 +109,7 @@
      */
     boolean mSurfaceDestroyDeferred;
 
-    boolean mDestroyPendingSurfaceUponRedraw;
+    boolean mDestroyPreservedSurfaceUponRedraw;
     float mShownAlpha = 0;
     float mAlpha = 0;
     float mLastAlpha = 0;
@@ -290,6 +291,9 @@
     // This must be called while inside a transaction.  Returns true if
     // there is more animation to run.
     boolean stepAnimationLocked(long currentTime) {
+        // Save the animation state as it was before this step so WindowManagerService can tell if
+        // we just started or just stopped animating by comparing mWasAnimating with isAnimating().
+        mWasAnimating = mAnimating;
         final DisplayContent displayContent = mWin.getDisplayContent();
         if (displayContent != null && mService.okToDisplay()) {
             // We will run animations as long as the display isn't frozen.
@@ -531,6 +535,7 @@
             mDrawState = COMMIT_DRAW_PENDING;
             return true;
         }
+
         return false;
     }
 
@@ -550,15 +555,14 @@
         mDrawState = READY_TO_SHOW;
         boolean result = false;
         final AppWindowToken atoken = mWin.mAppToken;
-        if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+        if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface ||
+                mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
             result = performShowLocked();
         }
-        if (mDestroyPendingSurfaceUponRedraw) {
-            mDestroyPendingSurfaceUponRedraw = false;
-            destroyDeferredSurfaceLocked();
-            mService.stopFreezingDisplayLocked();
+        if (mDestroyPreservedSurfaceUponRedraw && result) {
+            mService.mDestroyPreservedSurface.add(mWin);
         }
-        return false;
+        return result;
     }
 
     static class SurfaceTrace extends SurfaceControl {
@@ -778,6 +782,31 @@
         }
     }
 
+    void preserveSurfaceLocked() {
+        if (mDestroyPreservedSurfaceUponRedraw) {
+            return;
+        }
+        if (mSurfaceControl != null) {
+            SurfaceControl.openTransaction();
+            try {
+                mSurfaceControl.setLayer(WINDOW_FREEZE_LAYER);
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+        }
+        mDestroyPreservedSurfaceUponRedraw = true;
+        mSurfaceDestroyDeferred = true;
+        destroySurfaceLocked();
+    }
+
+    void destroyPreservedSurfaceLocked() {
+        if (!mDestroyPreservedSurfaceUponRedraw) {
+            return;
+        }
+        destroyDeferredSurfaceLocked();
+        mDestroyPreservedSurfaceUponRedraw = false;
+    }
+
     SurfaceControl createSurfaceLocked() {
         final WindowState w = mWin;
         if (mSurfaceControl == null) {
@@ -968,12 +997,16 @@
     }
 
     void destroySurfaceLocked() {
-        if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
-            mWin.mAppToken.startingDisplayed = false;
+        final AppWindowToken wtoken = mWin.mAppToken;
+        if (wtoken != null) {
+            wtoken.mHasSavedSurface = false;
+            wtoken.mAnimatingWithSavedSurface = false;
+            if (mWin == wtoken.startingWindow) {
+                wtoken.startingDisplayed = false;
+            }
         }
 
         if (mSurfaceControl != null) {
-
             int i = mWin.mChildWindows.size();
             while (i > 0) {
                 i--;
@@ -1162,9 +1195,7 @@
             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.mShownFrame.set(x, y, x+w, y+h);
+            mWin.mShownPosition.set((int) x, (int) y);
 
             // Now set the alpha...  but because our current hardware
             // can't do alpha transformation on a non-opaque surface,
@@ -1257,15 +1288,13 @@
             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.mShownFrame.set(x, y, x + w, y + h);
+            mWin.mShownPosition.set((int) x, (int) y);
 
             mShownAlpha = mAlpha;
         } else {
-            mWin.mShownFrame.set(mWin.mFrame);
+            mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
             if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
-                mWin.mShownFrame.offset(mWin.mXOffset, mWin.mYOffset);
+                mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
             }
             mShownAlpha = mAlpha;
             mHaveMatrix = false;
@@ -1355,7 +1384,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;
@@ -1369,18 +1397,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 {
@@ -1409,29 +1433,35 @@
         // living in a different stack. If we suddenly crop it to the new stack bounds, it might
         // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
         // gets removed. The window that will replace it will abide them.
-        if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) {
+        if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
             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);
         }
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
 
-        float left = w.mShownFrame.left;
-        float top = w.mShownFrame.top;
+        float left = w.mShownPosition.x;
+        float top = w.mShownPosition.y;
 
         int width;
         int height;
@@ -1673,10 +1703,10 @@
         }
     }
 
-    void setWallpaperOffset(RectF shownFrame) {
+    void setWallpaperOffset(Point shownPosition) {
         final LayoutParams attrs = mWin.getAttrs();
-        final int left = ((int) shownFrame.left) - attrs.surfaceInsets.left;
-        final int top = ((int) shownFrame.top) - attrs.surfaceInsets.top;
+        final int left = shownPosition.x - attrs.surfaceInsets.left;
+        final int top = shownPosition.y - attrs.surfaceInsets.top;
         if (mSurfaceX != left || mSurfaceY != top) {
             if (mAnimating) {
                 // If this window (or its app token) is animating, then the position
@@ -1783,8 +1813,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
@@ -1831,8 +1859,7 @@
                 }
             }
 
-            if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
-                    && mWin.mAppToken != null) {
+            if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
                 mWin.mAppToken.firstWindowDrawn = true;
 
                 if (mWin.mAppToken.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index db9fcf1..e2f4dd9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,6 +17,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.WindowManagerService.DEBUG;
 import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -340,6 +341,10 @@
                 // Don't remove this window until rotation has completed.
                 continue;
             }
+            // Discard the saved surface if window size is changed, it can't be reused.
+            if (win.mAppToken != null && win.mAppToken.mHasSavedSurface) {
+                win.mWinAnimator.destroySurfaceLocked();
+            }
             win.reportResized();
             mService.mResizingWindows.remove(i);
         }
@@ -371,7 +376,9 @@
                 if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
                     wallpaperDestroyed = true;
                 }
-                win.mWinAnimator.destroySurfaceLocked();
+                if (!win.saveSurfaceIfNeeded()) {
+                    win.mWinAnimator.destroySurfaceLocked();
+                }
             } while (i > 0);
             mService.mDestroySurface.clear();
         }
@@ -717,7 +724,8 @@
                     /*
                      * Updates the shown frame before we set up the surface. This is needed because
                      * the resizing could change the top-left position (in addition to size) of the
-                     * window. setSurfaceBoundariesLocked uses mShownFrame to position the surface.
+                     * window. setSurfaceBoundariesLocked uses mShownPosition to position the
+                      * surface.
                      */
                     winAnimator.computeShownFrameLocked();
                     winAnimator.setSurfaceBoundariesLocked(recoveringMemory);
@@ -965,7 +973,8 @@
         }
 
         mService.mPolicy.finishLayoutLw();
-        mService.mH.obtainMessage(UPDATE_DOCKED_STACK_DIVIDER, displayContent).sendToTarget();
+        mService.mH.obtainMessage(UPDATE_DOCKED_STACK_DIVIDER,
+                DOCK_DIVIDER_NO_FORCE_UPDATE, UNUSED, displayContent).sendToTarget();
     }
 
     /**
@@ -1145,7 +1154,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;
@@ -1163,6 +1181,18 @@
                 }
             }
             createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+
+            if (wtoken.mHasSavedSurface) {
+                if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                        "Early start of animation: " + wtoken + ", allDrawn=" + wtoken.allDrawn);
+
+                for (int j = 0; j < wtoken.windows.size(); j++) {
+                    WindowState ws = wtoken.windows.get(j);
+                    ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+                }
+                wtoken.mHasSavedSurface = false;
+                wtoken.mAnimatingWithSavedSurface = true;
+            }
         }
 
         AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
@@ -1208,6 +1238,10 @@
                         + wtoken.allDrawn + " startingDisplayed="
                         + wtoken.startingDisplayed + " startingMoved="
                         + wtoken.startingMoved);
+
+                if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) {
+                    continue;
+                }
                 if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
                     return false;
                 }
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 2dd7cde..8385685 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -124,6 +124,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.pm.UserRestrictionsUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -277,7 +278,8 @@
     final LocalService mLocalService;
 
     // Stores and loads state on device and profile owners.
-    private final Owners mOwners;
+    @VisibleForTesting
+    final Owners mOwners;
 
     private final Binder mToken = new Binder();
 
@@ -433,6 +435,8 @@
         private static final String TAG_PROVIDER = "provider";
         private static final String TAG_PACKAGE_LIST_ITEM  = "item";
 
+        private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
+
         final DeviceAdminInfo info;
 
         int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -512,6 +516,8 @@
 
         List<String> crossProfileWidgetProviders;
 
+        Bundle userRestrictions;
+
         ActiveAdmin(DeviceAdminInfo _info) {
             info = _info;
         }
@@ -686,6 +692,10 @@
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
             writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+            if (hasUserRestrictions()) {
+                UserRestrictionsUtils.writeRestrictions(
+                        out, userRestrictions, TAG_USER_RESTRICTIONS);
+            }
         }
 
         void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -795,6 +805,8 @@
                     permittedAccessiblityServices = readPackageList(parser, tag);
                 } else if (TAG_PERMITTED_IMES.equals(tag)) {
                     permittedInputMethods = readPackageList(parser, tag);
+                } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
+                    UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions());
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -915,6 +927,17 @@
             return result;
         }
 
+        boolean hasUserRestrictions() {
+            return userRestrictions != null && userRestrictions.size() > 0;
+        }
+
+        Bundle ensureUserRestrictions() {
+            if (userRestrictions == null) {
+                userRestrictions = new Bundle();
+            }
+            return userRestrictions;
+        }
+
         void dump(String prefix, PrintWriter pw) {
             pw.print(prefix); pw.print("uid="); pw.println(getUid());
             pw.print(prefix); pw.println("policies:");
@@ -984,6 +1007,8 @@
                 pw.print(prefix); pw.print("permittedInputMethods=");
                         pw.println(permittedInputMethods.toString());
             }
+            pw.print(prefix); pw.println("userRestrictions:");
+            UserRestrictionsUtils.dumpRestrictions(pw, prefix + "  ", userRestrictions);
         }
     }
 
@@ -1116,6 +1141,10 @@
             return getCallingUid() == Process.myUid();
         }
 
+        final int userHandleGetCallingUserId() {
+            return UserHandle.getUserId(binderGetCallingUid());
+        }
+
         File environmentGetUserSystemDirectory(int userId) {
             return Environment.getUserSystemDirectory(userId);
         }
@@ -1151,6 +1180,42 @@
         String getDevicePolicyFilePathForSystemUser() {
             return "/data/system/";
         }
+
+        int settingsSecureGetIntForUser(String name, int def, int userHandle) {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    name, def, userHandle);
+        }
+
+        void settingsSecurePutIntForUser(String name, int value, int userHandle) {
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    name, value, userHandle);
+        }
+
+        void settingsSecurePutStringForUser(String name, String value, int userHandle) {
+            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                    name, value, userHandle);
+        }
+
+        void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
+            Settings.Global.putStringForUser(mContext.getContentResolver(),
+                    name, value, userHandle);
+        }
+
+        void settingsSecurePutInt(String name, int value) {
+            Settings.Secure.putInt(mContext.getContentResolver(), name, value);
+        }
+
+        void settingsGlobalPutInt(String name, int value) {
+            Settings.Global.putInt(mContext.getContentResolver(), name, value);
+        }
+
+        void settingsSecurePutString(String name, String value) {
+            Settings.Secure.putString(mContext.getContentResolver(), name, value);
+        }
+
+        void settingsGlobalPutString(String name, String value) {
+            Settings.Global.putString(mContext.getContentResolver(), name, value);
+        }
     }
 
     /**
@@ -1261,10 +1326,59 @@
     void loadOwners() {
         synchronized (this) {
             mOwners.load();
+            findOwnerComponentIfNecessaryLocked();
+
+            // TODO PO may not have a class name either due to b/17652534.  Address that too.
+
             updateDeviceOwnerLocked();
         }
     }
 
+    private void findOwnerComponentIfNecessaryLocked() {
+        if (!mOwners.hasDeviceOwner()) {
+            return;
+        }
+        final ComponentName doComponentName = mOwners.getDeviceOwnerComponent();
+
+        if (!TextUtils.isEmpty(doComponentName.getClassName())) {
+            return; // Already a full component name.
+        }
+
+        final ComponentName doComponent = findAdminComponentWithPackageLocked(
+                doComponentName.getPackageName(),
+                mOwners.getDeviceOwnerUserId());
+        if (doComponent == null) {
+            Slog.e(LOG_TAG, "Device-owner isn't registered as device-admin");
+        } else {
+            mOwners.setDeviceOwner(
+                    doComponent,
+                    mOwners.getDeviceOwnerName(),
+                    mOwners.getDeviceOwnerUserId());
+            mOwners.writeDeviceOwner();
+        }
+    }
+
+    private ComponentName findAdminComponentWithPackageLocked(String packageName, int userId) {
+        final DevicePolicyData policy = getUserData(userId);
+        final int n = policy.mAdminList.size();
+        ComponentName found = null;
+        int nFound = 0;
+        for (int i = 0; i < n; i++) {
+            final ActiveAdmin admin = policy.mAdminList.get(i);
+            if (packageName.equals(admin.info.getPackageName())) {
+                // Found!
+                if (nFound == 0) {
+                    found = admin.info.getComponent();
+                }
+                nFound++;
+            }
+        }
+        if (nFound > 0) {
+            Slog.w(LOG_TAG, "Multiple DA found; assume the first one is DO.");
+        }
+        return found;
+    }
+
     /**
      * Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration
      * reminders.  Clears alarm if no expirations are configured.
@@ -1375,23 +1489,20 @@
         return null;
     }
 
-    private boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
+    @VisibleForTesting
+    boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
             int userId) {
-        boolean ownsDevice = isDeviceOwner(admin.info.getPackageName());
+        boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
         boolean ownsProfile = (getProfileOwner(userId) != null
                 && getProfileOwner(userId).getPackageName()
                     .equals(admin.info.getPackageName()));
-        boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName())
-                && !hasUserSetupCompleted(userId);
 
         if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && (ownsDevice || ownsInitialization))
-                    || (ownsDevice && ownsProfile)) {
+            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || (ownsDevice && ownsProfile)) {
                 return true;
             }
         } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile
-                    || ownsInitialization) {
+            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile) {
                 return true;
             }
         } else {
@@ -1798,7 +1909,6 @@
         validatePasswordOwnerLocked(policy);
         syncDeviceCapabilitiesLocked(policy);
         updateMaximumTimeToLockLocked(policy);
-        addDeviceInitializerToLockTaskPackagesLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
@@ -1820,8 +1930,10 @@
     private void updateDeviceOwnerLocked() {
         long ident = mInjector.binderClearCallingIdentity();
         try {
-            mInjector.getIActivityManager()
-                    .updateDeviceOwner(getDeviceOwner());
+            if (getDeviceOwner() != null) {
+                mInjector.getIActivityManager()
+                        .updateDeviceOwner(getDeviceOwner().getPackageName());
+            }
         } catch (RemoteException e) {
             // Not gonna happen.
         } finally {
@@ -1885,7 +1997,8 @@
         }
     }
 
-    public void systemReady(int phase) {
+    @VisibleForTesting
+    void systemReady(int phase) {
         if (!mHasFeature) {
             return;
         }
@@ -2224,7 +2337,7 @@
             }
             if (admin.getUid() != mInjector.binderGetCallingUid()) {
                 // Active device owners must remain active admins.
-                if (isDeviceOwner(adminReceiver.getPackageName())) {
+                if (isDeviceOwner(adminReceiver)) {
                     return;
                 }
                 mContext.enforceCallingOrSelfPermission(
@@ -3076,7 +3189,7 @@
             return false;
         }
 
-        boolean callerIsDeviceOwnerAdmin = isCallerDeviceOwnerOrInitializer(callingUid);
+        boolean callerIsDeviceOwnerAdmin = isCallerDeviceOwner(callingUid);
         boolean doNotAskCredentialsOnBoot =
                 (flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
         if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -3163,8 +3276,7 @@
             } else {
                 // Make sure KEEP_SCREEN_ON is disabled, since that
                 // would allow bypassing of the maximum time to lock.
-                Settings.Global.putInt(mContext.getContentResolver(),
-                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+                mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             }
 
             policy.mLastMaximumTimeToLock = timeMs;
@@ -3382,8 +3494,8 @@
         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()) {
-            ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdmin();
+        if (aliasChooser == null && caller.isSystem()) {
+            ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
             if (deviceOwnerAdmin != null) {
                 aliasChooser = deviceOwnerAdmin.info.getComponent();
             }
@@ -3484,11 +3596,8 @@
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-                    boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName())
-                            && !hasUserSetupCompleted(userHandle);
                     if (userHandle != UserHandle.USER_SYSTEM
-                            || !(isDeviceOwner(admin.info.getPackageName())
-                                    || ownsInitialization)) {
+                            || !isDeviceOwner(admin.info.getComponent())) {
                         throw new SecurityException(
                                "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
                     }
@@ -3591,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);
@@ -3846,16 +3958,15 @@
             } catch (NumberFormatException e) {}
         }
         exclusionList = exclusionList.trim();
-        ContentResolver res = mContext.getContentResolver();
 
         ProxyInfo proxyProperties = new ProxyInfo(data[0], proxyPort, exclusionList);
         if (!proxyProperties.isValid()) {
             Slog.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
             return;
         }
-        Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
-        Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
-        Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+        mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
+        mInjector.settingsGlobalPutInt(Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+        mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
                 exclusionList);
     }
 
@@ -4079,8 +4190,7 @@
         if (required) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
-                Settings.Global.putInt(mContext.getContentResolver(),
-                        Settings.Global.AUTO_TIME, 1 /* AUTO_TIME on */);
+                mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, 1 /* AUTO_TIME on */);
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
             }
@@ -4096,7 +4206,7 @@
             return false;
         }
         synchronized (this) {
-            ActiveAdmin deviceOwner = getDeviceOwnerAdmin();
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
             return (deviceOwner != null) ? deviceOwner.requireAutoTime : false;
         }
     }
@@ -4239,13 +4349,13 @@
     }
 
     @Override
-    public boolean setDeviceOwner(String packageName, String ownerName, int userId) {
+    public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
         if (!mHasFeature) {
             return false;
         }
-        if (packageName == null
-                || !isPackageInstalledForUser(packageName, userId)) {
-            throw new IllegalArgumentException("Invalid package name " + packageName
+        if (admin == null
+                || !isPackageInstalledForUser(admin.getPackageName(), userId)) {
+            throw new IllegalArgumentException("Invalid component " + admin
                     + " for device owner");
         }
         synchronized (this) {
@@ -4261,7 +4371,7 @@
                 mInjector.binderRestoreCallingIdentity(ident);
             }
 
-            mOwners.setDeviceOwner(packageName, ownerName, userId);
+            mOwners.setDeviceOwner(admin, ownerName, userId);
             mOwners.writeDeviceOwner();
             updateDeviceOwnerLocked();
             Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
@@ -4277,24 +4387,33 @@
         }
     }
 
+    public boolean isDeviceOwner(ComponentName who) {
+        if (!mHasFeature) {
+            return false;
+        }
+        synchronized (this) {
+            return mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerComponent().equals(who);
+        }
+    }
+
     @Override
-    public boolean isDeviceOwner(String packageName) {
+    public boolean isDeviceOwnerPackage(String packageName) {
         if (!mHasFeature) {
             return false;
         }
         synchronized (this) {
             return mOwners.hasDeviceOwner()
-                    && mOwners.getDeviceOwnerPackageName().equals(packageName);
+                    && mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName);
         }
     }
 
     @Override
-    public String getDeviceOwner() {
+    public ComponentName getDeviceOwner() {
         if (!mHasFeature) {
             return null;
         }
         synchronized (this) {
-            return mOwners.getDeviceOwnerPackageName();
+            return mOwners.getDeviceOwnerComponent();
         }
     }
 
@@ -4317,17 +4436,18 @@
     }
 
     // Returns the active device owner or null if there is no device owner.
-    private ActiveAdmin getDeviceOwnerAdmin() {
-        String deviceOwnerPackageName = getDeviceOwner();
-        if (deviceOwnerPackageName == null) {
+    @VisibleForTesting
+    ActiveAdmin getDeviceOwnerAdminLocked() {
+        ComponentName component = getDeviceOwner();
+        if (component == null) {
             return null;
         }
 
-        DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
+        DevicePolicyData policy = getUserData(mOwners.getDeviceOwnerUserId());
         final int n = policy.mAdminList.size();
         for (int i = 0; i < n; i++) {
             ActiveAdmin admin = policy.mAdminList.get(i);
-            if (deviceOwnerPackageName.equals(admin.info.getPackageName())) {
+            if (component.equals(admin.info.getComponent())) {
                 return admin;
             }
         }
@@ -4337,15 +4457,17 @@
     @Override
     public void clearDeviceOwner(String packageName) {
         Preconditions.checkNotNull(packageName, "packageName is null");
+        final int callingUid = mInjector.binderGetCallingUid();
         try {
             int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
-            if (uid != mInjector.binderGetCallingUid()) {
+            if (uid != callingUid) {
                 throw new SecurityException("Invalid packageName");
             }
         } catch (NameNotFoundException e) {
             throw new SecurityException(e);
         }
-        if (!isDeviceOwner(packageName)) {
+        if (!mOwners.hasDeviceOwner() || !getDeviceOwner().getPackageName().equals(packageName)
+                || (mOwners.getDeviceOwnerUserId() != UserHandle.getUserId(callingUid))) {
             throw new SecurityException("clearDeviceOwner can only be called by the device owner");
         }
         synchronized (this) {
@@ -4367,126 +4489,6 @@
     }
 
     @Override
-    public boolean setDeviceInitializer(ComponentName who, ComponentName initializer) {
-        if (!mHasFeature) {
-            return false;
-        }
-        if (initializer == null ||
-                !mOwners.hasDeviceOwner() ||
-                !isPackageInstalledForUser(initializer.getPackageName(),
-                        mOwners.getDeviceOwnerUserId())) {
-            throw new IllegalArgumentException("Invalid component name " + initializer
-                    + " for device initializer or no device owner set");
-        }
-        boolean isInitializerSystemApp;
-        try {
-            isInitializerSystemApp = isSystemApp(mIPackageManager,
-                    initializer.getPackageName(),
-                    mInjector.binderGetCallingUserHandle().getIdentifier());
-        } catch (RemoteException | IllegalArgumentException e) {
-            isInitializerSystemApp = false;
-            Slog.e(LOG_TAG, "Fail to check if device initialzer is system app.", e);
-        }
-        if (!isInitializerSystemApp) {
-            throw new IllegalArgumentException("Only system app can be set as device initializer.");
-        }
-        synchronized (this) {
-            enforceCanSetDeviceInitializer(who);
-
-            if (mOwners.hasDeviceInitializer()) {
-                throw new IllegalStateException(
-                        "Trying to set device initializer but device initializer is already set.");
-            }
-
-            mOwners.setDeviceInitializer(initializer);
-
-            addDeviceInitializerToLockTaskPackagesLocked(mOwners.getDeviceOwnerUserId());
-            mOwners.writeDeviceOwner();
-            return true;
-        }
-    }
-
-    private void enforceCanSetDeviceInitializer(ComponentName who) {
-        if (who == null) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
-            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                throw new IllegalStateException(
-                        "Trying to set device initializer but device is already provisioned.");
-            }
-        } else {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-    }
-
-    @Override
-    public boolean isDeviceInitializer(String packageName) {
-        if (!mHasFeature) {
-            return false;
-        }
-        synchronized (this) {
-            return mOwners.hasDeviceInitializer()
-                    && mOwners.getDeviceInitializerPackageName().equals(packageName);
-        }
-    }
-
-    @Override
-    public String getDeviceInitializer() {
-        if (!mHasFeature) {
-            return null;
-        }
-        synchronized (this) {
-            if (mOwners.hasDeviceInitializer()) {
-                return mOwners.getDeviceInitializerPackageName();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public ComponentName getDeviceInitializerComponent() {
-        if (!mHasFeature) {
-            return null;
-        }
-        synchronized (this) {
-            if (mOwners.hasDeviceInitializer()) {
-                return mOwners.getDeviceInitializerComponent();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void clearDeviceInitializer(ComponentName who) {
-        if (!mHasFeature) {
-            return;
-        }
-        Preconditions.checkNotNull(who, "ComponentName is null");
-
-        ActiveAdmin admin = getActiveAdminUncheckedLocked(who, UserHandle.getCallingUserId());
-
-        if (admin.getUid() != mInjector.binderGetCallingUid()) {
-            throw new SecurityException("Admin " + who + " is not owned by uid "
-                    + mInjector.binderGetCallingUid());
-        }
-
-        if (!isDeviceInitializer(admin.info.getPackageName())
-                && !isDeviceOwner(admin.info.getPackageName())) {
-            throw new SecurityException(
-                    "clearDeviceInitializer can only be called by the device initializer/owner");
-        }
-        synchronized (this) {
-            long ident = mInjector.binderClearCallingIdentity();
-            try {
-                mOwners.clearDeviceInitializer();
-                mOwners.writeDeviceOwner();
-            } finally {
-                mInjector.binderRestoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
     public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
         if (!mHasFeature) {
             return false;
@@ -4576,50 +4578,6 @@
     }
 
     @Override
-    public boolean setUserEnabled(ComponentName who) {
-        if (!mHasFeature) {
-            return false;
-        }
-        synchronized (this) {
-            if (who == null) {
-                throw new NullPointerException("ComponentName is null");
-            }
-            int userId = UserHandle.getCallingUserId();
-
-            ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (!isDeviceInitializer(activeAdmin.info.getPackageName())) {
-                throw new SecurityException(
-                        "This method can only be called by device initializers");
-            }
-
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                if (!isDeviceOwner(activeAdmin.info.getPackageName())) {
-                    mIPackageManager.setComponentEnabledSetting(who,
-                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                            PackageManager.DONT_KILL_APP, userId);
-
-                    removeActiveAdmin(who, userId);
-                }
-
-                if (userId == UserHandle.USER_SYSTEM) {
-                    Settings.Global.putInt(mContext.getContentResolver(),
-                            Settings.Global.DEVICE_PROVISIONED, 1);
-                }
-                Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.USER_SETUP_COMPLETE, 1, userId);
-            } catch (RemoteException e) {
-                Log.i(LOG_TAG, "Can't talk to package manager", e);
-                return false;
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
-            }
-            return true;
-        }
-    }
-
-    @Override
     public void setProfileEnabled(ComponentName who) {
         if (!mHasFeature) {
             return;
@@ -4674,7 +4632,8 @@
 
     // Returns the active profile owner for this user or null if the current user has no
     // profile owner.
-    private ActiveAdmin getProfileOwnerAdmin(int userHandle) {
+    @VisibleForTesting
+    ActiveAdmin getProfileOwnerAdminLocked(int userHandle) {
         ComponentName profileOwner = mOwners.getProfileOwnerComponent(userHandle);
         if (profileOwner == null) {
             return null;
@@ -5546,12 +5505,12 @@
     @Override
     public void setUserRestriction(ComponentName who, String key, boolean enabled) {
         Preconditions.checkNotNull(who, "ComponentName is null");
-        final int userHandle = UserHandle.getCallingUserId();
+        final int userHandle = mInjector.userHandleGetCallingUserId();
         final UserHandle user = new UserHandle(userHandle);
         synchronized (this) {
             ActiveAdmin activeAdmin =
                     getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            boolean isDeviceOwner = isDeviceOwner(activeAdmin.info.getPackageName());
+            boolean isDeviceOwner = isDeviceOwner(who);
             if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
                     && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
                 throw new SecurityException("Profile owners cannot set user restriction " + key);
@@ -5571,37 +5530,40 @@
                         mInjector.getIAudioService()
                                 .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
                     } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
-                        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        mInjector.settingsSecurePutIntForUser(
                                 Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
                                 userHandle);
                     } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        mInjector.settingsSecurePutIntForUser(
                                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF,
                                 userHandle);
-                        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                        mInjector.settingsSecurePutStringForUser(
                                 Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
                                 userHandle);
                     } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
                         // Only disable adb if changing for system user, since it is global
                         // TODO: should this be admin user?
                         if (userHandle == UserHandle.USER_SYSTEM) {
-                            Settings.Global.putStringForUser(mContext.getContentResolver(),
+                            mInjector.settingsGlobalPutStringForUser(
                                     Settings.Global.ADB_ENABLED, "0", userHandle);
                         }
                     } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
-                        Settings.Global.putStringForUser(mContext.getContentResolver(),
+                        mInjector.settingsGlobalPutStringForUser(
                                 Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
                                 userHandle);
-                        Settings.Global.putStringForUser(mContext.getContentResolver(),
+                        mInjector.settingsGlobalPutStringForUser(
                                 Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
                                 userHandle);
                     } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
-                        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                        mInjector.settingsSecurePutIntForUser(
                                 Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
                                 userHandle);
                     }
                 }
                 mUserManager.setUserRestriction(key, enabled, user);
+                activeAdmin.ensureUserRestrictions().putBoolean(key, enabled);
+                saveSettingsLocked(userHandle);
+
                 if (enabled != alreadyRestricted) {
                     if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
                         // Send out notifications however as some clients may want to reread the
@@ -5902,7 +5864,7 @@
         // TODO: Should there be a check to make sure this relationship is within a profile group?
         //enforceSystemProcess("getCrossProfileCallerIdDisabled can only be called by system");
         synchronized (this) {
-            ActiveAdmin admin = getProfileOwnerAdmin(userId);
+            ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
             return (admin != null) ? admin.disableCallerId : false;
         }
     }
@@ -5995,7 +5957,7 @@
         // within a profile group?
         // enforceSystemProcess("getCrossProfileCallerIdDisabled can only be called by system");
         synchronized (this) {
-            ActiveAdmin admin = getProfileOwnerAdmin(userId);
+            ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
             return (admin != null) ? admin.disableBluetoothContactSharing : false;
         }
     }
@@ -6081,7 +6043,7 @@
             Bundle adminExtras = new Bundle();
             adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
             for (ActiveAdmin admin : policy.mAdminList) {
-                boolean ownsDevice = isDeviceOwner(admin.info.getPackageName());
+                boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
                 boolean ownsProfile = (getProfileOwner(userHandle) != null
                         && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
                 if (ownsDevice || ownsProfile) {
@@ -6098,7 +6060,6 @@
 
     @Override
     public void setGlobalSetting(ComponentName who, String setting, String value) {
-        final ContentResolver contentResolver = mContext.getContentResolver();
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         synchronized (this) {
@@ -6126,7 +6087,7 @@
 
             long id = mInjector.binderClearCallingIdentity();
             try {
-                Settings.Global.putString(contentResolver, setting, value);
+                mInjector.settingsGlobalPutString(setting, value);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
@@ -6140,10 +6101,9 @@
         final ContentResolver contentResolver = mContext.getContentResolver();
 
         synchronized (this) {
-            ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
 
-            if (isDeviceOwner(activeAdmin.info.getPackageName())) {
+            if (isDeviceOwner(who)) {
                 if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)) {
                     throw new SecurityException(String.format(
                             "Permission denial: Device owners cannot update %1$s", setting));
@@ -6155,7 +6115,7 @@
 
             long id = mInjector.binderClearCallingIdentity();
             try {
-                Settings.Secure.putStringForUser(contentResolver, setting, value, callingUserId);
+                mInjector.settingsSecurePutStringForUser(setting, value, callingUserId);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
@@ -6278,18 +6238,15 @@
      */
     void updateUserSetupComplete() {
         List<UserInfo> users = mUserManager.getUsers(true);
-        ContentResolver resolver = mContext.getContentResolver();
         final int N = users.size();
         for (int i = 0; i < N; i++) {
             int userHandle = users.get(i).id;
-            if (Settings.Secure.getIntForUser(resolver, Settings.Secure.USER_SETUP_COMPLETE, 0,
+            if (mInjector.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                     userHandle) != 0) {
                 DevicePolicyData policy = getUserData(userHandle);
                 if (!policy.mUserSetupComplete) {
                     policy.mUserSetupComplete = true;
                     synchronized (this) {
-                        // The DeviceInitializer was whitelisted but now should be removed.
-                        removeDeviceInitializerFromLockTaskPackages(userHandle);
                         saveSettingsLocked(userHandle);
                     }
                 }
@@ -6297,35 +6254,6 @@
         }
     }
 
-    private void addDeviceInitializerToLockTaskPackagesLocked(int userHandle) {
-        if (hasUserSetupCompleted(userHandle)) {
-            return;
-        }
-
-        final String deviceInitializerPackage = getDeviceInitializer();
-        if (deviceInitializerPackage == null) {
-            return;
-        }
-
-        final List<String> packages = getLockTaskPackagesLocked(userHandle);
-        if (!packages.contains(deviceInitializerPackage)) {
-            packages.add(deviceInitializerPackage);
-            setLockTaskPackagesLocked(userHandle, packages);
-        }
-    }
-
-    private void removeDeviceInitializerFromLockTaskPackages(int userHandle) {
-        final String deviceInitializerPackage = getDeviceInitializer();
-        if (deviceInitializerPackage == null) {
-            return;
-        }
-
-        List<String> packages = getLockTaskPackagesLocked(userHandle);
-        if (packages.remove(deviceInitializerPackage)) {
-            setLockTaskPackagesLocked(userHandle, packages);
-        }
-    }
-
     private class SetupContentObserver extends ContentObserver {
 
         private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
@@ -6450,15 +6378,15 @@
     }
 
     /**
-     * Checks if the caller of the method is the device owner app or device initialization app.
+     * Checks if the caller of the method is the device owner app.
      *
      * @param callerUid UID of the caller.
-     * @return true if the caller is the device owner app or device initializer.
+     * @return true if the caller is the device owner app
      */
-    private boolean isCallerDeviceOwnerOrInitializer(int callerUid) {
+    private boolean isCallerDeviceOwner(int callerUid) {
         String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
         for (String pkg : pkgs) {
-            if (isDeviceOwner(pkg) || isDeviceInitializer(pkg)) {
+            if (isDeviceOwnerPackage(pkg)) {
                 return true;
             }
         }
@@ -6480,7 +6408,8 @@
                 updateReceivedTime);
 
         synchronized (this) {
-            String deviceOwnerPackage = getDeviceOwner();
+            final String deviceOwnerPackage = getDeviceOwner() == null ? null :
+                    getDeviceOwner().getPackageName();
             if (deviceOwnerPackage == null) {
                 return;
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 370cf48..799267d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -16,16 +16,11 @@
 
 package com.android.server.devicepolicy;
 
-import android.app.AppGlobals;
 import android.app.admin.SystemUpdatePolicy;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.os.Environment;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -35,6 +30,7 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -90,9 +86,6 @@
 
     private int mDeviceOwnerUserId = UserHandle.USER_NULL;
 
-    // Internal state for the device initializer package.
-    private OwnerInfo mDeviceInitializer;
-
     // Internal state for the profile owner packages.
     private final ArrayMap<Integer, OwnerInfo> mProfileOwners = new ArrayMap<>();
 
@@ -153,12 +146,16 @@
         return mDeviceOwner != null ? mDeviceOwner.name : null;
     }
 
-    void setDeviceOwner(String packageName, String ownerName, int userId) {
+    ComponentName getDeviceOwnerComponent() {
+        return mDeviceOwner != null ? mDeviceOwner.admin : null;
+    }
+
+    void setDeviceOwner(ComponentName admin, String ownerName, int userId) {
         if (userId < 0) {
             Slog.e(TAG, "Invalid user id for device owner user: " + userId);
             return;
         }
-        mDeviceOwner = new OwnerInfo(ownerName, packageName);
+        mDeviceOwner = new OwnerInfo(ownerName, admin);
         mDeviceOwnerUserId = userId;
     }
 
@@ -167,26 +164,6 @@
         mDeviceOwnerUserId = UserHandle.USER_NULL;
     }
 
-    ComponentName getDeviceInitializerComponent() {
-        return mDeviceInitializer.admin;
-    }
-
-    String getDeviceInitializerPackageName() {
-        return mDeviceInitializer != null ? mDeviceInitializer.packageName : null;
-    }
-
-    void setDeviceInitializer(ComponentName admin) {
-        mDeviceInitializer = new OwnerInfo(null, admin);
-    }
-
-    void clearDeviceInitializer() {
-        mDeviceInitializer = null;
-    }
-
-    boolean hasDeviceInitializer() {
-        return mDeviceInitializer != null;
-    }
-
     void setProfileOwner(ComponentName admin, String ownerName, int userId) {
         mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
     }
@@ -252,18 +229,7 @@
                     mDeviceOwner = new OwnerInfo(name, packageName);
                     mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
                 } else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
-                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-                    String initializerComponentStr =
-                            parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
-                    ComponentName admin =
-                            ComponentName.unflattenFromString(initializerComponentStr);
-                    if (admin != null) {
-                        mDeviceInitializer = new OwnerInfo(null, admin);
-                    } else {
-                        mDeviceInitializer = new OwnerInfo(null, packageName);
-                        Slog.e(TAG, "Error parsing device-owner file. Bad component name " +
-                                initializerComponentStr);
-                    }
+                    // Deprecated tag
                 } else if (tag.equals(TAG_PROFILE_OWNER)) {
                     String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
                     String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME);
@@ -444,8 +410,7 @@
 
         @Override
         boolean shouldWrite() {
-            return (mDeviceOwner != null) || (mDeviceInitializer != null)
-                    || (mSystemUpdatePolicy != null);
+            return (mDeviceOwner != null) || (mSystemUpdatePolicy != null);
         }
 
         @Override
@@ -457,9 +422,6 @@
                 out.endTag(null, TAG_DEVICE_OWNER_CONTEXT);
             }
 
-            if (mDeviceInitializer != null) {
-                mDeviceInitializer.writeToXml(out, TAG_DEVICE_INITIALIZER);
-            }
             if (mSystemUpdatePolicy != null) {
                 out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
                 mSystemUpdatePolicy.saveToXml(out);
@@ -488,7 +450,7 @@
                     break;
                 }
                 case TAG_DEVICE_INITIALIZER:
-                    mDeviceInitializer = OwnerInfo.readFromXml(parser);
+                    // Deprecated tag
                     break;
                 case TAG_SYSTEM_UPDATE_POLICY:
                     mSystemUpdatePolicy = SystemUpdatePolicy.restoreFromXml(parser);
@@ -584,7 +546,7 @@
                 } else {
                     // This shouldn't happen but switch from package name -> component name
                     // might have written bad device owner files. b/17652534
-                    Slog.e(TAG, "Error parsing device-owner file. Bad component name " +
+                    Slog.e(TAG, "Error parsing owner file. Bad component name " +
                             componentName);
                 }
             }
@@ -607,11 +569,6 @@
             pw.println(prefix + "  User ID: " + mDeviceOwnerUserId);
             pw.println();
         }
-        if (mDeviceInitializer != null) {
-            pw.println(prefix + "Device Initializer: ");
-            mDeviceInitializer.dump(prefix + "  ", pw);
-            pw.println();
-        }
         if (mSystemUpdatePolicy != null) {
             pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy);
             pw.println();
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java
index 334f708..df44b11 100644
--- a/services/net/java/android/net/dhcp/DhcpAckPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java
@@ -46,7 +46,7 @@
 
         return s + " ACK: your new IP " + mYourIp +
                 ", netmask " + mSubnetMask +
-                ", gateway " + mGateway + dnsServers +
+                ", gateways " + mGateways + dnsServers +
                 ", lease time " + mLeaseTime;
     }
 
@@ -79,7 +79,7 @@
         }
 
         addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateway);
+        addTlv(buffer, DHCP_ROUTER, mGateways);
         addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
         addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
         addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index e0d2ac1..28cb114 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -299,6 +299,7 @@
             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
             Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
             Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
             NetworkUtils.protectFromVpn(mUdpSock);
         } catch(SocketException|ErrnoException e) {
@@ -308,6 +309,16 @@
         return true;
     }
 
+    private boolean connectUdpSock(Inet4Address to) {
+        try {
+            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
+            return true;
+        } catch (SocketException|ErrnoException e) {
+            Log.e(TAG, "Error connecting UDP socket", e);
+            return false;
+        }
+    }
+
     private static void closeQuietly(FileDescriptor fd) {
         try {
             IoBridge.closeAndSignalBlockedThreads(fd);
@@ -325,7 +336,7 @@
         try {
             mNMService.setInterfaceConfig(mIfaceName, ifcg);
         } catch (RemoteException|IllegalStateException e) {
-            Log.e(TAG, "Error configuring IP address : " + e);
+            Log.e(TAG, "Error configuring IP address " + address + ": ", e);
             return false;
         }
         return true;
@@ -345,21 +356,22 @@
         public void run() {
             maybeLog("Receive thread started");
             while (!stopped) {
+                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
                 try {
-                    int length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
                     DhcpPacket packet = null;
                     packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
-                    if (packet != null) {
-                        maybeLog("Received packet: " + packet);
-                        sendMessage(CMD_RECEIVED_PACKET, packet);
-                    } else if (PACKET_DBG) {
-                        Log.d(TAG,
-                                "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length));
-                    }
+                    maybeLog("Received packet: " + packet);
+                    sendMessage(CMD_RECEIVED_PACKET, packet);
                 } catch (IOException|ErrnoException e) {
                     if (!stopped) {
                         Log.e(TAG, "Read error", e);
                     }
+                } catch (DhcpPacket.ParseException e) {
+                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
+                    if (PACKET_DBG) {
+                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
+                    }
                 }
             }
             maybeLog("Receive thread stopped");
@@ -376,8 +388,10 @@
                 maybeLog("Broadcasting " + description);
                 Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
             } else {
-                maybeLog("Unicasting " + description + " to " + to.getHostAddress());
-                Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+                // It's safe to call getpeername here, because we only send unicast packets if we
+                // have an IP address, and we connect the UDP socket in DhcpHaveAddressState#enter.
+                maybeLog("Unicasting " + description + " to " + Os.getpeername(mUdpSock));
+                Os.write(mUdpSock, buf);
             }
         } catch(ErrnoException|IOException e) {
             Log.e(TAG, "Can't send packet: ", e);
@@ -789,6 +803,7 @@
                     transitionTo(mDhcpBoundState);
                 }
             } else if (packet instanceof DhcpNakPacket) {
+                // TODO: Wait a while before returning into INIT state.
                 Log.d(TAG, "Received NAK, returning to INIT");
                 mOffer = null;
                 transitionTo(mDhcpInitState);
@@ -806,10 +821,8 @@
         @Override
         public void enter() {
             super.enter();
-            if (setIpAddress(mDhcpLease.ipAddress)) {
-                maybeLog("Configured IP address " + mDhcpLease.ipAddress);
-            } else {
-                Log.e(TAG, "Failed to configure IP address " + mDhcpLease.ipAddress);
+            if (!setIpAddress(mDhcpLease.ipAddress) ||
+                    !connectUdpSock((mDhcpLease.serverAddress))) {
                 notifyFailure();
                 // There's likely no point in going into DhcpInitState here, we'll probably just
                 // repeat the transaction, get the same IP address as before, and fail.
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
index 7ca7100..99154ef 100644
--- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
@@ -48,7 +48,7 @@
         }
 
         return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
-                dnsServers + ", gateway " + mGateway +
+                dnsServers + ", gateways " + mGateways +
                 " lease time " + mLeaseTime + ", domain " + mDomainName;
     }
 
@@ -81,7 +81,7 @@
         }
 
         addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateway);
+        addTlv(buffer, DHCP_ROUTER, mGateways);
         addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
         addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
         addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index cbf8fc2..8927bfa 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -114,6 +114,11 @@
     protected static final int MAX_LENGTH = 1500;
 
     /**
+     * The magic cookie that identifies this as a DHCP packet instead of BOOTP.
+     */
+    private static final int DHCP_MAGIC_COOKIE = 0x63825363;
+
+    /**
      * DHCP Optional Type: DHCP Subnet Mask
      */
     protected static final byte DHCP_SUBNET_MASK = 1;
@@ -123,7 +128,7 @@
      * DHCP Optional Type: DHCP Router
      */
     protected static final byte DHCP_ROUTER = 3;
-    protected Inet4Address mGateway;
+    protected List <Inet4Address> mGateways;
 
     /**
      * DHCP Optional Type: DHCP DNS Server
@@ -403,7 +408,7 @@
                      (HWADDR_LEN - mClientMac.length) // pad addr to 16 bytes
                      + 64     // empty server host name (64 bytes)
                      + 128);  // empty boot file name (128 bytes)
-        buf.putInt(0x63825363); // magic number
+        buf.putInt(DHCP_MAGIC_COOKIE); // magic number
         finishPacket(buf);
 
         // round up to an even number of octets
@@ -668,6 +673,20 @@
         return new String(bytes, 0, length, StandardCharsets.US_ASCII);
     }
 
+    private static boolean isPacketToOrFromClient(short udpSrcPort, short udpDstPort) {
+        return (udpSrcPort == DHCP_CLIENT) || (udpDstPort == DHCP_CLIENT);
+    }
+
+    private static boolean isPacketServerToServer(short udpSrcPort, short udpDstPort) {
+        return (udpSrcPort == DHCP_SERVER) && (udpDstPort == DHCP_SERVER);
+    }
+
+    public static class ParseException extends Exception {
+        public ParseException(String msg, Object... args) {
+            super(String.format(msg, args));
+        }
+    }
+
     /**
      * Creates a concrete DhcpPacket from the supplied ByteBuffer.  The
      * buffer may have an L2 encapsulation (which is the full EthernetII
@@ -677,7 +696,7 @@
      * A subset of the optional parameters are parsed and are stored
      * in object fields.
      */
-    public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType)
+    public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
     {
         // bootp parameters
         int transactionId;
@@ -687,8 +706,8 @@
         Inet4Address nextIp;
         Inet4Address relayIp;
         byte[] clientMac;
-        List<Inet4Address> dnsServers = new ArrayList<Inet4Address>();
-        Inet4Address gateway = null; // aka router
+        List<Inet4Address> dnsServers = new ArrayList<>();
+        List<Inet4Address> gateways = new ArrayList<>();  // aka router
         Inet4Address serverIdentifier = null;
         Inet4Address netMask = null;
         String message = null;
@@ -720,7 +739,8 @@
         // check to see if we need to parse L2, IP, and UDP encaps
         if (pktType == ENCAP_L2) {
             if (packet.remaining() < MIN_PACKET_LENGTH_L2) {
-                return null;
+                throw new ParseException("L2 packet too short, %d < %d",
+                        packet.remaining(), MIN_PACKET_LENGTH_L2);
             }
 
             byte[] l2dst = new byte[6];
@@ -732,18 +752,20 @@
             short l2type = packet.getShort();
 
             if (l2type != OsConstants.ETH_P_IP)
-                return null;
+                throw new ParseException("Unexpected L2 type 0x%04x, expected 0x%04x",
+                        l2type, OsConstants.ETH_P_IP);
         }
 
         if (pktType <= ENCAP_L3) {
             if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
-                return null;
+                throw new ParseException("L3 packet too short, %d < %d",
+                        packet.remaining(), MIN_PACKET_LENGTH_L3);
             }
 
             byte ipTypeAndLength = packet.get();
             int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
             if (ipVersion != 4) {
-                return null;
+                throw new ParseException("Invalid IP version %d", ipVersion);
             }
 
             // System.out.println("ipType is " + ipType);
@@ -759,8 +781,9 @@
             ipSrc = readIpAddress(packet);
             ipDst = readIpAddress(packet);
 
-            if (ipProto != IP_TYPE_UDP) // UDP
-                return null;
+            if (ipProto != IP_TYPE_UDP) {
+                throw new ParseException("Protocol not UDP: %d", ipProto);
+            }
 
             // Skip options. This cannot cause us to read beyond the end of the buffer because the
             // IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than
@@ -776,13 +799,19 @@
             short udpLen = packet.getShort();
             short udpChkSum = packet.getShort();
 
-            if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT))
+            // Only accept packets to or from the well-known client port (expressly permitting
+            // packets from ports other than the well-known server port; http://b/24687559), and
+            // server-to-server packets, e.g. for relays.
+            if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) &&
+                !isPacketServerToServer(udpSrcPort, udpDstPort)) {
                 return null;
+            }
         }
 
         // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length.
         if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) {
-            return null;
+            throw new ParseException("Invalid type or BOOTP packet too short, %d < %d",
+                        packet.remaining(), MIN_PACKET_LENGTH_BOOTP);
         }
 
         byte type = packet.get();
@@ -805,7 +834,7 @@
             packet.get(ipv4addr);
             relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
         } catch (UnknownHostException ex) {
-            return null;
+            throw new ParseException("Invalid IPv4 address: %s", Arrays.toString(ipv4addr));
         }
 
         // Some DHCP servers have been known to announce invalid client hardware address values such
@@ -828,8 +857,10 @@
 
         int dhcpMagicCookie = packet.getInt();
 
-        if (dhcpMagicCookie !=  0x63825363)
-            return null;
+        if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
+            throw new ParseException("Bad magic cookie 0x%08x, should be 0x%08x", dhcpMagicCookie,
+                    DHCP_MAGIC_COOKIE);
+        }
 
         // parse options
         boolean notFinishedOptions = true;
@@ -852,8 +883,9 @@
                             expectedLen = 4;
                             break;
                         case DHCP_ROUTER:
-                            gateway = readIpAddress(packet);
-                            expectedLen = 4;
+                            for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
+                                gateways.add(readIpAddress(packet));
+                            }
                             break;
                         case DHCP_DNS_SERVER:
                             for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
@@ -937,18 +969,20 @@
                     }
 
                     if (expectedLen != optionLen) {
-                        return null;
+                        throw new ParseException("Invalid length %d for option %d, expected %d",
+                                optionLen, optionType, expectedLen);
                     }
                 }
             } catch (BufferUnderflowException e) {
-                return null;
+                throw new ParseException("BufferUnderflowException");
             }
         }
 
         DhcpPacket newPacket;
 
         switch(dhcpType) {
-            case -1: return null;
+            case (byte) 0xFF:
+                throw new ParseException("No DHCP message type option");
             case DHCP_MESSAGE_TYPE_DISCOVER:
                 newPacket = new DhcpDiscoverPacket(
                     transactionId, secs, clientMac, broadcast);
@@ -981,14 +1015,13 @@
                     clientMac);
                 break;
             default:
-                System.out.println("Unimplemented type: " + dhcpType);
-                return null;
+                throw new ParseException("Unimplemented DHCP type %d", dhcpType);
         }
 
         newPacket.mBroadcastAddress = bcAddr;
         newPacket.mDnsServers = dnsServers;
         newPacket.mDomainName = domainName;
-        newPacket.mGateway = gateway;
+        newPacket.mGateways = gateways;
         newPacket.mHostName = hostName;
         newPacket.mLeaseTime = leaseTime;
         newPacket.mMessage = message;
@@ -1009,7 +1042,7 @@
      * Parse a packet from an array of bytes, stopping at the given length.
      */
     public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType)
-    {
+            throws ParseException {
         ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
         return decodeFullPacket(buffer, pktType);
     }
@@ -1044,7 +1077,11 @@
         } catch (IllegalArgumentException e) {
             return null;
         }
-        results.gateway = mGateway;
+
+        if (mGateways.size() > 0) {
+            results.gateway = mGateways.get(0);
+        }
+
         results.dnsServers.addAll(mDnsServers);
         results.domains = mDomainName;
         results.serverAddress = mServerIdentifier;
@@ -1086,11 +1123,11 @@
     public static ByteBuffer buildOfferPacket(int encap, int transactionId,
         boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
         byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
-        Inet4Address gateway, List<Inet4Address> dnsServers,
+        List<Inet4Address> gateways, List<Inet4Address> dnsServers,
         Inet4Address dhcpServerIdentifier, String domainName) {
         DhcpPacket pkt = new DhcpOfferPacket(
             transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
-        pkt.mGateway = gateway;
+        pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
         pkt.mDomainName = domainName;
@@ -1106,11 +1143,11 @@
     public static ByteBuffer buildAckPacket(int encap, int transactionId,
         boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
         byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
-        Inet4Address gateway, List<Inet4Address> dnsServers,
+        List<Inet4Address> gateways, List<Inet4Address> dnsServers,
         Inet4Address dhcpServerIdentifier, String domainName) {
         DhcpPacket pkt = new DhcpAckPacket(
             transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
-        pkt.mGateway = gateway;
+        pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
         pkt.mDomainName = domainName;
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index cd3b8bb..7e60bf1 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -117,7 +117,7 @@
 
     private void assertDomainAndVendorInfoParses(
             String expectedDomain, byte[] domainBytes,
-            String expectedVendorInfo, byte[] vendorInfoBytes) {
+            String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception {
         ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER)
                 .setDomainBytes(domainBytes)
                 .setVendorInfoBytes(vendorInfoBytes)
@@ -158,17 +158,25 @@
     }
 
     private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime,
-                                       long leaseTimeMillis, byte[] leaseTimeBytes) {
+            long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception {
         TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER);
         if (leaseTimeBytes != null) {
             testPacket.setLeaseTimeBytes(leaseTimeBytes);
         }
         ByteBuffer packet = testPacket.build();
-        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+        DhcpPacket offerPacket = null;
+
         if (!expectValid) {
-            assertNull(offerPacket);
+            try {
+                offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+                fail("Invalid packet parsed successfully: " + offerPacket);
+            } catch (ParseException expected) {
+            }
             return;
         }
+
+        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
+        assertNotNull(offerPacket);
         assertEquals(rawLeaseTime, offerPacket.mLeaseTime);
         DhcpResults dhcpResults = offerPacket.toDhcpResults();  // Just check this doesn't crash.
         assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis());
@@ -200,14 +208,14 @@
     }
 
     private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp,
-                                byte[] netmaskBytes) {
+                                byte[] netmaskBytes) throws Exception {
         checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes);
         checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes);
     }
 
     private void checkIpAddress(String expected, byte type,
                                 Inet4Address clientIp, Inet4Address yourIp,
-                                byte[] netmaskBytes) {
+                                byte[] netmaskBytes) throws Exception {
         ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp)
                 .setNetmaskBytes(netmaskBytes)
                 .build();
@@ -506,4 +514,74 @@
         assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
                 "lancs.ac.uk", "10.32.255.128", null, 7200, false, dhcpResults);
     }
+
+    @SmallTest
+    public void testUdpServerAnySourcePort() throws Exception {
+        final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+            // Ethernet header.
+            "9cd917000000001c2e0000000800" +
+            // IP header.
+            "45a00148000040003d115087d18194fb0a0f7af2" +
+            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
+            // NOTE: The server source port is not the canonical port 67.
+            "C29F004401341268" +
+            // BOOTP header.
+            "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
+            // MAC address.
+            "9cd91700000000000000000000000000" +
+            // Server name.
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            // File.
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            // Options.
+            "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
+            "d18180060f0777766d2e6564751c040a0fffffff000000"
+        ).toCharArray(), false));
+
+        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
+        assertTrue(offerPacket instanceof DhcpOfferPacket);
+        assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
+        DhcpResults dhcpResults = offerPacket.toDhcpResults();
+        assertDhcpResults("10.15.122.242/16", "10.15.200.23",
+                "209.129.128.3,209.129.148.3,209.129.128.6",
+                "wvm.edu", "10.1.105.252", null, 86400, false, dhcpResults);
+    }
+
+    @SmallTest
+    public void testMultipleRouters() throws Exception {
+        final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode((
+            // Ethernet header.
+            "fc3d93000000" + "081735000000" + "0800" +
+            // IP header.
+            "45000148c2370000ff117ac2c0a8bd02ffffffff" +
+            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
+            "0043004401343beb" +
+            // BOOTP header.
+            "0201060027f518e20000800000000000c0a8bd310000000000000000" +
+            // MAC address.
+            "fc3d9300000000000000000000000000" +
+            // Server name.
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            // File.
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000" +
+            // Options.
+            "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
+            "0308c0a8bd01ffffff0006080808080808080404ff000000000000"
+        ).toCharArray(), false));
+
+        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
+        assertTrue(offerPacket instanceof DhcpOfferPacket);
+        assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
+        DhcpResults dhcpResults = offerPacket.toDhcpResults();
+        assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
+                null, "192.171.189.2", null, 28800, false, dhcpResults);
+    }
 }
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/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index f4ffe2e..b109e7b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -225,5 +225,45 @@
         boolean userManagerIsSplitSystemUser() {
             return context.userManagerForMock.isSplitSystemUser();
         }
+
+        @Override
+        int settingsSecureGetIntForUser(String name, int def, int userHandle) {
+            return context.settings.settingsSecureGetIntForUser(name, def, userHandle);
+        }
+
+        @Override
+        void settingsSecurePutIntForUser(String name, int value, int userHandle) {
+            context.settings.settingsSecurePutIntForUser(name, value, userHandle);
+        }
+
+        @Override
+        void settingsSecurePutStringForUser(String name, String value, int userHandle) {
+            context.settings.settingsSecurePutStringForUser(name, value, userHandle);
+        }
+
+        @Override
+        void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
+            context.settings.settingsGlobalPutStringForUser(name, value, userHandle);
+        }
+
+        @Override
+        void settingsSecurePutInt(String name, int value) {
+            context.settings.settingsSecurePutInt(name, value);
+        }
+
+        @Override
+        void settingsGlobalPutInt(String name, int value) {
+            context.settings.settingsGlobalPutInt(name, value);
+        }
+
+        @Override
+        void settingsSecurePutString(String name, String value) {
+            context.settings.settingsSecurePutString(name, value);
+        }
+
+        @Override
+        void settingsGlobalPutString(String name, String value) {
+            context.settings.settingsGlobalPutString(name, value);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 5b23798..d6a60c7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -16,6 +16,7 @@
 package com.android.server.devicepolicy;
 
 import com.android.server.LocalServices;
+import com.android.server.SystemService;
 
 import android.Manifest.permission;
 import android.app.Activity;
@@ -31,8 +32,8 @@
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.content.pm.PackageInfo;
-import android.content.pm.UserInfo;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Pair;
 
 import org.mockito.ArgumentCaptor;
@@ -44,6 +45,7 @@
 import java.util.Map;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
@@ -65,8 +67,6 @@
  (mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
  */
 public class DevicePolicyManagerTest extends DpmTestBase {
-
-
     private DpmMockContext mContext;
     public DevicePolicyManager dpm;
     public DevicePolicyManagerServiceTestable dpms;
@@ -83,9 +83,7 @@
         when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
                 .thenReturn(true);
 
-        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-        dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
-        dpm = new DevicePolicyManagerTestable(mContext, dpms);
+        initializeDpms();
 
         admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
         admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
@@ -95,19 +93,36 @@
         setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
 
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
-                DpmMockContext.CALLER_UID);
-
-        setUpPackageInfo();
         setUpUserManager();
     }
 
-    /**
-     * Set up a mock result for {@link PackageManager#queryBroadcastReceivers}.  We'll return
-     * the actual ResolveInfo for the admin component, but we need to mock PM so it'll return
-     * it for user {@link DpmMockContext#CALLER_USER_HANDLE}.
-     */
-    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) {
+    private void initializeDpms() {
+        // Need clearCallingIdentity() to pass permission checks.
+        final long ident = mContext.binder.clearCallingIdentity();
+        try {
+            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+            dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+
+            dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+            dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+
+            dpm = new DevicePolicyManagerTestable(mContext, dpms);
+        } finally {
+            mContext.binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception {
+        setUpPackageManagerForAdmin(admin, packageUid,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+    }
+
+    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
+            int enabledSetting) throws Exception {
+
+        // Set up queryBroadcastReceivers().
+
         final Intent resolveIntent = new Intent();
         resolveIntent.setComponent(admin);
         final List<ResolveInfo> realResolveInfo =
@@ -128,13 +143,9 @@
                 eq(PackageManager.GET_META_DATA
                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
                 eq(UserHandle.getUserId(packageUid)));
-    }
 
-    /**
-     * Set up a mock result for {@link IPackageManager#getApplicationInfo} for user
-     * {@link DpmMockContext#CALLER_USER_HANDLE}.
-     */
-    private void setUpApplicationInfo(int enabledSetting, int packageUid) throws Exception {
+        // Set up getApplicationInfo().
+
         final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
                 mRealTestContext.getPackageManager().getApplicationInfo(
                         admin1.getPackageName(),
@@ -147,25 +158,20 @@
                 eq(admin1.getPackageName()),
                 eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
                 eq(UserHandle.getUserId(packageUid)));
-    }
 
-    /**
-     * Set up a mock result for {@link IPackageManager#getPackageInfo(String, int, int)} for user
-     * {@link DpmMockContext#CALLER_USER_HANDLE} as well as the system user.
-     */
-    private void setUpPackageInfo() throws Exception {
-        final PackageInfo pi = mRealTestContext.getPackageManager().getPackageInfo(
-                admin1.getPackageName(), 0);
+        // Set up getPackageInfo().
+
+        final PackageInfo pi = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getPackageInfo(
+                        admin1.getPackageName(), 0));
         assertTrue(pi.applicationInfo.flags != 0);
 
+        pi.applicationInfo.uid = packageUid;
+
         doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
                 eq(admin1.getPackageName()),
                 eq(0),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
-        doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
-                eq(admin1.getPackageName()),
-                eq(0),
-                eq(UserHandle.USER_SYSTEM));
+                eq(UserHandle.getUserId(packageUid)));
     }
 
     private void setUpUserManager() {
@@ -207,9 +213,7 @@
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
 
-        final UserInfo uh = new UserInfo(DpmMockContext.CALLER_USER_HANDLE, "user", 0);
-
-        // DO needs to be an DA.
+        // PO needs to be an DA.
         dpm.setActiveAdmin(admin, /* replace =*/ false);
 
         // Fire!
@@ -307,8 +311,8 @@
 
         // Next, add one more admin.
         // Before doing so, update the application info, now it's enabled.
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                DpmMockContext.CALLER_UID);
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID,
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
 
         dpm.setActiveAdmin(admin2, /* replace =*/ false);
 
@@ -362,8 +366,6 @@
 
         // Set up pacakge manager for the other user.
         setUpPackageManagerForAdmin(admin2, ANOTHER_ADMIN_UID);
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
-                ANOTHER_ADMIN_UID);
 
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
 
@@ -536,14 +538,12 @@
 
         // Make sure admin1 is installed on system user.
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
-                DpmMockContext.CALLER_SYSTEM_USER_UID);
 
         // DO needs to be an DA.
         dpm.setActiveAdmin(admin1, /* replace =*/ false);
 
         // Fire!
-        assertTrue(dpm.setDeviceOwner(admin1.getPackageName(), "owner-name"));
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
 
         // Verify internal calls.
         verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
@@ -575,7 +575,7 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
 
         try {
-            dpm.setDeviceOwner("a.b.c");
+            dpm.setDeviceOwner(new ComponentName("a.b.c", ".def"));
             fail("Didn't throw IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -585,6 +585,85 @@
         // TODO Test more failure cases.  Basically test all chacks in enforceCanSetDeviceOwner().
     }
 
+    public void testClearDeviceOwner() throws Exception {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // Set admin1 as a DA to the secondary user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+        // Set admin 1 as the DO to the system user.
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
+
+        // Verify internal calls.
+        verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+                eq(admin1.getPackageName()));
+
+        assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
+
+        // Set up other mocks.
+        when(mContext.userManager.getUserRestrictions()).thenReturn(new Bundle());
+
+        // Now call clear.
+        doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager).getPackageUid(
+                eq(admin1.getPackageName()),
+                anyInt());
+        dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+        // Now DO shouldn't be set.
+        assertNull(dpm.getDeviceOwner());
+
+        // TODO Check other calls.
+    }
+
+    public void testClearDeviceOwner_fromDifferentUser() throws Exception {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // Set admin1 as a DA to the secondary user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+        // Set admin 1 as the DO to the system user.
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
+
+        // Verify internal calls.
+        verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+                eq(admin1.getPackageName()));
+
+        assertEquals(admin1.getPackageName(), dpm.getDeviceOwner());
+
+        // Now call clear from the secondary user, which should throw.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        // Now call clear.
+        doReturn(DpmMockContext.CALLER_UID).when(mContext.packageManager).getPackageUid(
+                eq(admin1.getPackageName()),
+                anyInt());
+        try {
+            dpm.clearDeviceOwnerApp(admin1.getPackageName());
+            fail("Didn't throw");
+        } catch (SecurityException e) {
+            assertEquals("clearDeviceOwner can only be called by the device owner", e.getMessage());
+        }
+
+        // Now DO shouldn't be set.
+        assertNotNull(dpm.getDeviceOwner());
+    }
+
     public void testSetProfileOwner() throws Exception {
         setAsProfileOwner(admin1);
     }
@@ -593,6 +672,85 @@
         // TODO Test more failure cases.  Basically test all chacks in enforceCanSetProfileOwner().
     }
 
+    public void testGetDeviceOwnerAdminLocked() throws Exception {
+        checkDeviceOwnerWithMultipleDeviceAdmins();
+    }
+
+    private void checkDeviceOwnerWithMultipleDeviceAdmins() throws Exception {
+        // In ths test, we use 3 users (system + 2 secondary users), set some device admins to them,
+        // set admin2 on CALLER_USER_HANDLE as DO, then call getDeviceOwnerAdminLocked() to
+        // make sure it gets the right component from the right user.
+
+        final int ANOTHER_USER_ID = 100;
+        final int ANOTHER_ADMIN_UID = UserHandle.getUid(ANOTHER_USER_ID, 456);
+
+        mMockContext.addUser(ANOTHER_USER_ID, 0); // Add one more user.
+
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Make sure the admin packge is installed to each user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
+
+        setUpPackageManagerForAdmin(admin2, ANOTHER_ADMIN_UID);
+
+
+        // Set active admins to the users.
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+        dpm.setActiveAdmin(admin3, /* replace =*/ false);
+
+        dpm.setActiveAdmin(admin1, /* replace =*/ false, DpmMockContext.CALLER_USER_HANDLE);
+        dpm.setActiveAdmin(admin2, /* replace =*/ false, DpmMockContext.CALLER_USER_HANDLE);
+
+        dpm.setActiveAdmin(admin2, /* replace =*/ false, ANOTHER_USER_ID);
+
+        // Set DO on the first non-system user.
+        mContext.setUserRunning(DpmMockContext.CALLER_USER_HANDLE, true);
+        assertTrue(dpm.setDeviceOwner(admin2, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
+
+        // Make sure it's set.
+        assertEquals(admin2, dpm.getDeviceOwnerComponent());
+
+        // Then check getDeviceOwnerAdminLocked().
+        assertEquals(admin2, dpms.getDeviceOwnerAdminLocked().info.getComponent());
+        assertEquals(DpmMockContext.CALLER_UID, dpms.getDeviceOwnerAdminLocked().getUid());
+    }
+
+    /**
+     * This essentially tests
+     * {@code DevicePolicyManagerService.findOwnerComponentIfNecessaryLocked()}. (which is private.)
+     *
+     * We didn't use to persist the DO component class name, but now we do, and the above method
+     * finds the right component from a package name upon migration.
+     */
+    public void testDeviceOwnerMigration() throws Exception {
+        checkDeviceOwnerWithMultipleDeviceAdmins();
+
+        // Overwrite the device owner setting and clears the clas name.
+        dpms.mOwners.setDeviceOwner(
+                new ComponentName(admin2.getPackageName(), ""),
+                "owner-name", DpmMockContext.CALLER_USER_HANDLE);
+        dpms.mOwners.writeDeviceOwner();
+
+        // Make sure the DO component name doesn't have a class name.
+        assertEquals("", dpms.getDeviceOwner().getClassName());
+
+        // Then create a new DPMS to have it load the settings from files.
+        initializeDpms();
+
+        // Now the DO component name is a full name.
+        // *BUT* because both admin1 and admin2 belong to the same package, we think admin1 is the
+        // DO.
+        assertEquals(admin1, dpms.getDeviceOwner());
+    }
+
     public void testSetGetApplicationRestriction() {
         setAsProfileOwner(admin1);
 
@@ -625,4 +783,95 @@
         dpm.setApplicationRestrictions(admin1, "pkg2", new Bundle());
         assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg2").size());
     }
+
+    public void testSetUserRestriction_asDo() throws Exception {
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // First, set DO.
+
+        // Call from a process on the system user.
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Make sure admin1 is installed on system user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+        // Call.
+        dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
+                UserHandle.USER_SYSTEM));
+
+        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_SMS));
+        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+
+        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_SMS));
+        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
+
+        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_SMS));
+        assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+
+        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_SMS));
+        assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        // TODO Check inner calls.
+        // TODO Make sure restrictions are written to the file.
+    }
+
+    public void testSetUserRestriction_asPo() {
+        setAsProfileOwner(admin1);
+
+        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
+        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+
+        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
+        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+
+        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
+        assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+
+        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
+        assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                .ensureUserRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+        // TODO Check inner calls.
+        // TODO Make sure restrictions are written to the file.
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 7b36e88..d1b4835 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -22,6 +22,7 @@
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -35,6 +36,7 @@
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.view.IWindowManager;
 
@@ -158,6 +160,33 @@
         }
     }
 
+    public static class SettingsForMock {
+        int settingsSecureGetIntForUser(String name, int def, int userHandle) {
+            return 0;
+        }
+
+        void settingsSecurePutIntForUser(String name, int value, int userHandle) {
+        }
+
+        void settingsSecurePutStringForUser(String name, String value, int userHandle) {
+        }
+
+        void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
+        }
+
+        void settingsSecurePutInt(String name, int value) {
+        }
+
+        void settingsGlobalPutInt(String name, int value) {
+        }
+
+        void settingsSecurePutString(String name, String value) {
+        }
+
+        void settingsGlobalPutString(String name, String value) {
+        }
+    }
+
     public final Context realTestContext;
 
     /**
@@ -184,6 +213,8 @@
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
+    public final SettingsForMock settings;
+    public final MockContentResolver contentResolver;
 
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -212,18 +243,20 @@
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
+        settings = mock(SettingsForMock.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(context.getPackageManager());
 
         spiedContext = mock(Context.class);
 
+        contentResolver = new MockContentResolver();
+
         // Add the system user
         systemUserDataDir = addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY);
 
         // System user is always running.
-        when(userManager.isUserRunning(MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)))
-                .thenReturn(true);
+        setUserRunning(UserHandle.USER_SYSTEM, true);
     }
 
     public File addUser(int userId, int flags) {
@@ -252,6 +285,11 @@
         }
     }
 
+    public void setUserRunning(int userId, boolean isRunning) {
+        when(userManager.isUserRunning(MockUtils.checkUserHandle(userId)))
+                .thenReturn(isRunning);
+    }
+
     @Override
     public Object getSystemService(String name) {
         switch (name) {
@@ -444,4 +482,9 @@
     public void unregisterReceiver(BroadcastReceiver receiver) {
         spiedContext.unregisterReceiver(receiver);
     }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return contentResolver;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 4a39614..79845d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import java.io.BufferedReader;
@@ -28,6 +29,8 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 
 import static org.mockito.Mockito.when;
@@ -94,7 +97,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
         }
@@ -106,7 +108,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
         }
@@ -139,7 +140,6 @@
             assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName());
             assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId());
 
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
         }
@@ -154,7 +154,6 @@
             assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName());
             assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId());
 
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
         }
@@ -184,7 +183,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
 
             assertEquals(2, owners.getProfileOwnerKeys().size());
@@ -207,7 +205,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertNull(owners.getSystemUpdatePolicy());
 
             assertEquals(2, owners.getProfileOwnerKeys().size());
@@ -251,8 +248,6 @@
             assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName());
             assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId());
 
-            assertTrue(owners.hasDeviceInitializer());
-            assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName());
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
 
@@ -279,8 +274,6 @@
             assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName());
             assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId());
 
-            assertTrue(owners.hasDeviceInitializer());
-            assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName());
             assertNotNull(owners.getSystemUpdatePolicy());
             assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
 
@@ -313,7 +306,8 @@
             // The legacy file should be removed.
             assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
 
-            assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
+            // Note device initializer is no longer supported.  No need to write the DO file.
+            assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
 
             assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
             assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
@@ -322,8 +316,6 @@
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
 
-            assertTrue(owners.hasDeviceInitializer());
-            assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName());
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
@@ -337,8 +329,6 @@
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
 
-            assertTrue(owners.hasDeviceInitializer());
-            assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName());
 
             assertNull(owners.getSystemUpdatePolicy());
             assertEquals(0, owners.getProfileOwnerKeys().size());
@@ -368,7 +358,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertEquals(0, owners.getProfileOwnerKeys().size());
 
             assertNotNull(owners.getSystemUpdatePolicy());
@@ -382,7 +371,6 @@
 
             assertFalse(owners.hasDeviceOwner());
             assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
-            assertFalse(owners.hasDeviceInitializer());
             assertEquals(0, owners.getProfileOwnerKeys().size());
 
             assertNotNull(owners.getSystemUpdatePolicy());
@@ -408,7 +396,6 @@
         assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
 
         // Then clear all information and save.
-        owners.clearDeviceInitializer();
         owners.clearDeviceOwner();
         owners.clearSystemUpdatePolicy();
         owners.removeProfileOwner(10);
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/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8b37ec4..13e600e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -314,6 +314,8 @@
                 mAppIdleParoled = paroled;
                 if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleParoled);
                 if (paroled) {
+                    postParoleEndTimeout();
+                } else {
                     mLastAppIdleParoledTime = checkAndGetTimeLocked();
                     postNextParoleTimeout();
                 }
@@ -404,8 +406,6 @@
                 if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
                     if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
                     setAppIdleParoled(true);
-                    // Make sure it ends at some point
-                    postParoleEndTimeout();
                 } else {
                     if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
                     postNextParoleTimeout();
@@ -492,7 +492,6 @@
             if (!deviceIdle
                     && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
-                postNextParoleTimeout();
                 setAppIdleParoled(true);
             } else if (deviceIdle) {
                 if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index 3ca0c84..31d859f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -87,6 +87,7 @@
     // This is an indirect indication of the microphone being open in some other application.
     private boolean mServiceDisabled = false;
     private boolean mStarted = false;
+    private boolean mRecognitionAborted = false;
     private PowerSaveModeListener mPowerSaveModeListener;
 
     SoundTriggerHelper(Context context) {
@@ -386,8 +387,9 @@
 
     private void onRecognitionAbortLocked() {
         Slog.w(TAG, "Recognition aborted");
-        // No-op
-        // This is handled via service state changes instead.
+        // If abort has been called, the hardware has already stopped recognition, so we shouldn't
+        // call it again when we process the state change.
+        mRecognitionAborted = true;
     }
 
     private void onRecognitionFailureLocked() {
@@ -490,8 +492,13 @@
             }
             return status;
         } else {
-            // Stop recognition.
-            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            // Stop recognition (only if we haven't been aborted).
+            int status = STATUS_OK;
+            if (!mRecognitionAborted) {
+                status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            } else {
+                mRecognitionAborted = false;
+            }
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "stopRecognition call failed with " + status);
                 if (notify) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a8874d0..a96c164 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -925,7 +925,7 @@
                 return;
             }
             synchronized (this) {
-                pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
+                pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)");
                 pw.println("  mEnableService: " + mEnableService);
                 if (mImpl == null) {
                     pw.println("  (No active implementation)");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 28520be..30296e1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -36,6 +36,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionServiceInfo;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.view.IWindowManager;
 
@@ -114,9 +115,9 @@
         mAm = ActivityManagerNative.getDefault();
         VoiceInteractionServiceInfo info;
         try {
-            info = new VoiceInteractionServiceInfo(context.getPackageManager(), service);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Voice interaction service not found: " + service);
+            info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
+        } catch (RemoteException|PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Voice interaction service not found: " + service, e);
             mInfo = null;
             mSessionComponentName = null;
             mIWindowManager = null;
@@ -260,9 +261,18 @@
             }
             return;
         }
+        pw.print("  mUser="); pw.println(mUser);
         pw.print("  mComponent="); pw.println(mComponent.flattenToShortString());
         pw.print("  Session service="); pw.println(mInfo.getSessionService());
+        pw.println("  Service info:");
+        mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), "    ");
+        pw.println("  Application info:");
+        mInfo.getServiceInfo().applicationInfo.dump(new PrintWriterPrinter(pw), "    ");
+        pw.print("  Recognition service="); pw.println(mInfo.getRecognitionService());
         pw.print("  Settings activity="); pw.println(mInfo.getSettingsActivity());
+        pw.print("  Supports assist="); pw.println(mInfo.getSupportsAssist());
+        pw.print("  Supports launch from keyguard=");
+        pw.println(mInfo.getSupportsLaunchFromKeyguard());
         if (mDisabledShowContext != 0) {
             pw.print("  mDisabledShowContext=");
             pw.println(Integer.toHexString(mDisabledShowContext));
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index cdb0bf2..5ecd2b5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -51,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
@@ -121,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";
@@ -160,6 +195,7 @@
     private final CharSequence mShortDescription;
     private final List<String> mSupportedUriSchemes;
     private final Icon mIcon;
+    private final Bundle mExtras;
     private boolean mIsEnabled;
 
     /**
@@ -175,6 +211,7 @@
         private CharSequence mShortDescription;
         private List<String> mSupportedUriSchemes = new ArrayList<String>();
         private Icon mIcon;
+        private Bundle mExtras;
         private boolean mIsEnabled = false;
 
         /**
@@ -202,6 +239,7 @@
             mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
             mIcon = phoneAccount.getIcon();
             mIsEnabled = phoneAccount.isEnabled();
+            mExtras = phoneAccount.getExtras();
         }
 
         /**
@@ -300,6 +338,20 @@
         }
 
         /**
+         * Specifies the extras associated with the {@link PhoneAccount}.
+         * <p>
+         * {@code PhoneAccount}s only support extra values of type: {@link String}, {@link Integer},
+         * and {@link Boolean}.  Extras which are not of these types are ignored.
+         *
+         * @param extras
+         * @return
+         */
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Sets the enabled state of the phone account.
          *
          * @param isEnabled The enabled state.
@@ -332,6 +384,7 @@
                     mLabel,
                     mShortDescription,
                     mSupportedUriSchemes,
+                    mExtras,
                     mIsEnabled);
         }
     }
@@ -346,6 +399,7 @@
             CharSequence label,
             CharSequence shortDescription,
             List<String> supportedUriSchemes,
+            Bundle extras,
             boolean isEnabled) {
         mAccountHandle = account;
         mAddress = address;
@@ -356,6 +410,7 @@
         mLabel = label;
         mShortDescription = shortDescription;
         mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
+        mExtras = extras;
         mIsEnabled = isEnabled;
     }
 
@@ -455,6 +510,18 @@
     }
 
     /**
+     * The extras associated with this {@code PhoneAccount}.
+     * <p>
+     * A {@link ConnectionService} may provide implementation specific information about the
+     * {@link PhoneAccount} via the extras.
+     *
+     * @return The extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
      * The icon to represent this {@code PhoneAccount}.
      *
      * @return The icon.
@@ -553,6 +620,7 @@
             mIcon.writeToParcel(out, flags);
         }
         out.writeByte((byte) (mIsEnabled ? 1 : 0));
+        out.writeBundle(mExtras);
     }
 
     public static final Creator<PhoneAccount> CREATOR
@@ -595,6 +663,7 @@
             mIcon = null;
         }
         mIsEnabled = in.readByte() == 1;
+        mExtras = in.readBundle();
     }
 
     @Override
@@ -610,6 +679,8 @@
             sb.append(scheme)
                     .append(" ");
         }
+        sb.append(" Extras: ");
+        sb.append(mExtras);
         sb.append("]");
         return sb.toString();
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a8f2aca..8b347cc 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -447,6 +447,8 @@
 
     private final Context mContext;
 
+    private final ITelecomService mTelecomServiceOverride;
+
     /**
      * @hide
      */
@@ -458,12 +460,20 @@
      * @hide
      */
     public TelecomManager(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * @hide
+     */
+    public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
         Context appContext = context.getApplicationContext();
         if (appContext != null) {
             mContext = appContext;
         } else {
             mContext = context;
         }
+        mTelecomServiceOverride = telecomServiceImpl;
     }
 
     /**
@@ -1340,6 +1350,9 @@
     }
 
     private ITelecomService getTelecomService() {
+        if (mTelecomServiceOverride != null) {
+            return mTelecomServiceOverride;
+        }
         return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e57964a..8ebef32 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -191,7 +191,7 @@
     public static final String
             KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
 
-   /**
+    /**
      * Override the platform's notion of a network operator being considered roaming.
      * Value is string array of MCCMNCs to be considered roaming for 3GPP RATs.
      */
@@ -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
@@ -304,6 +311,31 @@
             "carrier_instant_lettering_escaped_chars_string";
 
     /**
+     * When IMS instant lettering is available for a carrier (see
+     * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the character encoding
+     * which will be used when determining the length of messages.  Used in the InCall UI to limit
+     * the number of characters the user may type.  If empty-string, the instant lettering
+     * 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 bye.  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.
+     * @hide
+     */
+    public static final String KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING =
+            "carrier_instant_lettering_encoding_string";
+
+    /**
+     * When IMS instant lettering is available for a carrier (see
+     * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), the length limit for messages.  Used
+     * in the InCall UI to ensure the user cannot enter more characters than allowed by the carrier.
+     * See also {@link #KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING} for more information on how
+     * the length of the message is calculated.
+     * @hide
+     */
+    public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT =
+            "carrier_instant_lettering_length_limit_int";
+
+    /**
      * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0
      * this is the value that should be used instead. A configuration value of
      * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default
@@ -443,6 +475,12 @@
      */
     public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
 
+    /**
+     * Allow user to add APNs
+     * @hide
+     */
+    public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
+
     // These variables are used by the MMS service and exposed through another API, {@link
     // SmsManager}. The variable names and string values are copied from there.
     public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -504,10 +542,13 @@
         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, "");
         sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING, "");
+        sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING, "");
+        sDefaults.putInt(KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT, 64);
         sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false);
         sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true);
@@ -540,6 +581,7 @@
         sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, "");
         sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, "");
         sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false);
+        sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
 
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index f535e5d..fced667 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -911,7 +911,7 @@
         else if (tdScdmaDbm >= -49) level = SIGNAL_STRENGTH_GREAT;
         else if (tdScdmaDbm >= -73) level = SIGNAL_STRENGTH_GOOD;
         else if (tdScdmaDbm >= -97) level = SIGNAL_STRENGTH_MODERATE;
-        else if (tdScdmaDbm >= -120) level = SIGNAL_STRENGTH_POOR;
+        else if (tdScdmaDbm >= -110) level = SIGNAL_STRENGTH_POOR;
         else level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
 
         if (DBG) log("getTdScdmaLevel = " + level);
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/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f563839..d2e4de3 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -144,28 +144,6 @@
             = "android.intent.action.ANY_DATA_STATE";
 
     /**
-     * Broadcast Action: Occurs when a data connection connects to a provisioning apn
-     * and is broadcast by the low level data connection code.
-     * The intent will have the following extra values:</p>
-     * <dl>
-     *   <dt>apn</dt><dd>A string that is the APN associated with this connection.</dd>
-     *   <dt>apnType</dt><dd>A string array of APN types associated with this connection.
-     *      The APN type {@code *} is a special type that means this APN services all types.</dd>
-     *   <dt>linkProperties</dt><dd>{@code LinkProperties} for this APN.</dd>
-     *   <dt>linkCapabilities</dt><dd>The {@code LinkCapabilities} for this APN.</dd>
-     *   <dt>iface</dt><dd>A string that is the name of the interface.</dd>
-     * </dl>
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN
-            = "android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN";
-
-    /**
      * Broadcast Action: An attempt to establish a data connection has failed.
      * The intent will have the following extra values:</p>
      * <dl>
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
index e3e1d34..b4e0c70 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
@@ -106,12 +106,12 @@
 
     class CompositorScore {
         double mSurfaces;
-        double mBitrate;
+        double mBandwidth;
 
         @Override
         public String toString() {
             return DOUBLE_FORMAT.format(mSurfaces) + " surfaces. " +
-                    "Bitrate: " + getReadableMemory((long)mBitrate) + "/s";
+                    "Bandwidth: " + getReadableMemory((long)mBandwidth) + "/s";
         }
     }
 
@@ -131,7 +131,7 @@
         score.mSurfaces = measureCompositionScore(new Measurement(0, 60.0),
                 new Measurement(mViews.size() + 1, 0.0f), pixelFormat);
         // Assume 32 bits per pixel.
-        score.mBitrate = score.mSurfaces * mTargetFPS * mWidth * mHeight * 4.0;
+        score.mBandwidth = score.mSurfaces * mTargetFPS * mWidth * mHeight * 4.0;
         //memAccessTask.stop();
         return score;
     }
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
index 6e9e739..3f04888 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
@@ -15,7 +15,9 @@
  */
 package android.surfacecomposition;
 
+import android.app.Activity;
 import android.graphics.PixelFormat;
+import android.os.Bundle;
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
 import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
 import android.test.ActivityInstrumentationTestCase2;
@@ -25,6 +27,16 @@
 public class SurfaceCompositionTest extends
         ActivityInstrumentationTestCase2<SurfaceCompositionMeasuringActivity> {
     private final static String TAG = "SurfaceCompositionTest";
+    private final static String KEY_SURFACE_COMPOSITION_PERFORMANCE =
+            "surface-compoistion-peformance-sps";
+    private final static String KEY_SURFACE_COMPOSITION_BANDWITH =
+            "surface-compoistion-bandwidth-gbps";
+    private final static String KEY_SURFACE_ALLOCATION_PERFORMANCE_MEDIAN =
+            "surface-allocation-performance-median-sps";
+    private final static String KEY_SURFACE_ALLOCATION_PERFORMANCE_MIN =
+            "surface-allocation-performance-min-sps";
+    private final static String KEY_SURFACE_ALLOCATION_PERFORMANCE_MAX =
+            "surface-allocation-performance-max-sps";
 
     // Pass threshold for major pixel formats.
     private final static int[] TEST_PIXEL_FORMATS = new int[] {
@@ -53,6 +65,7 @@
 
     @SmallTest
     public void testSurfaceCompositionPerformance() {
+        Bundle status = new Bundle();
         for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
             int pixelFormat = TEST_PIXEL_FORMATS[i];
             String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
@@ -62,11 +75,20 @@
                     "performance score. " + score.mSurfaces + " < " +
                     MIN_ACCEPTED_COMPOSITION_SCORE[i] + ".",
                     score.mSurfaces >= MIN_ACCEPTED_COMPOSITION_SCORE[i]);
+            // Send status only for TRANSLUCENT format.
+            if (pixelFormat == PixelFormat.TRANSLUCENT) {
+                status.putDouble(KEY_SURFACE_COMPOSITION_PERFORMANCE, score.mSurfaces);
+                // Put bandwidth in GBPS.
+                status.putDouble(KEY_SURFACE_COMPOSITION_BANDWITH, score.mBandwidth /
+                        (1024.0 * 1024.0 * 1024.0));
+            }
         }
+        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
     }
 
     @SmallTest
     public void testSurfaceAllocationPerformance() {
+        Bundle status = new Bundle();
         for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
             int pixelFormat = TEST_PIXEL_FORMATS[i];
             String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
@@ -76,6 +98,13 @@
                     "performance score. " + score.mMedian + " < " +
                     MIN_ACCEPTED_ALLOCATION_SCORE[i] + ".",
                     score.mMedian >= MIN_ACCEPTED_ALLOCATION_SCORE[i]);
+            // Send status only for TRANSLUCENT format.
+            if (pixelFormat == PixelFormat.TRANSLUCENT) {
+                status.putDouble(KEY_SURFACE_ALLOCATION_PERFORMANCE_MEDIAN, score.mMedian);
+                status.putDouble(KEY_SURFACE_ALLOCATION_PERFORMANCE_MIN, score.mMin);
+                status.putDouble(KEY_SURFACE_ALLOCATION_PERFORMANCE_MAX, score.mMax);
+            }
         }
+        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
     }
 }
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/android/animation/AnimatorInflater_Delegate.java b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
deleted file mode 100644
index 4475fa4..0000000
--- a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.animation;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.util.AttributeSet;
-
-/**
- * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}.
- */
-public class AnimatorInflater_Delegate {
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Context context, int id)
-            throws NotFoundException {
-        return loadAnimator(context.getResources(), context.getTheme(), id);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id)
-            throws NotFoundException {
-        return loadAnimator(resources, theme, id, 1);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id,
-            float pathErrorScale) throws NotFoundException {
-        // This is a temporary fix to http://b.android.com/77865. This skips loading the
-        // animation altogether.
-        // TODO: Remove this override when Path.approximate() is supported.
-        return new FakeAnimator();
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme,
-            AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
-            throws NotFoundException {
-        return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale);
-    }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index f4b1f2c..0e39243 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -18,6 +18,7 @@
 
 import com.android.SdkConstants;
 import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.ide.common.rendering.api.LayoutlibCallback;
 import com.android.ide.common.rendering.api.ResourceValue;
@@ -661,13 +662,18 @@
         Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
 
         if (value != null) {
-            String v = value.getSecond().getValue();
+            ResourceValue resVal = value.getSecond();
+            String v = resVal.getValue();
 
             if (v != null) {
                 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
                         false /*requireUnit*/)) {
                     return;
                 }
+                if (resVal instanceof DensityBasedResourceValue) {
+                    outValue.density =
+                      ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+                }
 
                 // else it's a string
                 outValue.type = TypedValue.TYPE_STRING;
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index d858953..60514b6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -59,6 +59,7 @@
             if (opts.inPremultiplied) {
                 bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
             }
+            opts.inScaled = false;
         }
 
         try {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index f8b3739..64cd503 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -35,6 +35,8 @@
 import java.awt.Shape;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 
 
@@ -707,6 +709,12 @@
                     @Override
                     public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
                         Shape shape = pathDelegate.getJavaShape();
+                        Rectangle2D bounds = shape.getBounds2D();
+                        if (bounds.isEmpty()) {
+                            // Apple JRE 1.6 doesn't like drawing empty shapes.
+                            // http://b.android.com/178278
+                            return;
+                        }
                         int style = paintDelegate.getStyle();
 
                         if (style == Paint.Style.FILL.nativeInt ||
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
new file mode 100644
index 0000000..dd2978f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -0,0 +1,210 @@
+/*
+ * 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.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+/**
+ * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of PathMeasure have been
+ * replaced by
+ * calls to methods of the same name in this delegate class.
+ * <p/>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original PathMeasure class.
+ *
+ * @see DelegateManager
+ */
+public final class PathMeasure_Delegate {
+    // ---- delegate manager ----
+    private static final DelegateManager<PathMeasure_Delegate> sManager =
+            new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
+
+    // ---- delegate data ----
+    // This governs how accurate the approximation of the Path is.
+    private static final float PRECISION = 0.002f;
+
+    /**
+     * Array containing the path points components. There are three components for each point:
+     * <ul>
+     *     <li>Fraction along the length of the path that the point resides</li>
+     *     <li>The x coordinate of the point</li>
+     *     <li>The y coordinate of the point</li>
+     * </ul>
+     */
+    private float mPathPoints[];
+    private long mNativePath;
+
+    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
+        mNativePath = native_path;
+        if (forceClosed && mNativePath != 0) {
+            // Copy the path and call close
+            mNativePath = Path_Delegate.init2(native_path);
+            Path_Delegate.native_close(mNativePath);
+        }
+
+        mPathPoints =
+                mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long native_create(long native_path, boolean forceClosed) {
+        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_destroy(long native_instance) {
+        sManager.removeJavaReferenceFor(native_instance);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
+            float tan[]) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getPostTan is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
+            native_matrix, int flags) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getMatrix is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_nextContour(long native_instance) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.nextContour is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
+            forceClosed) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (forceClosed && native_path != 0) {
+            // Copy the path and call close
+            native_path = Path_Delegate.init2(native_path);
+            Path_Delegate.native_close(native_path);
+        }
+        pathMeasure.mNativePath = native_path;
+        pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float native_getLength(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (pathMeasure.mPathPoints == null) {
+            return 0;
+        }
+
+        float length = 0;
+        int nPoints = pathMeasure.mPathPoints.length / 3;
+        for (int i = 1; i < nPoints; i++) {
+            length += Point2D.distance(
+                    pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+                    pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+                    pathMeasure.mPathPoints[i*3 + 1],
+                    pathMeasure.mPathPoints[i*3 + 2]);
+        }
+
+        return length;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_isClosed(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
+        if (path == null) {
+            return false;
+        }
+
+        PathIterator pathIterator = path.getJavaShape().getPathIterator(null);
+
+        int type = 0;
+        float segment[] = new float[6];
+        while (!pathIterator.isDone()) {
+            type = pathIterator.currentSegment(segment);
+            pathIterator.next();
+        }
+
+        // A path is a closed path if the last element is SEG_CLOSE
+        return type == PathIterator.SEG_CLOSE;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
+            long native_dst_path, boolean startWithMoveTo) {
+        if (startD < 0) {
+            startD = 0;
+        }
+
+        if (startD >= stopD) {
+            return false;
+        }
+
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (pathMeasure.mPathPoints == null) {
+            return false;
+        }
+
+        float accLength = 0;
+        boolean isZeroLength = true; // Whether the output has zero length or not
+        int nPoints = pathMeasure.mPathPoints.length / 3;
+        for (int i = 0; i < nPoints; i++) {
+            float x = pathMeasure.mPathPoints[i * 3 + 1];
+            float y = pathMeasure.mPathPoints[i * 3 + 2];
+            if (accLength >= startD && accLength <= stopD) {
+                if (startWithMoveTo) {
+                    startWithMoveTo = false;
+                    Path_Delegate.native_moveTo(native_dst_path, x, y);
+                } else {
+                    isZeroLength = false;
+                    Path_Delegate.native_lineTo(native_dst_path, x, y);
+                }
+            }
+
+            if (i > 0) {
+                accLength += Point2D.distance(
+                        pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+                        pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+                        pathMeasure.mPathPoints[i * 3 + 1],
+                        pathMeasure.mPathPoints[i * 3 + 2]);
+            }
+        }
+
+        return !isZeroLength;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 3c9a062..a2a53fe 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -36,6 +36,7 @@
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.awt.geom.RoundRectangle2D;
+import java.util.ArrayList;
 
 /**
  * Delegate implementing the native methods of android.graphics.Path
@@ -173,11 +174,8 @@
     @LayoutlibDelegate
     /*package*/ static boolean native_isEmpty(long nPath) {
         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
-        if (pathDelegate == null) {
-            return true;
-        }
+        return pathDelegate == null || pathDelegate.isEmpty();
 
-        return pathDelegate.isEmpty();
     }
 
     @LayoutlibDelegate
@@ -488,54 +486,44 @@
 
     @LayoutlibDelegate
     /*package*/ static float[] native_approximate(long nPath, float error) {
-        Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not fully supported",
-                null);
         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
         if (pathDelegate == null) {
             return null;
         }
-        PathIterator pathIterator = pathDelegate.mPath.getPathIterator(null);
-        float[] tmp = new float[6];
-        float[] coords = new float[6];
-        boolean isFirstPoint = true;
-        while (!pathIterator.isDone()) {
-            int type = pathIterator.currentSegment(tmp);
-            switch (type) {
-                case PathIterator.SEG_MOVETO:
-                case PathIterator.SEG_LINETO:
-                    store(tmp, coords, 1, isFirstPoint);
-                    break;
-                case PathIterator.SEG_QUADTO:
-                    store(tmp, coords, 2, isFirstPoint);
-                    break;
-                case PathIterator.SEG_CUBICTO:
-                    store(tmp, coords, 3, isFirstPoint);
-                    break;
-                case PathIterator.SEG_CLOSE:
-                    // No points returned.
-            }
-            isFirstPoint = false;
-            pathIterator.next();
-        }
-        if (isFirstPoint) {
-            // No points found
-            return new float[0];
-        } else {
-            return coords;
-        }
-    }
+        // Get a FlatteningIterator
+        PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
 
-    private static void store(float[] src, float[] dst, int count, boolean isFirst) {
-        if (isFirst) {
-            dst[0] = 0;       // fraction
-            dst[1] = src[0];  // abscissa
-            dst[2] = src[1];  // ordinate
+        float segment[] = new float[6];
+        float totalLength = 0;
+        ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
+        Point2D.Float previousPoint = null;
+        while (!iterator.isDone()) {
+            int type = iterator.currentSegment(segment);
+            Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
+            // MoveTo shouldn't affect the length
+            if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
+                totalLength += currentPoint.distance(previousPoint);
+            }
+            previousPoint = currentPoint;
+            points.add(currentPoint);
+            iterator.next();
         }
-        if (count > 1 || !isFirst) {
-            dst[3] = 1;
-            dst[4] = src[2 * count - 2];
-            dst[5] = src[2 * count - 1];
+
+        int nPoints = points.size();
+        float[] result = new float[nPoints * 3];
+        previousPoint = null;
+        for (int i = 0; i < nPoints; i++) {
+            Point2D.Float point = points.get(i);
+            float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
+            result[i * 3] = distance / totalLength;
+            result[i * 3 + 1] = point.x;
+            result[i * 3 + 2] = point.y;
+
+            totalLength += distance;
+            previousPoint = point;
         }
+
+        return result;
     }
 
     // ---- Private helper methods ----
@@ -735,6 +723,9 @@
      */
     private void cubicTo(float x1, float y1, float x2, float y2,
                         float x3, float y3) {
+        if (isEmpty()) {
+            mPath.moveTo(0, 0);
+        }
         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
     }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java
new file mode 100644
index 0000000..a3ad2aac
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.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 android.graphics.drawable;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Path;
+import android.graphics.drawable.GradientDrawable.GradientState;
+
+import java.lang.reflect.Field;
+
+/**
+ * Delegate implementing the native methods of {@link GradientDrawable}
+ *
+ * Through the layoutlib_create tool, the original native methods of GradientDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class GradientDrawable_Delegate {
+
+    /**
+     * The ring can be built either by drawing full circles, or by drawing arcs in case the
+     * circle isn't complete. LayoutLib cannot handle drawing full circles (requires path
+     * subtraction). So, if we need to draw full circles, we switch to drawing 99% circle.
+     */
+    @LayoutlibDelegate
+    /*package*/ static Path buildRing(GradientDrawable thisDrawable, GradientState st) {
+        boolean useLevel = st.mUseLevelForShape;
+        int level = thisDrawable.getLevel();
+        // 10000 is the max level. See android.graphics.drawable.Drawable#getLevel()
+        float sweep = useLevel ? (360.0f * level / 10000.0f) : 360f;
+        Field mLevel = null;
+        if (sweep >= 360 || sweep <= -360) {
+            st.mUseLevelForShape = true;
+            // Use reflection to set the value of the field to prevent setting the drawable to
+            // dirty again.
+            try {
+                mLevel = Drawable.class.getDeclaredField("mLevel");
+                mLevel.setAccessible(true);
+                mLevel.setInt(thisDrawable, 9999);  // set to one less than max.
+            } catch (NoSuchFieldException e) {
+                // The field has been removed in a recent framework change. Fall back to old
+                // buggy behaviour.
+            } catch (IllegalAccessException e) {
+                // We've already set the field to be accessible.
+                assert false;
+            }
+        }
+        Path path = thisDrawable.buildRing_Original(st);
+        st.mUseLevelForShape = useLevel;
+        if (mLevel != null) {
+            try {
+                mLevel.setInt(thisDrawable, level);
+            } catch (IllegalAccessException e) {
+                assert false;
+            }
+        }
+        return path;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
index 49ee642..2e44a77 100644
--- a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
@@ -29,9 +29,6 @@
 import android.view.ViewGroup;
 import android.widget.ListView;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Delegate that provides implementation for native methods in {@link Preference}
  * <p/>
@@ -59,9 +56,9 @@
      */
     public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) {
         PreferenceManager pm = new PreferenceManager(context);
-        PreferenceScreen ps = pm.getPreferenceScreen();
         PreferenceInflater inflater = new BridgePreferenceInflater(context, pm);
-        ps = (PreferenceScreen) inflater.inflate(parser, ps, true);
+        PreferenceScreen ps = (PreferenceScreen) inflater.inflate(parser, null, true);
+        pm.setPreferences(ps);
         ListView preferenceView = createContainerView(context, root);
         ps.bind(preferenceView);
         return preferenceView;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index b2dc29a..93b3f4f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -73,6 +73,7 @@
 import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -1130,6 +1131,11 @@
                 public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
                     return false;
                 }
+
+                @Override
+                public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                  String[] args, ResultReceiver resultReceiver) {
+                }
             };
         }
         return mBinder;
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/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index 771c3c8..c2d8d0c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -91,14 +91,6 @@
     }
 
     @Override
-    public void onAnimationStarted(int remainingFrameCount) {
-    }
-
-    @Override
-    public void onAnimationStopped() {
-    }
-
-    @Override
     public void dispatchWindowShown() {
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 499bea4..e480ead 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -152,7 +152,6 @@
      * The list of methods to rewrite as delegates.
      */
     public final static String[] DELEGATE_METHODS = new String[] {
-        "android.animation.AnimatorInflater#loadAnimator",  // TODO: remove when Path.approximate() is supported.
         "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
         "android.content.res.Resources$Theme#obtainStyledAttributes",
         "android.content.res.Resources$Theme#resolveAttribute",
@@ -162,6 +161,7 @@
         "android.content.res.TypedArray#getValueAt",
         "android.content.res.TypedArray#obtain",
         "android.graphics.BitmapFactory#finishDecode",
+        "android.graphics.drawable.GradientDrawable#buildRing",
         "android.graphics.Typeface#getSystemFontConfigLocation",
         "android.os.Handler#sendMessageAtTime",
         "android.os.HandlerThread#run",
@@ -228,6 +228,7 @@
         "android.graphics.Path",
         "android.graphics.PathDashPathEffect",
         "android.graphics.PathEffect",
+        "android.graphics.PathMeasure",
         "android.graphics.PixelXorXfermode",
         "android.graphics.PorterDuffColorFilter",
         "android.graphics.PorterDuffXfermode",
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);
     }