Merge "CEC: Replace determistic mute commands"
diff --git a/Android.mk b/Android.mk
index 71bba0f..f545207 100644
--- a/Android.mk
+++ b/Android.mk
@@ -281,6 +281,7 @@
 	core/java/com/android/internal/app/IAppOpsService.aidl \
 	core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
+	core/java/com/android/internal/app/IEphemeralResolver.aidl \
 	core/java/com/android/internal/app/IProcessStats.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
@@ -417,6 +418,8 @@
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
 	packages/services/Proxy/com/android/net/IProxyCallback.aidl \
 	packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
+	core/java/android/service/quicksettings/IQSService.aidl \
+	core/java/android/service/quicksettings/IQSTileService.aidl \
 
 # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
 LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
@@ -427,7 +430,7 @@
 			$(framework_res_source_path)/com/android/internal/R.java
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart conscrypt okhttp core-junit bouncycastle ext
+LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
 
 LOCAL_MODULE := framework
 
@@ -625,6 +628,7 @@
 	frameworks/base/core/java/android/bluetooth/le/ScanResult.aidl \
 	frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
 	frameworks/base/core/java/android/database/CursorWindow.aidl \
+	frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
 $(gen): PRIVATE_SRC_FILES := $(aidl_files)
@@ -715,6 +719,7 @@
 	$(framework_res_source_path)/com/android/internal/R.java
 
 framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
+	core-oj \
 	core-libart \
 	conscrypt \
 	bouncycastle \
@@ -1100,7 +1105,7 @@
 LOCAL_SRC_FILES := $(ext_src_files)
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
 LOCAL_STATIC_JAVA_LIBRARIES := libphonenumber-platform
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
diff --git a/api/current.txt b/api/current.txt
index 84e9079..2f5686e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30,6 +30,7 @@
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -332,6 +333,7 @@
     field public static final int calendarTextColor = 16843931; // 0x101049b
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canControlMagnification = 16844040; // 0x1010508
     field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
     field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -499,6 +501,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enabled = 16842766; // 0x101000e
+    field public static final int encryptionAware = 16844038; // 0x1010506
     field public static final int end = 16843996; // 0x10104dc
     field public static final int endColor = 16843166; // 0x101019e
     field public static final deprecated int endYear = 16843133; // 0x101017d
@@ -559,6 +562,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceDeviceEncrypted = 16844037; // 0x1010505
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -931,6 +935,7 @@
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+    field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
     field public static final int preferenceInformationStyle = 16842893; // 0x101008d
     field public static final int preferenceLayoutChild = 16842900; // 0x1010094
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1708,11 +1713,13 @@
     field public static final int icon = 16908294; // 0x1020006
     field public static final int icon1 = 16908295; // 0x1020007
     field public static final int icon2 = 16908296; // 0x1020008
+    field public static final int icon_frame = 16908350; // 0x102003e
     field public static final int input = 16908297; // 0x1020009
     field public static final int inputArea = 16908318; // 0x102001e
     field public static final int inputExtractEditText = 16908325; // 0x1020025
     field public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
+    field public static final int list_container = 16908351; // 0x102003f
     field public static final int mask = 16908334; // 0x102002e
     field public static final int message = 16908299; // 0x102000b
     field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1732,6 +1739,7 @@
     field public static final int stopSelectingText = 16908329; // 0x1020029
     field public static final int summary = 16908304; // 0x1020010
     field public static final int switchInputMethod = 16908324; // 0x1020024
+    field public static final int switch_widget = 16908352; // 0x1020040
     field public static final int tabcontent = 16908305; // 0x1020011
     field public static final int tabhost = 16908306; // 0x1020012
     field public static final int tabs = 16908307; // 0x1020013
@@ -2613,6 +2621,7 @@
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2650,6 +2659,23 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
   }
 
+  public static final class AccessibilityService.MagnificationController {
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+    method public float getCenterX();
+    method public float getCenterY();
+    method public android.graphics.Region getMagnifiedRegion();
+    method public float getScale();
+    method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public boolean reset(boolean);
+    method public boolean setCenter(float, float, boolean);
+    method public boolean setScale(float, boolean);
+  }
+
+  public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+    method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+  }
+
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     ctor public AccessibilityServiceInfo();
     method public static java.lang.String capabilityToString(int);
@@ -2664,6 +2690,7 @@
     method public java.lang.String getSettingsActivityName();
     method public java.lang.String loadDescription(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
     field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
     field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2706,6 +2733,8 @@
     method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
     method public final android.os.IBinder getIBinder();
     method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startUpdateCredentialsSession(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
   }
@@ -2770,6 +2799,8 @@
     method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
     method public void setPassword(android.accounts.Account, java.lang.String);
     method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
     field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2786,6 +2817,8 @@
     field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
     field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
     field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+    field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+    field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
     field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
     field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3469,6 +3502,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
@@ -3747,6 +3781,8 @@
   }
 
   public class ActivityOptions {
+    method public android.graphics.Rect getLaunchBounds();
+    method public boolean hasLaunchBounds();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3756,6 +3792,7 @@
     method public static android.app.ActivityOptions makeTaskLaunchBehind();
     method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
+    method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -4769,7 +4806,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 android.app.Notification.Topic getTopic();
     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";
@@ -4832,6 +4869,7 @@
     field public static final int PRIORITY_MAX = 2; // 0x2
     field public static final int PRIORITY_MIN = -2; // 0xfffffffe
     field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+    field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
     field public static final int VISIBILITY_PRIVATE = 0; // 0x0
     field public static final int VISIBILITY_PUBLIC = 1; // 0x1
     field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -4934,7 +4972,6 @@
     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();
@@ -4983,6 +5020,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setTopic(android.app.Notification.Topic);
     method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setVisibility(int);
@@ -5140,10 +5178,12 @@
   }
 
   public static class NotificationManager.Policy implements android.os.Parcelable {
-    ctor public NotificationManager.Policy(int, int, int);
+    ctor public deprecated NotificationManager.Policy(int, int, int);
+    ctor public NotificationManager.Policy(int, int, int, int);
     method public int describeContents();
     method public static java.lang.String priorityCategoriesToString(int);
     method public static java.lang.String prioritySendersToString(int);
+    method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5154,9 +5194,13 @@
     field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
     field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
     field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+    field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public final int priorityCallSenders;
     field public final int priorityCategories;
     field public final int priorityMessageSenders;
+    field public final int suppressedVisualEffects;
   }
 
   public final class PendingIntent implements android.os.Parcelable {
@@ -5705,6 +5749,7 @@
     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 getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5757,6 +5802,7 @@
     method public void setCameraDisabled(android.content.ComponentName, boolean);
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -5822,6 +5868,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+    field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
     field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
     field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -9912,6 +9959,7 @@
   public static class Resources.NotFoundException extends java.lang.RuntimeException {
     ctor public Resources.NotFoundException();
     ctor public Resources.NotFoundException(java.lang.String);
+    ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
   }
 
   public final class Resources.Theme {
@@ -15511,6 +15559,7 @@
     field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
     field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
+    field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
     field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
     field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
     field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
@@ -15562,6 +15611,7 @@
   public static final class MediaCodec.CryptoInfo {
     ctor public MediaCodec.CryptoInfo();
     method public void set(int, int[], int[], byte[], byte[], int);
+    method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
     field public byte[] iv;
     field public byte[] key;
     field public int mode;
@@ -15570,6 +15620,13 @@
     field public int numSubSamples;
   }
 
+  public static final class MediaCodec.CryptoInfo.Pattern {
+    ctor public MediaCodec.CryptoInfo.Pattern(int, int);
+    method public int getEncryptBlocks();
+    method public int getSkipBlocks();
+    method public void set(int, int);
+  }
+
   public static abstract interface MediaCodec.OnFrameRenderedListener {
     method public abstract void onFrameRendered(android.media.MediaCodec, long, long);
   }
@@ -17759,6 +17816,7 @@
 package android.media.tv {
 
   public final class TvContentRating {
+    method public final boolean contains(android.media.tv.TvContentRating);
     method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...);
     method public java.lang.String flattenToString();
     method public java.lang.String getDomain();
@@ -25741,6 +25799,7 @@
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
     field public static final android.net.Uri CONTENT_VCARD_URI;
+    field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -25861,12 +25920,15 @@
   }
 
   public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+    method public static boolean isEnterpriseDirectoryId(long);
+    method public static boolean isRemoteDirectory(long);
     method public static void notifyDirectoryChange(android.content.ContentResolver);
     field public static final java.lang.String ACCOUNT_NAME = "accountName";
     field public static final java.lang.String ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final android.net.Uri CORP_CONTENT_URI;
     field public static final long DEFAULT = 0L; // 0x0L
     field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
     field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -26226,6 +26288,7 @@
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
     field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
@@ -28955,12 +29018,15 @@
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
   }
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
     method public java.lang.String getKey();
     method public int getRank();
+    method public int getSuppressedVisualEffects();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
   }
@@ -28995,6 +29061,35 @@
 
 }
 
+package android.service.quicksettings {
+
+  public final class Tile implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getLabel();
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setIcon(android.graphics.drawable.Icon);
+    method public void setLabel(java.lang.CharSequence);
+    method public void updateTile();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+  }
+
+  public class TileService extends android.app.Service {
+    ctor public TileService();
+    method public final android.service.quicksettings.Tile getQsTile();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public void onClick();
+    method public void onStartListening();
+    method public void onStopListening();
+    method public void onTileAdded();
+    method public void onTileRemoved();
+    field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+  }
+
+}
+
 package android.service.restrictions {
 
   public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -34359,6 +34454,7 @@
     ctor public LocaleList(java.util.Locale[]);
     method public static android.util.LocaleList forLanguageTags(java.lang.String);
     method public java.util.Locale get(int);
+    method public java.util.Locale getBestMatch(java.lang.String[]);
     method public static android.util.LocaleList getDefault();
     method public static android.util.LocaleList getEmptyLocaleList();
     method public java.util.Locale getPrimary();
@@ -36190,6 +36286,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -36207,6 +36304,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
     method public void destroyDrawingCache();
+    method public final boolean didLayoutParamsChange();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
     method public void dispatchDisplayHint(int);
@@ -36432,6 +36530,7 @@
     method public boolean isOpaque();
     method protected boolean isPaddingOffsetRequired();
     method public boolean isPaddingRelative();
+    method public final boolean isPartialLayoutRequested();
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
@@ -36668,7 +36767,8 @@
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
     method public void startAnimation(android.view.animation.Animation);
-    method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
     method public boolean startNestedScroll(int);
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
@@ -37041,6 +37141,7 @@
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method public int findDependentLayoutAxes(android.view.View, int);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -37107,6 +37208,8 @@
     method public void requestChildFocus(android.view.View, android.view.View);
     method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
     method public void requestDisallowInterceptTouchEvent(boolean);
+    method public void requestLayoutForChild(android.view.View);
+    method public void requestPartialLayoutForChild(android.view.View);
     method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public void requestTransparentRegion(android.view.View);
     method public void scheduleLayoutAnimation();
@@ -37221,6 +37324,7 @@
     method public abstract void childHasTransientStateChanged(android.view.View, boolean);
     method public abstract void clearChildFocus(android.view.View);
     method public abstract void createContextMenu(android.view.ContextMenu);
+    method public abstract int findDependentLayoutAxes(android.view.View, int);
     method public abstract android.view.View focusSearch(android.view.View, int);
     method public abstract void focusableViewAvailable(android.view.View);
     method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -37250,12 +37354,16 @@
     method public abstract void requestDisallowInterceptTouchEvent(boolean);
     method public abstract void requestFitSystemWindows();
     method public abstract void requestLayout();
+    method public abstract void requestLayoutForChild(android.view.View);
     method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public abstract void requestTransparentRegion(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View, float, float);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+    field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+    field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+    field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
   }
 
   public class ViewPropertyAnimator {
diff --git a/api/system-current.txt b/api/system-current.txt
index 31634bf..cbe8db7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43,6 +43,7 @@
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -424,6 +425,7 @@
     field public static final int calendarTextColor = 16843931; // 0x101049b
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canControlMagnification = 16844040; // 0x1010508
     field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
     field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -591,6 +593,7 @@
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
     field public static final int enabled = 16842766; // 0x101000e
+    field public static final int encryptionAware = 16844038; // 0x1010506
     field public static final int end = 16843996; // 0x10104dc
     field public static final int endColor = 16843166; // 0x101019e
     field public static final deprecated int endYear = 16843133; // 0x101017d
@@ -651,6 +654,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceDeviceEncrypted = 16844037; // 0x1010505
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -1023,6 +1027,7 @@
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
+    field public static final int preferenceFragmentStyle = 16844039; // 0x1010507
     field public static final int preferenceInformationStyle = 16842893; // 0x101008d
     field public static final int preferenceLayoutChild = 16842900; // 0x1010094
     field public static final int preferenceScreenStyle = 16842891; // 0x101008b
@@ -1804,11 +1809,13 @@
     field public static final int icon = 16908294; // 0x1020006
     field public static final int icon1 = 16908295; // 0x1020007
     field public static final int icon2 = 16908296; // 0x1020008
+    field public static final int icon_frame = 16908350; // 0x102003e
     field public static final int input = 16908297; // 0x1020009
     field public static final int inputArea = 16908318; // 0x102001e
     field public static final int inputExtractEditText = 16908325; // 0x1020025
     field public static final int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
+    field public static final int list_container = 16908351; // 0x102003f
     field public static final int mask = 16908334; // 0x102002e
     field public static final int message = 16908299; // 0x102000b
     field public static final int navigationBarBackground = 16908336; // 0x1020030
@@ -1828,6 +1835,7 @@
     field public static final int stopSelectingText = 16908329; // 0x1020029
     field public static final int summary = 16908304; // 0x1020010
     field public static final int switchInputMethod = 16908324; // 0x1020024
+    field public static final int switch_widget = 16908352; // 0x1020040
     field public static final int tabcontent = 16908305; // 0x1020011
     field public static final int tabhost = 16908306; // 0x1020012
     field public static final int tabs = 16908307; // 0x1020013
@@ -2712,6 +2720,7 @@
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2749,6 +2758,23 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
   }
 
+  public static final class AccessibilityService.MagnificationController {
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+    method public float getCenterX();
+    method public float getCenterY();
+    method public android.graphics.Region getMagnifiedRegion();
+    method public float getScale();
+    method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public boolean reset(boolean);
+    method public boolean setCenter(float, float, boolean);
+    method public boolean setScale(float, boolean);
+  }
+
+  public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+    method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+  }
+
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     ctor public AccessibilityServiceInfo();
     method public static java.lang.String capabilityToString(int);
@@ -2763,6 +2789,7 @@
     method public java.lang.String getSettingsActivityName();
     method public java.lang.String loadDescription(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
     field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
     field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2805,6 +2832,8 @@
     method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
     method public final android.os.IBinder getIBinder();
     method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startUpdateCredentialsSession(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
   }
@@ -2869,6 +2898,8 @@
     method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
     method public void setPassword(android.accounts.Account, java.lang.String);
     method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
     field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2885,6 +2916,8 @@
     field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
     field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
     field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+    field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+    field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
     field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
     field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3572,6 +3605,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
@@ -3856,6 +3890,8 @@
   }
 
   public class ActivityOptions {
+    method public android.graphics.Rect getLaunchBounds();
+    method public boolean hasLaunchBounds();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3865,6 +3901,7 @@
     method public static android.app.ActivityOptions makeTaskLaunchBehind();
     method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
+    method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -4887,7 +4924,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 android.app.Notification.Topic getTopic();
     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";
@@ -4950,6 +4987,7 @@
     field public static final int PRIORITY_MAX = 2; // 0x2
     field public static final int PRIORITY_MIN = -2; // 0xfffffffe
     field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+    field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
     field public static final int VISIBILITY_PRIVATE = 0; // 0x0
     field public static final int VISIBILITY_PUBLIC = 1; // 0x1
     field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -5052,7 +5090,6 @@
     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();
@@ -5101,6 +5138,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setTopic(android.app.Notification.Topic);
     method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setVisibility(int);
@@ -5258,10 +5296,12 @@
   }
 
   public static class NotificationManager.Policy implements android.os.Parcelable {
-    ctor public NotificationManager.Policy(int, int, int);
+    ctor public deprecated NotificationManager.Policy(int, int, int);
+    ctor public NotificationManager.Policy(int, int, int, int);
     method public int describeContents();
     method public static java.lang.String priorityCategoriesToString(int);
     method public static java.lang.String prioritySendersToString(int);
+    method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5272,9 +5312,13 @@
     field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
     field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
     field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+    field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public final int priorityCallSenders;
     field public final int priorityCategories;
     field public final int priorityMessageSenders;
+    field public final int suppressedVisualEffects;
   }
 
   public final class PendingIntent implements android.os.Parcelable {
@@ -5831,6 +5875,7 @@
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
     method public java.lang.String getDeviceOwner();
+    method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5889,6 +5934,7 @@
     method public void setCameraDisabled(android.content.ComponentName, boolean);
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -5956,6 +6002,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+    field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
     field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
     field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -8481,6 +8528,7 @@
     field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
     field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
     field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+    field public static final java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
     field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
     field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
     field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
@@ -8528,6 +8576,7 @@
     field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
     field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
     field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
+    field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
     field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
     field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
     field public static final java.lang.String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
@@ -10253,6 +10302,7 @@
   public static class Resources.NotFoundException extends java.lang.RuntimeException {
     ctor public Resources.NotFoundException();
     ctor public Resources.NotFoundException(java.lang.String);
+    ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
   }
 
   public final class Resources.Theme {
@@ -16796,6 +16846,7 @@
     field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
     field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
+    field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
     field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
     field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
     field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
@@ -16847,6 +16898,7 @@
   public static final class MediaCodec.CryptoInfo {
     ctor public MediaCodec.CryptoInfo();
     method public void set(int, int[], int[], byte[], byte[], int);
+    method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
     field public byte[] iv;
     field public byte[] key;
     field public int mode;
@@ -16855,6 +16907,13 @@
     field public int numSubSamples;
   }
 
+  public static final class MediaCodec.CryptoInfo.Pattern {
+    ctor public MediaCodec.CryptoInfo.Pattern(int, int);
+    method public int getEncryptBlocks();
+    method public int getSkipBlocks();
+    method public void set(int, int);
+  }
+
   public static abstract interface MediaCodec.OnFrameRenderedListener {
     method public abstract void onFrameRendered(android.media.MediaCodec, long, long);
   }
@@ -20297,6 +20356,7 @@
     method public static void incrementOperationCount(int, int);
     method public static void setThreadStatsTag(int);
     method public static void setThreadStatsTagBackup();
+    method public static void setThreadStatsTagRestore();
     method public static void setThreadStatsUid(int);
     method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
     method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
@@ -21476,10 +21536,13 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public int band;
     field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
+    field public int exponent;
+    field public int maxPeriodInMs;
     field public int maxScansToCache;
     field public int numBssidsPerScan;
     field public int periodInMs;
     field public int reportEvents;
+    field public int stepCount;
   }
 
   public static abstract interface WifiScanner.WifiChangeListener implements android.net.wifi.WifiScanner.ActionListener {
@@ -27719,6 +27782,7 @@
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
     field public static final android.net.Uri CONTENT_VCARD_URI;
+    field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -27839,12 +27903,15 @@
   }
 
   public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+    method public static boolean isEnterpriseDirectoryId(long);
+    method public static boolean isRemoteDirectory(long);
     method public static void notifyDirectoryChange(android.content.ContentResolver);
     field public static final java.lang.String ACCOUNT_NAME = "accountName";
     field public static final java.lang.String ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final android.net.Uri CORP_CONTENT_URI;
     field public static final long DEFAULT = 0L; // 0x0L
     field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
     field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -28234,6 +28301,7 @@
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
     field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
@@ -31071,6 +31139,8 @@
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public static final int TRIM_FULL = 0; // 0x0
     field public static final int TRIM_LIGHT = 1; // 0x1
   }
@@ -31079,6 +31149,7 @@
     ctor public NotificationListenerService.Ranking();
     method public java.lang.String getKey();
     method public int getRank();
+    method public int getSuppressedVisualEffects();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
   }
@@ -31138,6 +31209,35 @@
 
 }
 
+package android.service.quicksettings {
+
+  public final class Tile implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getLabel();
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setIcon(android.graphics.drawable.Icon);
+    method public void setLabel(java.lang.CharSequence);
+    method public void updateTile();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+  }
+
+  public class TileService extends android.app.Service {
+    ctor public TileService();
+    method public final android.service.quicksettings.Tile getQsTile();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public void onClick();
+    method public void onStartListening();
+    method public void onStopListening();
+    method public void onTileAdded();
+    method public void onTileRemoved();
+    field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+  }
+
+}
+
 package android.service.restrictions {
 
   public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -36677,6 +36777,7 @@
     ctor public LocaleList(java.util.Locale[]);
     method public static android.util.LocaleList forLanguageTags(java.lang.String);
     method public java.util.Locale get(int);
+    method public java.util.Locale getBestMatch(java.lang.String[]);
     method public static android.util.LocaleList getDefault();
     method public static android.util.LocaleList getEmptyLocaleList();
     method public java.util.Locale getPrimary();
@@ -38508,6 +38609,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -38525,6 +38627,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
     method public void destroyDrawingCache();
+    method public final boolean didLayoutParamsChange();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
     method public void dispatchDisplayHint(int);
@@ -38750,6 +38853,7 @@
     method public boolean isOpaque();
     method protected boolean isPaddingOffsetRequired();
     method public boolean isPaddingRelative();
+    method public final boolean isPartialLayoutRequested();
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
@@ -38986,7 +39090,8 @@
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
     method public void startAnimation(android.view.animation.Animation);
-    method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
     method public boolean startNestedScroll(int);
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
@@ -39359,6 +39464,7 @@
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method public int findDependentLayoutAxes(android.view.View, int);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -39425,6 +39531,8 @@
     method public void requestChildFocus(android.view.View, android.view.View);
     method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
     method public void requestDisallowInterceptTouchEvent(boolean);
+    method public void requestLayoutForChild(android.view.View);
+    method public void requestPartialLayoutForChild(android.view.View);
     method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public void requestTransparentRegion(android.view.View);
     method public void scheduleLayoutAnimation();
@@ -39539,6 +39647,7 @@
     method public abstract void childHasTransientStateChanged(android.view.View, boolean);
     method public abstract void clearChildFocus(android.view.View);
     method public abstract void createContextMenu(android.view.ContextMenu);
+    method public abstract int findDependentLayoutAxes(android.view.View, int);
     method public abstract android.view.View focusSearch(android.view.View, int);
     method public abstract void focusableViewAvailable(android.view.View);
     method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -39568,12 +39677,16 @@
     method public abstract void requestDisallowInterceptTouchEvent(boolean);
     method public abstract void requestFitSystemWindows();
     method public abstract void requestLayout();
+    method public abstract void requestLayoutForChild(android.view.View);
     method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public abstract void requestTransparentRegion(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View, float, float);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+    field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+    field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+    field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
   }
 
   public class ViewPropertyAnimator {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7f33cb5..daf01ec 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -55,6 +55,7 @@
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -64,6 +65,7 @@
 import android.view.IWindowManager;
 
 import com.android.internal.os.BaseCommand;
+import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
 
 import java.io.BufferedReader;
@@ -72,6 +74,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
+import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -117,7 +120,8 @@
 
     @Override
     public void onShowUsage(PrintStream out) {
-        out.println(
+        PrintWriter pw = new PrintWriter(out);
+        pw.println(
                 "usage: am [subcommand] [options]\n" +
                 "usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
                 "               [--sampling INTERVAL] [-R COUNT] [-S]\n" +
@@ -149,6 +153,7 @@
                 "       am to-app-uri [INTENT]\n" +
                 "       am switch-user <USER_ID>\n" +
                 "       am start-user <USER_ID>\n" +
+                "       am unlock-user <USER_ID> [TOKEN_HEX]\n" +
                 "       am stop-user [-w] <USER_ID>\n" +
                 "       am stack start <DISPLAY_ID> <INTENT>\n" +
                 "       am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
@@ -337,52 +342,10 @@
                 "am send-trim-memory: send a memory trim event to a <PROCESS>.\n" +
                 "\n" +
                 "am get-current-user: returns id of the current foreground user.\n" +
-                "\n" +
-                "<INTENT> specifications include these flags and arguments:\n" +
-                "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
-                "    [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
-                "    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
-                "    [--esn <EXTRA_KEY> ...]\n" +
-                "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
-                "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
-                "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
-                "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
-                "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
-                "    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]\n" +
-                "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
-                "        (mutiple extras passed as Integer[])\n" +
-                "    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Integer>)\n" +
-                "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
-                "        (mutiple extras passed as Long[])\n" +
-                "    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Long>)\n" +
-                "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
-                "        (mutiple extras passed as Float[])\n" +
-                "    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Float>)\n" +
-                "    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
-                "        (mutiple extras passed as String[]; to embed a comma into a string,\n" +
-                "         escape it using \"\\,\")\n" +
-                "    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
-                "        (mutiple extras passed as List<String>; to embed a comma into a string,\n" +
-                "         escape it using \"\\,\")\n" +
-                "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
-                "    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" +
-                "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
-                "    [--include-stopped-packages]\n" +
-                "    [--activity-brought-to-front] [--activity-clear-top]\n" +
-                "    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
-                "    [--activity-launched-from-history] [--activity-multiple-task]\n" +
-                "    [--activity-no-animation] [--activity-no-history]\n" +
-                "    [--activity-no-user-action] [--activity-previous-is-top]\n" +
-                "    [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
-                "    [--activity-single-top] [--activity-clear-task]\n" +
-                "    [--activity-task-on-home]\n" +
-                "    [--receiver-registered-only] [--receiver-replace-pending]\n" +
-                "    [--selector]\n" +
-                "    [<URI> | <PACKAGE> | <COMPONENT>]\n"
-                );
+                "\n"
+        );
+        Intent.printIntentArgsHelp(pw, "");
+        pw.flush();
     }
 
     @Override
@@ -450,6 +413,8 @@
             runSwitchUser();
         } else if (op.equals("start-user")) {
             runStartUserInBackground();
+        } else if (op.equals("unlock-user")) {
+            runUnlockUser();
         } else if (op.equals("stop-user")) {
             runStopUser();
         } else if (op.equals("stack")) {
@@ -486,10 +451,6 @@
     }
 
     private Intent makeIntent(int defUser) throws URISyntaxException {
-        Intent intent = new Intent();
-        Intent baseIntent = intent;
-        boolean hasIntentInfo = false;
-
         mStartFlags = 0;
         mWaitOption = false;
         mStopOption = false;
@@ -498,316 +459,38 @@
         mSamplingInterval = 0;
         mAutoStop = false;
         mUserId = defUser;
-        Uri data = null;
-        String type = null;
 
-        String opt;
-        while ((opt=nextOption()) != null) {
-            if (opt.equals("-a")) {
-                intent.setAction(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-d")) {
-                data = Uri.parse(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-t")) {
-                type = nextArgRequired();
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-c")) {
-                intent.addCategory(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-e") || opt.equals("--es")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, value);
-            } else if (opt.equals("--esn")) {
-                String key = nextArgRequired();
-                intent.putExtra(key, (String) null);
-            } else if (opt.equals("--ei")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Integer.decode(value));
-            } else if (opt.equals("--eu")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Uri.parse(value));
-            } else if (opt.equals("--ecn")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                ComponentName cn = ComponentName.unflattenFromString(value);
-                if (cn == null) throw new IllegalArgumentException("Bad component name: " + value);
-                intent.putExtra(key, cn);
-            } else if (opt.equals("--eia")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                int[] list = new int[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Integer.decode(strings[i]);
-                }
-                intent.putExtra(key, list);
-            } else if (opt.equals("--eial")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Integer> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Integer.decode(strings[i]));
-                }
-                intent.putExtra(key, list);
-            } else if (opt.equals("--el")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Long.valueOf(value));
-            } else if (opt.equals("--ela")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                long[] list = new long[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Long.valueOf(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--elal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Long> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Long.valueOf(strings[i]));
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--ef")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Float.valueOf(value));
-                hasIntentInfo = true;
-            } else if (opt.equals("--efa")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                float[] list = new float[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Float.valueOf(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--efal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Float> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Float.valueOf(strings[i]));
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--esa")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                // Split on commas unless they are preceeded by an escape.
-                // The escape character must be escaped for the string and
-                // again for the regex, thus four escape characters become one.
-                String[] strings = value.split("(?<!\\\\),");
-                intent.putExtra(key, strings);
-                hasIntentInfo = true;
-            } else if (opt.equals("--esal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                // Split on commas unless they are preceeded by an escape.
-                // The escape character must be escaped for the string and
-                // again for the regex, thus four escape characters become one.
-                String[] strings = value.split("(?<!\\\\),");
-                ArrayList<String> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--ez")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired().toLowerCase();
-                // Boolean.valueOf() results in false for anything that is not "true", which is
-                // error-prone in shell commands
-                boolean arg;
-                if ("true".equals(value) || "t".equals(value)) {
-                    arg = true;
-                } else if ("false".equals(value) || "f".equals(value)) {
-                    arg = false;
+        return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() {
+            @Override
+            public boolean handleOption(String opt, ShellCommand cmd) {
+                if (opt.equals("-D")) {
+                    mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+                } else if (opt.equals("-W")) {
+                    mWaitOption = true;
+                } else if (opt.equals("-P")) {
+                    mProfileFile = nextArgRequired();
+                    mAutoStop = true;
+                } else if (opt.equals("--start-profiler")) {
+                    mProfileFile = nextArgRequired();
+                    mAutoStop = false;
+                } else if (opt.equals("--sampling")) {
+                    mSamplingInterval = Integer.parseInt(nextArgRequired());
+                } else if (opt.equals("-R")) {
+                    mRepeat = Integer.parseInt(nextArgRequired());
+                } else if (opt.equals("-S")) {
+                    mStopOption = true;
+                } else if (opt.equals("--track-allocation")) {
+                    mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
+                } else if (opt.equals("--user")) {
+                    mUserId = parseUserArg(nextArgRequired());
+                } else if (opt.equals("--receiver-permission")) {
+                    mReceiverPermission = nextArgRequired();
                 } else {
-                    try {
-                        arg = Integer.decode(value) != 0;
-                    } catch (NumberFormatException ex) {
-                        throw new IllegalArgumentException("Invalid boolean value: " + value);
-                    }
+                    return false;
                 }
-
-                intent.putExtra(key, arg);
-            } else if (opt.equals("-n")) {
-                String str = nextArgRequired();
-                ComponentName cn = ComponentName.unflattenFromString(str);
-                if (cn == null) throw new IllegalArgumentException("Bad component name: " + str);
-                intent.setComponent(cn);
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-p")) {
-                String str = nextArgRequired();
-                intent.setPackage(str);
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-f")) {
-                String str = nextArgRequired();
-                intent.setFlags(Integer.decode(str).intValue());
-            } else if (opt.equals("--grant-read-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            } else if (opt.equals("--grant-write-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            } else if (opt.equals("--grant-persistable-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-            } else if (opt.equals("--grant-prefix-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-            } else if (opt.equals("--exclude-stopped-packages")) {
-                intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-            } else if (opt.equals("--include-stopped-packages")) {
-                intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-            } else if (opt.equals("--debug-log-resolution")) {
-                intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
-            } else if (opt.equals("--activity-brought-to-front")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-            } else if (opt.equals("--activity-clear-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            } else if (opt.equals("--activity-clear-when-task-reset")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-            } else if (opt.equals("--activity-exclude-from-recents")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            } else if (opt.equals("--activity-launched-from-history")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
-            } else if (opt.equals("--activity-multiple-task")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-            } else if (opt.equals("--activity-no-animation")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-            } else if (opt.equals("--activity-no-history")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-            } else if (opt.equals("--activity-no-user-action")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-            } else if (opt.equals("--activity-previous-is-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-            } else if (opt.equals("--activity-reorder-to-front")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-            } else if (opt.equals("--activity-reset-task-if-needed")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-            } else if (opt.equals("--activity-single-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-            } else if (opt.equals("--activity-clear-task")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-            } else if (opt.equals("--activity-task-on-home")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            } else if (opt.equals("--receiver-registered-only")) {
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            } else if (opt.equals("--receiver-replace-pending")) {
-                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            } else if (opt.equals("--selector")) {
-                intent.setDataAndType(data, type);
-                intent = new Intent();
-            } else if (opt.equals("-D")) {
-                mStartFlags |= ActivityManager.START_FLAG_DEBUG;
-            } else if (opt.equals("-W")) {
-                mWaitOption = true;
-            } else if (opt.equals("-P")) {
-                mProfileFile = nextArgRequired();
-                mAutoStop = true;
-            } else if (opt.equals("--start-profiler")) {
-                mProfileFile = nextArgRequired();
-                mAutoStop = false;
-            } else if (opt.equals("--sampling")) {
-                mSamplingInterval = Integer.parseInt(nextArgRequired());
-            } else if (opt.equals("-R")) {
-                mRepeat = Integer.parseInt(nextArgRequired());
-            } else if (opt.equals("-S")) {
-                mStopOption = true;
-            } else if (opt.equals("--track-allocation")) {
-                mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
-            } else if (opt.equals("--user")) {
-                mUserId = parseUserArg(nextArgRequired());
-            } else if (opt.equals("--receiver-permission")) {
-                mReceiverPermission = nextArgRequired();
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
+                return true;
             }
-        }
-        intent.setDataAndType(data, type);
-
-        final boolean hasSelector = intent != baseIntent;
-        if (hasSelector) {
-            // A selector was specified; fix up.
-            baseIntent.setSelector(intent);
-            intent = baseIntent;
-        }
-
-        String arg = nextArg();
-        baseIntent = null;
-        if (arg == null) {
-            if (hasSelector) {
-                // If a selector has been specified, and no arguments
-                // have been supplied for the main Intent, then we can
-                // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
-                // need to have a component name specified yet, the
-                // selector will take care of that.
-                baseIntent = new Intent(Intent.ACTION_MAIN);
-                baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            }
-        } else if (arg.indexOf(':') >= 0) {
-            // The argument is a URI.  Fully parse it, and use that result
-            // to fill in any data not specified so far.
-            baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
-                    | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
-        } else if (arg.indexOf('/') >= 0) {
-            // The argument is a component name.  Build an Intent to launch
-            // it.
-            baseIntent = new Intent(Intent.ACTION_MAIN);
-            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            baseIntent.setComponent(ComponentName.unflattenFromString(arg));
-        } else {
-            // Assume the argument is a package name.
-            baseIntent = new Intent(Intent.ACTION_MAIN);
-            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            baseIntent.setPackage(arg);
-        }
-        if (baseIntent != null) {
-            Bundle extras = intent.getExtras();
-            intent.replaceExtras((Bundle)null);
-            Bundle uriExtras = baseIntent.getExtras();
-            baseIntent.replaceExtras((Bundle)null);
-            if (intent.getAction() != null && baseIntent.getCategories() != null) {
-                HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
-                for (String c : cats) {
-                    baseIntent.removeCategory(c);
-                }
-            }
-            intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
-            if (extras == null) {
-                extras = uriExtras;
-            } else if (uriExtras != null) {
-                uriExtras.putAll(extras);
-                extras = uriExtras;
-            }
-            intent.replaceExtras(extras);
-            hasIntentInfo = true;
-        }
-
-        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
-        return intent;
+        });
     }
 
     private void runStartService() throws Exception {
@@ -1407,6 +1090,21 @@
         }
     }
 
+    private void runUnlockUser() throws Exception {
+        int userId = Integer.parseInt(nextArgRequired());
+        String tokenHex = nextArg();
+        byte[] token = null;
+        if (tokenHex != null) {
+            token = HexDump.hexStringToByteArray(tokenHex);
+        }
+        boolean success = mAm.unlockUser(userId, token);
+        if (success) {
+            System.out.println("Success: user unlocked");
+        } else {
+            System.err.println("Error: could not unlock user");
+        }
+    }
+
     private static class StopUserCallback extends IStopUserCallback.Stub {
         private boolean mFinished = false;
 
diff --git a/cmds/appops/Android.mk b/cmds/appops/Android.mk
index 1e15204..6801ce9 100644
--- a/cmds/appops/Android.mk
+++ b/cmds/appops/Android.mk
@@ -3,14 +3,8 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := appops
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := appops
 LOCAL_SRC_FILES := appops
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
-
diff --git a/cmds/appops/appops b/cmds/appops/appops
index 407e551..25d2031 100755
--- a/cmds/appops/appops
+++ b/cmds/appops/appops
@@ -1,5 +1 @@
-# Script to start "appwidget" on the device, which has a very rudimentary shell.
-base=/system
-export CLASSPATH=$base/framework/appops.jar
-exec app_process $base/bin com.android.commands.appops.AppOpsCommand "$@"
-
+cmd appops $@
diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
deleted file mode 100644
index c9b9e58..0000000
--- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-** Copyright 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.commands.appops;
-
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import android.util.TimeUtils;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.os.BaseCommand;
-
-import java.io.PrintStream;
-import java.util.List;
-
-/**
- * This class is a command line utility for manipulating AppOps permissions.
- */
-public class AppOpsCommand extends BaseCommand {
-
-    public static void main(String[] args) {
-        new AppOpsCommand().run(args);
-    }
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println("usage: appops set [--user <USER_ID>] <PACKAGE> <OP> <MODE>\n"
-                + "       appops get [--user <USER_ID>] <PACKAGE> [<OP>]\n"
-                + "       appops reset [--user <USER_ID>] [<PACKAGE>]\n"
-                + "  <PACKAGE> an Android package name.\n"
-                + "  <OP>      an AppOps operation.\n"
-                + "  <MODE>    one of allow, ignore, deny, or default\n"
-                + "  <USER_ID> the user id under which the package is installed. If --user is not\n"
-                + "            specified, the current user is assumed.\n");
-    }
-
-    private static final String COMMAND_SET = "set";
-    private static final String COMMAND_GET = "get";
-    private static final String COMMAND_RESET = "reset";
-
-    @Override
-    public void onRun() throws Exception {
-        String command = nextArgRequired();
-        switch (command) {
-            case COMMAND_SET:
-                runSet();
-                break;
-
-            case COMMAND_GET:
-                runGet();
-                break;
-
-            case COMMAND_RESET:
-                runReset();
-                break;
-
-            default:
-                System.err.println("Error: Unknown command: '" + command + "'.");
-                break;
-        }
-    }
-
-    private static final String ARGUMENT_USER = "--user";
-
-    // Modes
-    private static final String MODE_ALLOW = "allow";
-    private static final String MODE_DENY = "deny";
-    private static final String MODE_IGNORE = "ignore";
-    private static final String MODE_DEFAULT = "default";
-
-    private int strOpToOp(String op) {
-        try {
-            return AppOpsManager.strOpToOp(op);
-        } catch (IllegalArgumentException e) {
-        }
-        try {
-            return Integer.parseInt(op);
-        } catch (NumberFormatException e) {
-        }
-        try {
-            return AppOpsManager.strDebugOpToOp(op);
-        } catch (IllegalArgumentException e) {
-            System.err.println("Error: " + e.getMessage());
-            return -1;
-        }
-    }
-
-    private void runSet() throws Exception {
-        String packageName = null;
-        String op = null;
-        String mode = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else if (mode == null) {
-                    mode = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        } else if (op == null) {
-            System.err.println("Error: Operation not specified.");
-            return;
-        } else if (mode == null) {
-            System.err.println("Error: Mode not specified.");
-            return;
-        }
-
-        final int opInt = strOpToOp(op);
-        if (opInt < 0) {
-            return;
-        }
-        final int modeInt;
-        switch (mode) {
-            case MODE_ALLOW:
-                modeInt = AppOpsManager.MODE_ALLOWED;
-                break;
-            case MODE_DENY:
-                modeInt = AppOpsManager.MODE_ERRORED;
-                break;
-            case MODE_IGNORE:
-                modeInt = AppOpsManager.MODE_IGNORED;
-                break;
-            case MODE_DEFAULT:
-                modeInt = AppOpsManager.MODE_DEFAULT;
-                break;
-            default:
-                System.err.println("Error: Mode " + mode + " is not valid,");
-                return;
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        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;
-        }
-        appOpsService.setMode(opInt, uid, packageName, modeInt);
-    }
-
-    private void runGet() throws Exception {
-        String packageName = null;
-        String op = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        }
-
-        final int opInt = op != null ? strOpToOp(op) : 0;
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        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;
-        }
-        List<AppOpsManager.PackageOps> ops = appOpsService.getOpsForPackage(uid, packageName,
-                op != null ? new int[] {opInt} : null);
-        if (ops == null || ops.size() <= 0) {
-            System.out.println("No operations.");
-            return;
-        }
-        final long now = System.currentTimeMillis();
-        for (int i=0; i<ops.size(); i++) {
-            List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
-            for (int j=0; j<entries.size(); j++) {
-                AppOpsManager.OpEntry ent = entries.get(j);
-                System.out.print(AppOpsManager.opToName(ent.getOp()));
-                System.out.print(": ");
-                switch (ent.getMode()) {
-                    case AppOpsManager.MODE_ALLOWED:
-                        System.out.print("allow");
-                        break;
-                    case AppOpsManager.MODE_IGNORED:
-                        System.out.print("ignore");
-                        break;
-                    case AppOpsManager.MODE_ERRORED:
-                        System.out.print("deny");
-                        break;
-                    case AppOpsManager.MODE_DEFAULT:
-                        System.out.print("default");
-                        break;
-                    default:
-                        System.out.print("mode=");
-                        System.out.print(ent.getMode());
-                        break;
-                }
-                if (ent.getTime() != 0) {
-                    System.out.print("; time=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getRejectTime() != 0) {
-                    System.out.print("; rejectTime=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getRejectTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getDuration() == -1) {
-                    System.out.print(" (running)");
-                } else if (ent.getDuration() != 0) {
-                    System.out.print("; duration=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(ent.getDuration(), sb);
-                    System.out.print(sb);
-                }
-                System.out.println();
-            }
-        }
-    }
-
-    private void runReset() throws Exception {
-        String packageName = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                String userStr = nextArgRequired();
-                if ("all".equals(userStr)) {
-                    userId = UserHandle.USER_ALL;
-                } else if ("current".equals(userStr)) {
-                    userId = UserHandle.USER_CURRENT;
-                } else if ("owner".equals(userStr) || "system".equals(userStr)) {
-                    userId = UserHandle.USER_SYSTEM;
-                } else {
-                    userId = Integer.parseInt(nextArgRequired());
-                }
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        appOpsService.resetAllModes(userId, packageName);
-        System.out.print("Reset all modes for: ");
-        if (userId == UserHandle.USER_ALL) {
-            System.out.print("all users");
-        } else {
-            System.out.print("user "); System.out.print(userId);
-        }
-        System.out.print(", ");
-        if (packageName == null) {
-            System.out.println("all packages");
-        } else {
-            System.out.print("package "); System.out.println(packageName);
-        }
-    }
-}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 031bdbd..659dc73 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -42,7 +42,6 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.content.pm.VerificationParams;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -60,20 +59,18 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import libcore.io.IoUtils;
-
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 
+import libcore.io.IoUtils;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -106,7 +103,7 @@
         System.exit(exitCode);
     }
 
-    public int run(String[] args) throws IOException, RemoteException {
+    public int run(String[] args) throws RemoteException {
         boolean validCommand = false;
         if (args.length < 1) {
             return showUsage();
@@ -142,19 +139,19 @@
         }
 
         if ("install-create".equals(op)) {
-            return runInstallCreate();
+            return runInstallSession();
         }
 
         if ("install-write".equals(op)) {
-            return runInstallWrite();
+            return runInstallSession();
         }
 
         if ("install-commit".equals(op)) {
-            return runInstallCommit();
+            return runInstallSession();
         }
 
         if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
-            return runInstallAbandon();
+            return runInstallSession();
         }
 
         if ("set-installer".equals(op)) {
@@ -299,6 +296,210 @@
         return -1;
     }
 
+    private static class LocalIntentReceiver {
+        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public int send(int code, Intent intent, String resolvedType,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                return 0;
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private int translateUserId(int userId, String logContext) {
+        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, logContext, "pm command");
+    }
+
+    private static String checkAbiArgument(String abi) {
+        if (TextUtils.isEmpty(abi)) {
+            throw new IllegalArgumentException("Missing ABI argument");
+        }
+        if ("-".equals(abi)) {
+            return abi;
+        }
+        final String[] supportedAbis = Build.SUPPORTED_ABIS;
+        for (String supportedAbi : supportedAbis) {
+            if (supportedAbi.equals(abi)) {
+                return abi;
+            }
+        }
+        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
+    }
+
+    private void writeSessionFile(int sessionId, String path, long sizeBytes) throws RemoteException {
+        final SessionInfo info = mInstaller.getSessionInfo(sessionId);
+
+        PackageInstaller.Session session = null;
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
+
+            if (path == null) {
+                in = new SizedInputStream(System.in, sizeBytes);
+            } else {
+                in = new FileInputStream(path);
+            }
+            out = session.openWrite("base.apk", 0, sizeBytes);
+
+            int total = 0;
+            byte[] buffer = new byte[65536];
+            int c;
+            while ((c = in.read(buffer)) != -1) {
+                total += c;
+                out.write(buffer, 0, c);
+
+                if (info.sizeBytes > 0) {
+                    final float fraction = ((float) c / (float) info.sizeBytes);
+                    session.addProgress(fraction);
+                }
+            }
+            session.fsync(out);
+        } catch (IOException ignore) {
+        } finally {
+            IoUtils.closeQuietly(out);
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private int commitSessionFile(int sessionId) throws RemoteException {
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
+
+            final LocalIntentReceiver receiver = new LocalIntentReceiver();
+            session.commit(receiver.getIntentSender());
+
+            final Intent result = receiver.getResult();
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                System.out.println("Success");
+            } else {
+                System.err.println("Failure ["
+                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            }
+            return status;
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    /*
+     * Keep this around to support existing users of the "pm install" command that may not be
+     * able to be updated [or, at least informed the API has changed] such as ddmlib.
+     *
+     * Moving the implementation of "pm install" to "cmd package install" changes the executing
+     * context. Instead of being a stand alone process, "cmd package install" runs in the
+     * system_server process. Due to SELinux rules, system_server cannot access many directories;
+     * one of which being the package install staging directory [/data/local/tmp].
+     *
+     * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
+     */
+    private int runInstall() throws RemoteException {
+        int userId = UserHandle.USER_ALL;
+        String installerPackageName = null;
+
+        final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+        String opt;
+        while ((opt = nextOption()) != null) {
+            if (opt.equals("-l")) {
+                params.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+            } else if (opt.equals("-r")) {
+                params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+            } else if (opt.equals("-i")) {
+                installerPackageName = nextArg();
+                if (installerPackageName == null) {
+                    throw new IllegalArgumentException("Missing installer package");
+                }
+            } else if (opt.equals("-t")) {
+                params.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+            } else if (opt.equals("-s")) {
+                params.installFlags |= PackageManager.INSTALL_EXTERNAL;
+            } else if (opt.equals("-f")) {
+                params.installFlags |= PackageManager.INSTALL_INTERNAL;
+            } else if (opt.equals("-d")) {
+                params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
+            } else if (opt.equals("-g")) {
+                params.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+            } else if (opt.equals("--originating-uri")) {
+                params.originatingUri = Uri.parse(nextOptionData());
+            } else if (opt.equals("--referrer")) {
+                params.referrerUri = Uri.parse(nextOptionData());
+            } else if (opt.equals("-p")) {
+                params.mode = SessionParams.MODE_INHERIT_EXISTING;
+                params.appPackageName = nextOptionData();
+                if (params.appPackageName == null) {
+                    throw new IllegalArgumentException("Missing inherit package name");
+                }
+            } else if (opt.equals("-S")) {
+                params.setSize(Long.parseLong(nextOptionData()));
+            } else if (opt.equals("--abi")) {
+                params.abiOverride = checkAbiArgument(nextOptionData());
+            } else if (opt.equals("--user")) {
+                userId = Integer.parseInt(nextOptionData());
+            } else if (opt.equals("--install-location")) {
+                params.installLocation = Integer.parseInt(nextOptionData());
+            } else if (opt.equals("--force-uuid")) {
+                params.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
+                params.volumeUuid = nextOptionData();
+                if ("internal".equals(params.volumeUuid)) {
+                    params.volumeUuid = null;
+                }
+            } else {
+                throw new IllegalArgumentException("Unknown option " + opt);
+            }
+        }
+
+        userId = translateUserId(userId, "runInstallCreate");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
+        }
+
+        long sizeBytes = params.sizeBytes;
+        String path = nextArg();
+        if ("-".equals(path)) {
+            path = null;
+        } else if (path != null) {
+            final File file = new File(path);
+            if (file.isFile()) {
+                sizeBytes = file.length();
+            }
+        }
+
+        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
+
+        writeSessionFile(sessionId, path, sizeBytes);
+        return commitSessionFile(sessionId);
+    }
+
+    private int runInstallSession() {
+        return runShellCommand("package", mArgs);
+    }
+
     /**
      * Execute the list sub-command.
      *
@@ -317,6 +518,10 @@
         return runShellCommand("package", mArgs);
     }
 
+    private int runUninstall() {
+        return runShellCommand("package", mArgs);
+    }
+
     private int runPath() {
         int userId = UserHandle.USER_SYSTEM;
         String option = nextOption();
@@ -370,49 +575,6 @@
         }
     }
 
-    /**
-     * Converts a failure code into a string by using reflection to find a matching constant
-     * in PackageManager.
-     */
-    private String installFailureToString(LocalPackageInstallObserver obs) {
-        final int result = obs.result;
-        Field[] fields = PackageManager.class.getFields();
-        for (Field f: fields) {
-            if (f.getType() == int.class) {
-                int modifiers = f.getModifiers();
-                // only look at public final static fields.
-                if (((modifiers & Modifier.FINAL) != 0) &&
-                        ((modifiers & Modifier.PUBLIC) != 0) &&
-                        ((modifiers & Modifier.STATIC) != 0)) {
-                    String fieldName = f.getName();
-                    if (fieldName.startsWith("INSTALL_FAILED_") ||
-                            fieldName.startsWith("INSTALL_PARSE_FAILED_")) {
-                        // get the int value and compare it to result.
-                        try {
-                            if (result == f.getInt(null)) {
-                                StringBuilder sb = new StringBuilder(64);
-                                sb.append(fieldName);
-                                if (obs.extraPermission != null) {
-                                    sb.append(" perm=");
-                                    sb.append(obs.extraPermission);
-                                }
-                                if (obs.extraPackage != null) {
-                                    sb.append(" pkg=" + obs.extraPackage);
-                                }
-                                return sb.toString();
-                            }
-                        } catch (IllegalAccessException e) {
-                            // this shouldn't happen since we only look for public static fields.
-                        }
-                    }
-                }
-            }
-        }
-
-        // couldn't find a matching constant? return the value
-        return Integer.toString(result);
-    }
-
     // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
     private int runSetAppLink() {
         int userId = UserHandle.USER_SYSTEM;
@@ -602,316 +764,6 @@
         }
     }
 
-    private int runInstall() {
-        int installFlags = 0;
-        int userId = UserHandle.USER_ALL;
-        String installerPackageName = null;
-
-        String opt;
-
-        String originatingUriString = null;
-        String referrer = null;
-        String abi = null;
-
-        while ((opt=nextOption()) != null) {
-            if (opt.equals("-l")) {
-                installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-            } else if (opt.equals("-r")) {
-                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-            } else if (opt.equals("-i")) {
-                installerPackageName = nextOptionData();
-                if (installerPackageName == null) {
-                    System.err.println("Error: no value specified for -i");
-                    return 1;
-                }
-            } else if (opt.equals("-t")) {
-                installFlags |= PackageManager.INSTALL_ALLOW_TEST;
-            } else if (opt.equals("-s")) {
-                // Override if -s option is specified.
-                installFlags |= PackageManager.INSTALL_EXTERNAL;
-            } else if (opt.equals("-f")) {
-                // Override if -s option is specified.
-                installFlags |= PackageManager.INSTALL_INTERNAL;
-            } else if (opt.equals("-d")) {
-                installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
-            } else if (opt.equals("-g")) {
-                installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
-            } else if (opt.equals("--originating-uri")) {
-                originatingUriString = nextOptionData();
-                if (originatingUriString == null) {
-                    System.err.println("Error: must supply argument for --originating-uri");
-                    return 1;
-                }
-            } else if (opt.equals("--referrer")) {
-                referrer = nextOptionData();
-                if (referrer == null) {
-                    System.err.println("Error: must supply argument for --referrer");
-                    return 1;
-                }
-            } else if (opt.equals("--abi")) {
-                abi = checkAbiArgument(nextOptionData());
-            } else if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-            } else {
-                System.err.println("Error: Unknown option: " + opt);
-                return 1;
-            }
-        }
-
-        userId = translateUserId(userId, "runInstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            installFlags |= PackageManager.INSTALL_ALL_USERS;
-        }
-
-        final Uri verificationURI;
-        final Uri originatingURI;
-        final Uri referrerURI;
-
-        if (originatingUriString != null) {
-            originatingURI = Uri.parse(originatingUriString);
-        } else {
-            originatingURI = null;
-        }
-
-        if (referrer != null) {
-            referrerURI = Uri.parse(referrer);
-        } else {
-            referrerURI = null;
-        }
-
-        // Populate apkURI, must be present
-        final String apkFilePath = nextArg();
-        System.err.println("\tpkg: " + apkFilePath);
-        if (apkFilePath == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-
-        // Populate verificationURI, optionally present
-        final String verificationFilePath = nextArg();
-        if (verificationFilePath != null) {
-            System.err.println("\tver: " + verificationFilePath);
-            verificationURI = Uri.fromFile(new File(verificationFilePath));
-        } else {
-            verificationURI = null;
-        }
-
-        LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
-        try {
-            VerificationParams verificationParams = new VerificationParams(verificationURI,
-                    originatingURI, referrerURI, VerificationParams.NO_UID, null);
-
-            mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
-                    installerPackageName, verificationParams, abi, userId);
-
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-                if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
-                    System.out.println("Success");
-                    return 0;
-                } else {
-                    System.err.println("Failure ["
-                            + installFailureToString(obs)
-                            + "]");
-                    return 1;
-                }
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    /**
-     * @param userId The user id to be translated.
-     * @param logContext Optional human readable text to provide some context in error log.
-     * @return Translated concrete user id.  This will include USER_ALL.
-     */
-    private int translateUserId(int userId, String logContext) {
-        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, logContext, "pm command");
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        int userId = UserHandle.USER_ALL;
-        String installerPackageName = null;
-
-        final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("-l")) {
-                params.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-            } else if (opt.equals("-r")) {
-                params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-            } else if (opt.equals("-i")) {
-                installerPackageName = nextArg();
-                if (installerPackageName == null) {
-                    throw new IllegalArgumentException("Missing installer package");
-                }
-            } else if (opt.equals("-t")) {
-                params.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
-            } else if (opt.equals("-s")) {
-                params.installFlags |= PackageManager.INSTALL_EXTERNAL;
-            } else if (opt.equals("-f")) {
-                params.installFlags |= PackageManager.INSTALL_INTERNAL;
-            } else if (opt.equals("-d")) {
-                params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
-            } else if (opt.equals("-g")) {
-                params.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
-            } else if (opt.equals("--originating-uri")) {
-                params.originatingUri = Uri.parse(nextOptionData());
-            } else if (opt.equals("--referrer")) {
-                params.referrerUri = Uri.parse(nextOptionData());
-            } else if (opt.equals("-p")) {
-                params.mode = SessionParams.MODE_INHERIT_EXISTING;
-                params.appPackageName = nextOptionData();
-                if (params.appPackageName == null) {
-                    throw new IllegalArgumentException("Missing inherit package name");
-                }
-            } else if (opt.equals("-S")) {
-                params.setSize(Long.parseLong(nextOptionData()));
-            } else if (opt.equals("--abi")) {
-                params.abiOverride = checkAbiArgument(nextOptionData());
-            } else if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-            } else if (opt.equals("--install-location")) {
-                params.installLocation = Integer.parseInt(nextOptionData());
-            } else if (opt.equals("--force-uuid")) {
-                params.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
-                params.volumeUuid = nextOptionData();
-                if ("internal".equals(params.volumeUuid)) {
-                    params.volumeUuid = null;
-                }
-            } else {
-                throw new IllegalArgumentException("Unknown option " + opt);
-            }
-        }
-
-        userId = translateUserId(userId, "runInstallCreate");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
-        }
-
-        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
-
-        // NOTE: adb depends on parsing this string
-        System.out.println("Success: created install session [" + sessionId + "]");
-        return 0;
-    }
-
-    private int runInstallWrite() throws IOException, RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(nextOptionData());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(nextArg());
-        final String splitName = nextArg();
-
-        String path = nextArg();
-        if ("-".equals(path)) {
-            path = null;
-        } else if (path != null) {
-            final File file = new File(path);
-            if (file.isFile()) {
-                sizeBytes = file.length();
-            }
-        }
-
-        final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
-        PackageInstaller.Session session = null;
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-
-            if (path != null) {
-                in = new FileInputStream(path);
-            } else {
-                in = new SizedInputStream(System.in, sizeBytes);
-            }
-            out = session.openWrite(splitName, 0, sizeBytes);
-
-            int total = 0;
-            byte[] buffer = new byte[65536];
-            int c;
-            while ((c = in.read(buffer)) != -1) {
-                total += c;
-                out.write(buffer, 0, c);
-
-                if (info.sizeBytes > 0) {
-                    final float fraction = ((float) c / (float) info.sizeBytes);
-                    session.addProgress(fraction);
-                }
-            }
-            session.fsync(out);
-
-            System.out.println("Success: streamed " + total + " bytes");
-            return 0;
-        } finally {
-            IoUtils.closeQuietly(out);
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            session.commit(receiver.getIntentSender());
-
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                Log.e(TAG, "Failure details: " + result.getExtras());
-                System.err.println("Failure ["
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-                return 1;
-            }
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-            session.abandon();
-            System.out.println("Success");
-            return 0;
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
     private int runSetInstaller() throws RemoteException {
         final String targetPackage = nextArg();
         final String installerPackageName = nextArg();
@@ -1080,80 +932,6 @@
         }
     }
 
-    private int runUninstall() throws RemoteException {
-        int flags = 0;
-        int userId = UserHandle.USER_ALL;
-
-        String opt;
-        while ((opt=nextOption()) != null) {
-            if (opt.equals("-k")) {
-                flags |= PackageManager.DELETE_KEEP_DATA;
-            } else if (opt.equals("--user")) {
-                String param = nextArg();
-                if (isNumber(param)) {
-                    userId = Integer.parseInt(param);
-                } else {
-                    showUsage();
-                    System.err.println("Error: Invalid user: " + param);
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: Unknown option: " + opt);
-                return 1;
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-
-        userId = translateUserId(userId, "runUninstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            flags |= PackageManager.DELETE_ALL_USERS;
-        } else {
-            PackageInfo info;
-            try {
-                info = mPm.getPackageInfo(pkg, 0, userId);
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-            if (info == null) {
-                System.err.println("Failure - not installed for " + userId);
-                return 1;
-            }
-            final boolean isSystem =
-                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            // If we are being asked to delete a system app for just one
-            // user set flag so it disables rather than reverting to system
-            // version of the app.
-            if (isSystem) {
-                flags |= PackageManager.DELETE_SYSTEM_APP;
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        mInstaller.uninstall(pkg, null /* callerPackageName */, flags,
-                receiver.getIntentSender(), userId);
-
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            System.out.println("Success");
-            return 0;
-        } else {
-            Log.e(TAG, "Failure details: " + result.getExtras());
-            System.err.println("Failure ["
-                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            return 1;
-        }
-    }
-
     static class ClearDataObserver extends IPackageDataObserver.Stub {
         boolean finished;
         boolean result;
@@ -1499,54 +1277,6 @@
         return 1;
     }
 
-    private static String checkAbiArgument(String abi) {
-        if (TextUtils.isEmpty(abi)) {
-            throw new IllegalArgumentException("Missing ABI argument");
-        }
-
-        if ("-".equals(abi)) {
-            return abi;
-        }
-
-        final String[] supportedAbis = Build.SUPPORTED_ABIS;
-        for (String supportedAbi : supportedAbis) {
-            if (supportedAbi.equals(abi)) {
-                return abi;
-            }
-        }
-
-        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
-    }
-
-    private static class LocalIntentReceiver {
-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public int send(int code, Intent intent, String resolvedType,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return 0;
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
     private String nextOption() {
         if (mNextArg >= mArgs.length) {
             return null;
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
index a675769..726167e 100644
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
@@ -291,7 +291,7 @@
         System.err.println("        settings [--user NUM] delete namespace key");
         System.err.println("        settings [--user NUM] list namespace");
         System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
-        System.err.println("If '--user NUM' is not given, the operations are performed on the"
+        System.err.println("If '--user NUM' is not given, the operations are performed on the "
                 + "system user.");
     }
 
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 1ee60b0..b208e43 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -86,6 +86,8 @@
             runBenchmark();
         } else if ("forget".equals(op)) {
             runForget();
+        } else if ("set-emulate-fbe".equals(op)) {
+            runSetEmulateFbe();
         } else {
             throw new IllegalArgumentException();
         }
@@ -137,6 +139,12 @@
                 StorageManager.DEBUG_FORCE_ADOPTABLE);
     }
 
+    public void runSetEmulateFbe() throws RemoteException {
+        final boolean emulateFbe = Boolean.parseBoolean(nextArg());
+        mSm.setDebugFlags(emulateFbe ? StorageManager.DEBUG_EMULATE_FBE : 0,
+                StorageManager.DEBUG_EMULATE_FBE);
+    }
+
     public void runPartition() throws RemoteException {
         final String diskId = nextArg();
         final String type = nextArg();
@@ -205,6 +213,8 @@
         System.err.println("");
         System.err.println("       sm forget [UUID|all]");
         System.err.println("");
+        System.err.println("       sm set-emulate-fbe [true|false]");
+        System.err.println("");
         return 1;
     }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6aa13..273483a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,14 +17,19 @@
 package android.accessibilityservice;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Region;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -36,7 +41,10 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * An accessibility service runs in the background and receives callbacks by the system
@@ -373,6 +381,8 @@
         public void init(int connectionId, IBinder windowToken);
         public boolean onGesture(int gestureId);
         public boolean onKeyEvent(KeyEvent event);
+        public void onMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY);
     }
 
     private int mConnectionId;
@@ -383,6 +393,8 @@
 
     private WindowManager mWindowManager;
 
+    private MagnificationController mMagnificationController;
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -396,6 +408,20 @@
     public abstract void onInterrupt();
 
     /**
+     * Dispatches service connection to internal components first, then the
+     * client code.
+     */
+    private void dispatchServiceConnected() {
+        if (mMagnificationController != null) {
+            mMagnificationController.onServiceConnected();
+        }
+
+        // The client gets to handle service connection last, after we've set
+        // up any state upon which their code may rely.
+        onServiceConnected();
+    }
+
+    /**
      * This method is a part of the {@link AccessibilityService} lifecycle and is
      * called after the system has successfully bound to the service. If is
      * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
@@ -513,6 +539,385 @@
     }
 
     /**
+     * Returns the magnification controller, which may be used to query and
+     * modify the state of display magnification.
+     * <p>
+     * <strong>Note:</strong> In order to control magnification, your service
+     * must declare the capability by setting the
+     * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+     * property in its meta-data. For more information, see
+     * {@link #SERVICE_META_DATA}.
+     *
+     * @return the magnification controller
+     */
+    @NonNull
+    public final MagnificationController getMagnificationController() {
+        if (mMagnificationController == null) {
+            mMagnificationController = new MagnificationController(this);
+        }
+        return mMagnificationController;
+    }
+
+    private void onMagnificationChanged(@NonNull Region region, float scale,
+            float centerX, float centerY) {
+        if (mMagnificationController != null) {
+            mMagnificationController.dispatchMagnificationChanged(
+                    region, scale, centerX, centerY);
+        }
+    }
+
+    /**
+     * Used to control and query the state of display magnification.
+     */
+    public static final class MagnificationController {
+        private final AccessibilityService mService;
+
+        /**
+         * Map of listeners to their handlers. Lazily created when adding the
+         * first magnification listener.
+         */
+        private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+
+        MagnificationController(@NonNull AccessibilityService service) {
+            mService = service;
+        }
+
+        /**
+         * Called when the service is connected.
+         */
+        void onServiceConnected() {
+            if (mListeners != null && !mListeners.isEmpty()) {
+                setMagnificationCallbackEnabled(true);
+            }
+        }
+
+        /**
+         * Adds the specified change listener to the list of magnification
+         * change listeners. The callback will occur on the service's main
+         * thread.
+         *
+         * @param listener the listener to add, must be non-{@code null}
+         */
+        public void addListener(@NonNull OnMagnificationChangedListener listener) {
+            addListener(listener, null);
+        }
+
+        /**
+         * Adds the specified change listener to the list of magnification
+         * change listeners. The callback will occur on the specified
+         * {@link Handler}'s thread, or on the service's main thread if the
+         * handler is {@code null}.
+         *
+         * @param listener the listener to add, must be non-null
+         * @param handler the handler on which the callback should execute, or
+         *        {@code null} to execute on the service's main thread
+         */
+        public void addListener(@NonNull OnMagnificationChangedListener listener,
+                @Nullable Handler handler) {
+            if (mListeners == null) {
+                mListeners = new ArrayMap<>();
+            }
+
+            final boolean shouldEnableCallback = mListeners.isEmpty();
+            mListeners.put(listener, handler);
+
+            if (shouldEnableCallback) {
+                // This may fail if the service is not connected yet, but if we
+                // still have listeners when it connects then we can try again.
+                setMagnificationCallbackEnabled(true);
+            }
+        }
+
+        /**
+         * Removes all instances of the specified change listener from the list
+         * of magnification change listeners.
+         *
+         * @param listener the listener to remove, must be non-null
+         * @return {@code true} if at least one instance of the listener was
+         *         removed
+         */
+        public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+            if (mListeners == null) {
+                return false;
+            }
+
+            final int keyIndex = mListeners.indexOfKey(listener);
+            final boolean hasKey = keyIndex >= 0;
+            if (hasKey) {
+                mListeners.removeAt(keyIndex);
+            }
+
+            if (hasKey && mListeners.isEmpty()) {
+                // We just removed the last listener, so we don't need
+                // callbacks from the service anymore.
+                setMagnificationCallbackEnabled(false);
+            }
+
+            return hasKey;
+        }
+
+        private void setMagnificationCallbackEnabled(boolean enabled) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    connection.setMagnificationCallbackEnabled(enabled);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+        }
+
+        /**
+         * Dispatches magnification changes to any registered listeners. This
+         * should be called on the service's main thread.
+         */
+        void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
+                final float centerX, final float centerY) {
+            if (mListeners == null || mListeners.isEmpty()) {
+                Slog.d(LOG_TAG, "Received magnification changed "
+                        + "callback with no listeners registered!");
+                setMagnificationCallbackEnabled(false);
+                return;
+            }
+
+            // Listeners may remove themselves. Perform a shallow copy to avoid
+            // concurrent modification.
+            final ArrayMap<OnMagnificationChangedListener, Handler> entries =
+                    new ArrayMap<>(mListeners);
+
+            for (int i = 0, count = entries.size(); i < count; i++) {
+                final OnMagnificationChangedListener listener = entries.keyAt(i);
+                final Handler handler = entries.valueAt(i);
+                if (handler != null) {
+                    handler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            listener.onMagnificationChanged(MagnificationController.this,
+                                    region, scale, centerX, centerY);
+                        }
+                    });
+                } else {
+                    // We're already on the main thread, just run the listener.
+                    listener.onMagnificationChanged(this, region, scale, centerX, centerY);
+                }
+            }
+        }
+
+        /**
+         * Returns the current magnification scale.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 1.0f}.
+         *
+         * @return the current magnification scale
+         */
+        public float getScale() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationScale();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain scale", re);
+                }
+            }
+            return 1.0f;
+        }
+
+        /**
+         * Returns the unscaled screen-relative X coordinate of the focal
+         * center of the magnified region. This is the point around which
+         * zooming occurs and is guaranteed to lie within the magnified
+         * region.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 0.0f}.
+         *
+         * @return the unscaled screen-relative X coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterX() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationCenterX();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain center X", re);
+                }
+            }
+            return 0.0f;
+        }
+
+        /**
+         * Returns the unscaled screen-relative Y coordinate of the focal
+         * center of the magnified region. This is the point around which
+         * zooming occurs and is guaranteed to lie within the magnified
+         * region.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 0.0f}.
+         *
+         * @return the unscaled screen-relative Y coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterY() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationCenterY();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain center Y", re);
+                }
+            }
+            return 0.0f;
+        }
+
+        /**
+         * Returns the region of the screen currently being magnified. If
+         * magnification is not enabled, the returned region will be empty.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return an empty region.
+         *
+         * @return the screen-relative bounds of the magnified region
+         */
+        @NonNull
+        public Region getMagnifiedRegion() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnifiedRegion();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+                }
+            }
+            return Region.obtain();
+        }
+
+        /**
+         * Resets magnification scale and center to their default (e.g. no
+         * magnification) values.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param animate {@code true} to animate from the current scale and
+         *                center or {@code false} to reset the scale and center
+         *                immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean reset(boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.resetMagnification(animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to reset", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Sets the magnification scale.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param scale the magnification scale to set, must be >= 1 and <= 5
+         * @param animate {@code true} to animate from the current scale or
+         *                {@code false} to set the scale immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean setScale(float scale, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(
+                            scale, Float.NaN, Float.NaN, animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to set scale", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Sets the center of the magnified viewport.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param centerX the unscaled screen-relative X coordinate on which to
+         *                center the viewport
+         * @param centerY the unscaled screen-relative Y coordinate on which to
+         *                center the viewport
+         * @param animate {@code true} to animate from the current viewport
+         *                center or {@code false} to set the center immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean setCenter(float centerX, float centerY, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(
+                            Float.NaN, centerX, centerY, animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to set center", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Listener for changes in the state of magnification.
+         */
+        public interface OnMagnificationChangedListener {
+            /**
+             * Called when the magnified region, scale, or center changes.
+             *
+             * @param controller the magnification controller
+             * @param region the new magnified region, may be empty if
+             *               magnification is not enabled (e.g. scale is 1)
+             * @param scale the new scale
+             * @param centerX the new X coordinate around which magnification is focused
+             * @param centerY the new Y coordinate around which magnification is focused
+             */
+            void onMagnificationChanged(@NonNull MagnificationController controller,
+                    @NonNull Region region, float scale, float centerX, float centerY);
+        }
+    }
+
+    /**
      * Performs a global action. Such an action can be performed
      * at any moment regardless of the current application or user
      * location in that application. For example going back, going
@@ -645,7 +1050,7 @@
         return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
             @Override
             public void onServiceConnected() {
-                AccessibilityService.this.onServiceConnected();
+                AccessibilityService.this.dispatchServiceConnected();
             }
 
             @Override
@@ -678,6 +1083,12 @@
             public boolean onKeyEvent(KeyEvent event) {
                 return AccessibilityService.this.onKeyEvent(event);
             }
+
+            @Override
+            public void onMagnificationChanged(@NonNull Region region,
+                    float scale, float centerX, float centerY) {
+                AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+            }
         });
     }
 
@@ -695,6 +1106,7 @@
         private static final int DO_ON_GESTURE = 4;
         private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
         private static final int DO_ON_KEY_EVENT = 6;
+        private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
 
         private final HandlerCaller mCaller;
 
@@ -741,6 +1153,18 @@
             mCaller.sendMessage(message);
         }
 
+        public void onMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = region;
+            args.arg2 = scale;
+            args.arg3 = centerX;
+            args.arg4 = centerY;
+
+            final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
+            mCaller.sendMessage(message);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -816,6 +1240,15 @@
                     }
                 } return;
 
+                case DO_ON_MAGNIFICATION_CHANGED: {
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final Region region = (Region) args.arg1;
+                    final float scale = (float) args.arg2;
+                    final float centerX = (float) args.arg3;
+                    final float centerY = (float) args.arg4;
+                    mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+                } return;
+
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4edb0c6..2c98fef 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -104,6 +104,12 @@
      */
     public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
 
+    /**
+     * Capability: This accessibility service can control display magnification.
+     * @see android.R.styleable#AccessibilityService_canControlMagnification
+     */
+    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
     private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
             new SparseArray<CapabilityInfo>();
     static {
@@ -123,6 +129,10 @@
                 new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
                         R.string.capability_title_canRequestFilterKeyEvents,
                         R.string.capability_desc_canRequestFilterKeyEvents));
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                        R.string.capability_title_canControlMagnification,
+                        R.string.capability_desc_canControlMagnification));
     }
 
     /**
@@ -502,6 +512,10 @@
                     .AccessibilityService_canRequestFilterKeyEvents, false)) {
                 mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
             }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canControlMagnification, false)) {
+                mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+            }
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -917,6 +931,8 @@
                 return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
                 return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+            case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+                return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 8b503dd..15666bf 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -17,6 +17,7 @@
 package android.accessibilityservice;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Region;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.KeyEvent;
@@ -39,4 +40,6 @@
     void clearAccessibilityCache();
 
     void onKeyEvent(in KeyEvent event, int sequence);
+
+    void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5f7a17d..6ac50bd 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.graphics.Region;
 import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -63,4 +64,19 @@
     boolean performGlobalAction(int action);
 
     oneway void setOnKeyEventResult(boolean handled, int sequence);
+
+    float getMagnificationScale();
+
+    float getMagnificationCenterX();
+
+    float getMagnificationCenterY();
+
+    Region getMagnifiedRegion();
+
+    boolean resetMagnification(boolean animate);
+
+    boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+        boolean animate);
+
+    void setMagnificationCallbackEnabled(boolean enabled);
 }
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 9c401c7f..041f591 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -116,6 +116,30 @@
      */
     public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
 
+    /**
+     * Bundle key used for the {@link String} account type in session bundle.
+     * This is used in the default implementation of
+     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     */
+    private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE";
+    /**
+     * Bundle key used for the {@link String} array of required features in
+     * session bundle. This is used in the default implementation of
+     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     */
+    private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+    /**
+     * Bundle key used for the {@link Bundle} options in session bundle. This is
+     * used in default implementation of {@link #startAddAccountSession} and
+     * {@link startUpdateCredentialsSession}.
+     */
+    private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+    /**
+     * Bundle key used for the {@link Account} account in session bundle. This is used
+     * used in default implementation of {@link startUpdateCredentialsSession}.
+     */
+    private static final String KEY_ACCOUNT = "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
+
     private final Context mContext;
 
     public AbstractAccountAuthenticator(Context context) {
@@ -336,6 +360,73 @@
                 handleException(response, "addAccountFromCredentials", account.toString(), e);
             }
         }
+
+        @Override
+        public void startAddAccountSession(IAccountAuthenticatorResponse response,
+                String accountType, String authTokenType, String[] features, Bundle options)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG,
+                        "startAddAccountSession: accountType " + accountType
+                        + ", authTokenType " + authTokenType
+                        + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+            }
+            checkBinderPermission();
+            try {
+                final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
+                        new AccountAuthenticatorResponse(response), accountType, authTokenType,
+                        features, options);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    if (result != null) {
+                        result.keySet(); // force it to be unparcelled
+                    }
+                    Log.v(TAG, "startAddAccountSession: result "
+                            + AccountManager.sanitizeResult(result));
+                }
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "startAddAccountSession", accountType, e);
+            }
+        }
+
+        @Override
+        public void startUpdateCredentialsSession(
+                IAccountAuthenticatorResponse response,
+                Account account,
+                String authTokenType,
+                Bundle loginOptions) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "startUpdateCredentialsSession: "
+                        + account
+                        + ", authTokenType "
+                        + authTokenType);
+            }
+            checkBinderPermission();
+            try {
+                final Bundle result = AbstractAccountAuthenticator.this
+                        .startUpdateCredentialsSession(
+                                new AccountAuthenticatorResponse(response),
+                                account,
+                                authTokenType,
+                                loginOptions);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    // Result may be null.
+                    if (result != null) {
+                        result.keySet(); // force it to be unparcelled
+                    }
+                    Log.v(TAG, "startUpdateCredentialsSession: result "
+                            + AccountManager.sanitizeResult(result));
+                }
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "startUpdateCredentialsSession",
+                        account.toString() + "," + authTokenType, e);
+            }
+        }
     }
 
     private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -603,4 +694,97 @@
         }).start();
         return null;
     }
+
+    /**
+     * Starts the add account session to authenticate user to an account of the
+     * specified accountType.
+     *
+     * @param response to send the result back to the AccountManager, will never
+     *            be null
+     * @param accountType the type of account to authenticate with, will never
+     *            be null
+     * @param authTokenType the type of auth token to retrieve after
+     *            authenticating with the account, may be null
+     * @param requiredFeatures a String array of authenticator-specific features
+     *            that the account authenticated with must support, may be null
+     * @param options a Bundle of authenticator-specific options, may be null
+     * @return a Bundle result or null if the result is to be returned via the
+     *         response. The result will contain either:
+     *         <ul>
+     *         <li>{@link AccountManager#KEY_INTENT}, or
+     *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
+     *         the account to device later, and if account is authenticated,
+     *         optional {@link AccountManager#KEY_PASSWORD} and
+     *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+     *         status of the account, or
+     *         <li>{@link AccountManager#KEY_ERROR_CODE} and
+     *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+     *         </ul>
+     * @throws NetworkErrorException if the authenticator could not honor the
+     *             request due to a network error
+     */
+    public Bundle startAddAccountSession(final AccountAuthenticatorResponse response,
+            final String accountType, final String authTokenType, final String[] requiredFeatures,
+            final Bundle options)
+            throws NetworkErrorException {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Bundle sessionBundle = new Bundle();
+                sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+                sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
+                sessionBundle.putBundle(KEY_OPTIONS, options);
+                Bundle result = new Bundle();
+                result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+                response.onResult(result);
+            }
+
+        }).start();
+        return null;
+    }
+
+    /**
+     * Asks user to re-authenticate for an account but defers updating the locally stored
+     * credentials.
+     *
+     * @param response to send the result back to the AccountManager, will never
+     *            be null
+     * @param account the account whose credentials are to be updated, will
+     *            never be null
+     * @param authTokenType the type of auth token to retrieve after updating
+     *            the credentials, may be null (TODO)
+     * @param options a Bundle of authenticator-specific options, may be null
+     * @return a Bundle result or null if the result is to be returned via the
+     *         response. The result will contain either:
+     *         <ul>
+     *         <li>{@link AccountManager#KEY_INTENT}, or
+     *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for updating the
+     *         locally stored credentials later, and if account is
+     *         re-authenticated, {@link AccountManager#KEY_PASSWORD} and
+     *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+     *         status of the account later, or
+     *         <li>{@link AccountManager#KEY_ERROR_CODE} and
+     *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+     *         </ul>
+     * @throws NetworkErrorException if the authenticator could not honor the
+     *             request due to a network error
+     */
+    public Bundle startUpdateCredentialsSession(final AccountAuthenticatorResponse response,
+            final Account account, final String authTokenType, final Bundle options)
+                    throws NetworkErrorException {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Bundle sessionBundle = new Bundle();
+                sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+                sessionBundle.putParcelable(KEY_ACCOUNT, account);
+                sessionBundle.putBundle(KEY_OPTIONS, options);
+                Bundle result = new Bundle();
+                result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+                response.onResult(result);
+            }
+
+        }).start();
+        return null;
+    }
 }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 0a7568a..5557905 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -240,6 +240,20 @@
      */
     public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
 
+    /**
+     * Bundle key used for a {@link Bundle} in result from
+     * {@link #startAddAccountSession} and friends which returns session data
+     * for installing an account later.
+     */
+    public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+
+    /**
+     * Bundle key used for the {@link String} account status token in result
+     * from {@link #startAddAccountSession} and friends which returns
+     * information about a particular account.
+     */
+    public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
+
     public static final String ACTION_AUTHENTICATOR_INTENT =
             "android.accounts.AccountAuthenticator";
     public static final String AUTHENTICATOR_META_DATA_NAME =
@@ -2590,4 +2604,170 @@
             }
         }
     }
+
+    /**
+     * Asks the user to authenticate with an account of a specified type. The
+     * authenticator for this account type processes this request with the
+     * appropriate user interface. If the user does elect to authenticate with a
+     * new account, a bundle of session data for installing the account later is
+     * returned with optional account password and account status token.
+     * <p>
+     * This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     * <p>
+     * <p>
+     * <b>NOTE:</b> The account will not be installed to the device by calling
+     * this api alone.
+     *
+     * @param accountType The type of account to add; must not be null
+     * @param authTokenType The type of auth token (see {@link #getAuthToken})
+     *            this account will need to be able to generate, null for none
+     * @param requiredFeatures The features (see {@link #hasFeatures}) this
+     *            account must have, null for none
+     * @param options Authenticator-specific options for the request, may be
+     *            null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *            authenticator-defined sub-Activity to prompt the user to
+     *            create an account; used only to call startActivity(); if null,
+     *            the prompt will not be launched directly, but the necessary
+     *            {@link Intent} will be returned to the caller instead
+     * @param callback Callback to invoke when the request completes, null for
+     *            no callback
+     * @param handler {@link Handler} identifying the callback thread, null for
+     *            the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *         these fields if activity was specified and user was authenticated
+     *         with an account:
+     *         <ul>
+     *         <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+     *         adding the the to the device later.
+     *         <li>{@link #KEY_PASSWORD} - optional, the password or password
+     *         hash of the account.
+     *         <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+     *         status of the account
+     *         </ul>
+     *         If no activity was specified, the returned Bundle contains only
+     *         {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     *         actual account creation process. If authenticator doesn't support
+     *         this method, the returned Bundle contains only
+     *         {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted
+     *         {@code options} needed to add account later. If an error
+     *         occurred, {@link AccountManagerFuture#getResult()} throws:
+     *         <ul>
+     *         <li>{@link AuthenticatorException} if no authenticator was
+     *         registered for this account type or the authenticator failed to
+     *         respond
+     *         <li>{@link OperationCanceledException} if the operation was
+     *         canceled for any reason, including the user canceling the
+     *         creation process or adding accounts (of this type) has been
+     *         disabled by policy
+     *         <li>{@link IOException} if the authenticator experienced an I/O
+     *         problem creating a new account, usually because of network
+     *         trouble
+     *         </ul>
+     */
+    public AccountManagerFuture<Bundle> startAddAccountSession(
+            final String accountType,
+            final String authTokenType,
+            final String[] requiredFeatures,
+            final Bundle options,
+            final Activity activity,
+            AccountManagerCallback<Bundle> callback,
+            Handler handler) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        final Bundle optionsIn = new Bundle();
+        if (options != null) {
+            optionsIn.putAll(options);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.startAddAccountSession(
+                        mResponse,
+                        accountType,
+                        authTokenType,
+                        requiredFeatures,
+                        activity != null,
+                        optionsIn);
+            }
+        }.start();
+    }
+
+    /**
+     * Asks the user to enter a new password for an account but not updating the
+     * saved credentials for the account until finishSession is
+     * called.
+     * <p>
+     * This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     * <p>
+     * <b>NOTE:</b> The saved credentials for the account alone will not be
+     * updated by calling this API alone .
+     *
+     * @param account The account to update credentials for
+     * @param authTokenType The credentials entered must allow an auth token of
+     *            this type to be created (but no actual auth token is
+     *            returned); may be null
+     * @param options Authenticator-specific options for the request; may be
+     *            null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *            authenticator-defined sub-Activity to prompt the user to enter
+     *            a password; used only to call startActivity(); if null, the
+     *            prompt will not be launched directly, but the necessary
+     *            {@link Intent} will be returned to the caller instead
+     * @param callback Callback to invoke when the request completes, null for
+     *            no callback
+     * @param handler {@link Handler} identifying the callback thread, null for
+     *            the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *         these fields if an activity was supplied and user was
+     *         successfully re-authenticated to the account (TODO: default impl
+     *         only returns escorw?):
+     *         <ul>
+     *         <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+     *         updating the local credentials on device later.
+     *         <li>{@link #KEY_PASSWORD} - optional, the password or password hash of the
+     *         account
+     *         <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check status of
+     *         the account
+     *         </ul>
+     *         If no activity was specified, the returned Bundle contains
+     *         {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     *         password prompt. If an error occurred,
+     *         {@link AccountManagerFuture#getResult()} throws:
+     *         <ul>
+     *         <li>{@link AuthenticatorException} if the authenticator failed to
+     *         respond
+     *         <li>{@link OperationCanceledException} if the operation was
+     *         canceled for any reason, including the user canceling the
+     *         password prompt
+     *         <li>{@link IOException} if the authenticator experienced an I/O
+     *         problem verifying the password, usually because of network
+     *         trouble
+     *         </ul>
+     */
+    public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
+            final Account account,
+            final String authTokenType,
+            final Bundle options,
+            final Activity activity,
+            final AccountManagerCallback<Bundle> callback,
+            final Handler handler) {
+        if (account == null) {
+            throw new IllegalArgumentException("account is null");
+        }
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.startUpdateCredentialsSession(
+                        mResponse,
+                        account,
+                        authTokenType,
+                        activity != null,
+                        options);
+            }
+        }.start();
+    }
 }
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 58612da..921fb19 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -83,4 +83,17 @@
      */
     void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
             in Bundle accountCredentials);
+
+    /**
+     * Starts the add account session by prompting the user for account information
+     * and return a Bundle containing data to finish the session later.
+     */
+    void startAddAccountSession(in IAccountAuthenticatorResponse response, String accountType,
+        String authTokenType, in String[] requiredFeatures, in Bundle options);
+
+    /**
+     * Prompts the user for a new password but does not write it to the IAccountManager.
+     */
+    void startUpdateCredentialsSession(in IAccountAuthenticatorResponse response, in Account account,
+        String authTokenType, in Bundle options);
 }
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0d95db1..8489e47 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -83,4 +83,12 @@
     String getPreviousName(in Account account);
     boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
 
+    /* Add account in two steps. */
+    void startAddAccountSession(in IAccountManagerResponse response, String accountType,
+        String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+        in Bundle options);
+
+    /* Update credentials in two steps. */
+    void startUpdateCredentialsSession(in IAccountManagerResponse response, in Account account,
+        String authTokenType, boolean expectActivityLaunch, in Bundle options);
 }
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index d8d2737..20d71a6 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -250,50 +250,19 @@
     /**
      * PathDataEvaluator is used to interpolate between two paths which are
      * represented in the same format but different control points' values.
-     * The path is represented as an array of PathDataNode here, which is
-     * fundamentally an array of floating point numbers.
+     * The path is represented as verbs and points for each of the verbs.
      */
-    private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> {
-        private PathParser.PathDataNode[] mNodeArray;
-
-        /**
-         * Create a PathParser.PathDataNode[] that does not reuse the animated value.
-         * Care must be taken when using this option because on every evaluation
-         * a new <code>PathParser.PathDataNode[]</code> will be allocated.
-         */
-        private PathDataEvaluator() {}
-
-        /**
-         * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call.
-         * Caution must be taken to ensure that the value returned from
-         * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
-         * used across threads. The value will be modified on each <code>evaluate()</code> call.
-         *
-         * @param nodeArray The array to modify and return from <code>evaluate</code>.
-         */
-        public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) {
-            mNodeArray = nodeArray;
-        }
+    private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
+        private final PathParser.PathData mPathData = new PathParser.PathData();
 
         @Override
-        public PathParser.PathDataNode[] evaluate(float fraction,
-                PathParser.PathDataNode[] startPathData,
-                PathParser.PathDataNode[] endPathData) {
-            if (!PathParser.canMorph(startPathData, endPathData)) {
+        public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
+                    PathParser.PathData endPathData) {
+            if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
                 throw new IllegalArgumentException("Can't interpolate between"
                         + " two incompatible pathData");
             }
-
-            if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) {
-                mNodeArray = PathParser.deepCopyNodes(startPathData);
-            }
-
-            for (int i = 0; i < startPathData.length; i++) {
-                mNodeArray[i].interpolatePathDataNode(startPathData[i],
-                        endPathData[i], fraction);
-            }
-
-            return mNodeArray;
+            return mPathData;
         }
     }
 
@@ -323,13 +292,14 @@
         if (valueType == VALUE_TYPE_PATH) {
             String fromString = styledAttributes.getString(valueFromId);
             String toString = styledAttributes.getString(valueToId);
-            PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
-            PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+            PathParser.PathData nodesFrom = fromString == null
+                    ? null : new PathParser.PathData(fromString);
+            PathParser.PathData nodesTo = toString == null
+                    ? null : new PathParser.PathData(toString);
 
             if (nodesFrom != null || nodesTo != null) {
                 if (nodesFrom != null) {
-                    TypeEvaluator evaluator =
-                            new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+                    TypeEvaluator evaluator = new PathDataEvaluator();
                     if (nodesTo != null) {
                         if (!PathParser.canMorph(nodesFrom, nodesTo)) {
                             throw new InflateException(" Can't morph from " + fromString + " to " +
@@ -342,8 +312,7 @@
                                 (Object) nodesFrom);
                     }
                 } else if (nodesTo != null) {
-                    TypeEvaluator evaluator =
-                            new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+                    TypeEvaluator evaluator = new PathDataEvaluator();
                     returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
                             (Object) nodesTo);
                 }
@@ -484,23 +453,25 @@
         TypeEvaluator evaluator = null;
         String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
         String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
-        PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
-        PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+        PathParser.PathData pathDataFrom = fromString == null
+                ? null : new PathParser.PathData(fromString);
+        PathParser.PathData pathDataTo = toString == null
+                ? null : new PathParser.PathData(toString);
 
-        if (nodesFrom != null) {
-            if (nodesTo != null) {
-                anim.setObjectValues(nodesFrom, nodesTo);
-                if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+        if (pathDataFrom != null) {
+            if (pathDataTo != null) {
+                anim.setObjectValues(pathDataFrom, pathDataTo);
+                if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
                     throw new InflateException(arrayAnimator.getPositionDescription()
                             + " Can't morph from " + fromString + " to " + toString);
                 }
             } else {
-                anim.setObjectValues((Object)nodesFrom);
+                anim.setObjectValues((Object)pathDataFrom);
             }
-            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
-        } else if (nodesTo != null) {
-            anim.setObjectValues((Object)nodesTo);
-            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+            evaluator = new PathDataEvaluator();
+        } else if (pathDataTo != null) {
+            anim.setObjectValues((Object)pathDataTo);
+            evaluator = new PathDataEvaluator();
         }
 
         if (DBG_ANIMATOR_INFLATER && evaluator != null) {
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6f65889..7f9a5d3 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -955,7 +955,6 @@
         mStarted = true;
         mPaused = false;
         mRunning = false;
-        mAnimationEndRequested = false;
         AnimationHandler animationHandler = AnimationHandler.getInstance();
         animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
 
@@ -1123,6 +1122,8 @@
             Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                     System.identityHashCode(this));
         }
+
+        mAnimationEndRequested = false;
         initAnimation();
         mRunning = true;
         if (mSeekFraction >= 0) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 472d97f..8bb0ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6618,6 +6618,17 @@
     }
 
     /**
+     * Set whether the caption should displayed directly on the content rather than push it down.
+     *
+     * This affects only freeform windows since they display the caption and only the main
+     * window of the activity. The caption is used to drag the window around and also shows
+     * maximize and close action buttons.
+     */
+    public void overlayWithDecorCaption(boolean overlay) {
+        mWindow.setOverlayDecorCaption(overlay);
+    }
+
+    /**
      * Interface for informing a translucent {@link Activity} once all visible activities below it
      * have completed drawing. This is necessary only after an {@link Activity} has been made
      * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ac54960..f7aee75 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -327,12 +327,39 @@
     /** @hide Process is being cached for later use and is empty. */
     public static final int PROCESS_STATE_CACHED_EMPTY = 16;
 
+    /** @hide Should this process state be considered a background state? */
+    public static final boolean isProcStateBackground(int procState) {
+        return procState >= PROCESS_STATE_BACKUP;
+    }
+
     /** @hide requestType for assist context: only basic information. */
     public static final int ASSIST_CONTEXT_BASIC = 0;
 
     /** @hide requestType for assist context: generate full AssistStructure. */
     public static final int ASSIST_CONTEXT_FULL = 1;
 
+    /** @hide Flag for registerUidObserver: report changes in process state. */
+    public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+    /** @hide Flag for registerUidObserver: report uid gone. */
+    public static final int UID_OBSERVER_GONE = 1<<1;
+
+    /** @hide Flag for registerUidObserver: report uid has become idle. */
+    public static final int UID_OBSERVER_IDLE = 1<<2;
+
+    /** @hide Flag for registerUidObserver: report uid has become active. */
+    public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+    public static final int APP_START_MODE_NORMAL = 0;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+    public static final int APP_START_MODE_DELAYED = 1;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+     * launches. */
+    public static final int APP_START_MODE_DISABLED = 2;
+
     /**
      * Lock task mode is not active.
      */
@@ -501,6 +528,15 @@
             return stackId == FULLSCREEN_WORKSPACE_STACK_ID
                     || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
         }
+
+        /**
+         * Returns true if animation specs should be constructed for app transition that moves
+         * the task to the specified stack.
+         */
+        public static boolean useAnimationSpecForAppTransition(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID
+                    || stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID;
+        }
     }
 
     /**
@@ -857,7 +893,7 @@
             if (mIcon != null) {
                 return mIcon;
             }
-            return loadTaskDescriptionIcon(mIconFilename);
+            return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
         }
 
         /** @hide */
@@ -871,11 +907,11 @@
         }
 
         /** @hide */
-        public static Bitmap loadTaskDescriptionIcon(String iconFilename) {
+        public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) {
             if (iconFilename != null) {
                 try {
                     return ActivityManagerNative.getDefault().
-                            getTaskDescriptionIcon(iconFilename);
+                            getTaskDescriptionIcon(iconFilename, userId);
                 } catch (RemoteException e) {
                 }
             }
@@ -1531,14 +1567,100 @@
         }
     }
 
+    /**
+     * Metadata related to the {@link TaskThumbnail}.
+     *
+     * @hide
+     */
+    public static class TaskThumbnailInfo implements Parcelable {
+        /** @hide */
+        public static final String ATTR_TASK_THUMBNAILINFO_PREFIX = "task_thumbnailinfo_";
+        private static final String ATTR_TASK_WIDTH =
+                ATTR_TASK_THUMBNAILINFO_PREFIX + "task_width";
+        private static final String ATTR_TASK_HEIGHT =
+                ATTR_TASK_THUMBNAILINFO_PREFIX + "task_height";
+        private static final String ATTR_SCREEN_ORIENTATION =
+                ATTR_TASK_THUMBNAILINFO_PREFIX + "screen_orientation";
+
+        public int taskWidth;
+        public int taskHeight;
+        public int screenOrientation;
+
+        public TaskThumbnailInfo() {
+            // Do nothing
+        }
+
+        private TaskThumbnailInfo(Parcel source) {
+            readFromParcel(source);
+        }
+
+        /**
+         * Resets this info state to the initial state.
+         * @hide
+         */
+        public void reset() {
+            taskWidth = 0;
+            taskHeight = 0;
+            screenOrientation = 0;
+        }
+
+        /** @hide */
+        public void saveToXml(XmlSerializer out) throws IOException {
+            out.attribute(null, ATTR_TASK_WIDTH, Integer.toString(taskWidth));
+            out.attribute(null, ATTR_TASK_HEIGHT, Integer.toString(taskHeight));
+            out.attribute(null, ATTR_SCREEN_ORIENTATION, Integer.toString(screenOrientation));
+        }
+
+        /** @hide */
+        public void restoreFromXml(String attrName, String attrValue) {
+            if (ATTR_TASK_WIDTH.equals(attrName)) {
+                taskWidth = Integer.parseInt(attrValue);
+            } else if (ATTR_TASK_HEIGHT.equals(attrName)) {
+                taskHeight = Integer.parseInt(attrValue);
+            } else if (ATTR_SCREEN_ORIENTATION.equals(attrName)) {
+                screenOrientation = Integer.parseInt(attrValue);
+            }
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(taskWidth);
+            dest.writeInt(taskHeight);
+            dest.writeInt(screenOrientation);
+        }
+
+        public void readFromParcel(Parcel source) {
+            taskWidth = source.readInt();
+            taskHeight = source.readInt();
+            screenOrientation = source.readInt();
+        }
+
+        public static final Creator<TaskThumbnailInfo> CREATOR = new Creator<TaskThumbnailInfo>() {
+            public TaskThumbnailInfo createFromParcel(Parcel source) {
+                return new TaskThumbnailInfo(source);
+            }
+            public TaskThumbnailInfo[] newArray(int size) {
+                return new TaskThumbnailInfo[size];
+            }
+        };
+    }
+
     /** @hide */
     public static class TaskThumbnail implements Parcelable {
         public Bitmap mainThumbnail;
         public ParcelFileDescriptor thumbnailFileDescriptor;
+        public TaskThumbnailInfo thumbnailInfo;
 
         public TaskThumbnail() {
         }
 
+        private TaskThumbnail(Parcel source) {
+            readFromParcel(source);
+        }
+
         public int describeContents() {
             if (thumbnailFileDescriptor != null) {
                 return thumbnailFileDescriptor.describeContents();
@@ -1559,6 +1681,12 @@
             } else {
                 dest.writeInt(0);
             }
+            if (thumbnailInfo != null) {
+                dest.writeInt(1);
+                thumbnailInfo.writeToParcel(dest, flags);
+            } else {
+                dest.writeInt(0);
+            }
         }
 
         public void readFromParcel(Parcel source) {
@@ -1572,6 +1700,11 @@
             } else {
                 thumbnailFileDescriptor = null;
             }
+            if (source.readInt() != 0) {
+                thumbnailInfo = TaskThumbnailInfo.CREATOR.createFromParcel(source);
+            } else {
+                thumbnailInfo = null;
+            }
         }
 
         public static final Creator<TaskThumbnail> CREATOR = new Creator<TaskThumbnail>() {
@@ -1582,10 +1715,6 @@
                 return new TaskThumbnail[size];
             }
         };
-
-        private TaskThumbnail(Parcel source) {
-            readFromParcel(source);
-        }
     }
 
     /** @hide */
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4449e4f..0b7b6fc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -756,7 +756,7 @@
             return true;
         }
 
-        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             final Rect r = Rect.CREATOR.createFromParcel(data);
@@ -1962,6 +1962,16 @@
             return true;
         }
 
+        case UNLOCK_USER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int userId = data.readInt();
+            byte[] token = data.createByteArray();
+            boolean result = unlockUser(userId, token);
+            reply.writeNoException();
+            reply.writeInt(result ? 1 : 0);
+            return true;
+        }
+
         case STOP_USER_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int userid = data.readInt();
@@ -2029,7 +2039,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             IUidObserver observer = IUidObserver.Stub.asInterface(
                     data.readStrongBinder());
-            registerUidObserver(observer);
+            int which = data.readInt();
+            registerUidObserver(observer, which);
             return true;
         }
 
@@ -2489,7 +2500,8 @@
         case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String filename = data.readString();
-            Bitmap icon = getTaskDescriptionIcon(filename);
+            int userId = data.readInt();
+            Bitmap icon = getTaskDescriptionIcon(filename, userId);
             reply.writeNoException();
             if (icon == null) {
                 reply.writeInt(0);
@@ -2698,13 +2710,22 @@
             reply.writeNoException();
             return true;
         }
-        case REMOVE_STACK: {
+        case REMOVE_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             removeStack(stackId);
             reply.writeNoException();
             return true;
         }
+        case GET_APP_START_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int uid = data.readInt();
+            final String pkg = data.readString();
+            int res = getAppStartMode(uid, pkg);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -3579,7 +3600,7 @@
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
         r.writeToParcel(data, 0);
-        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         final boolean res = reply.readInt() != 0;
         data.recycle();
@@ -5239,6 +5260,20 @@
         return result;
     }
 
+    public boolean unlockUser(int userId, byte[] token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(userId);
+        data.writeByteArray(token);
+        mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean result = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
+
     public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -5326,11 +5361,12 @@
         reply.recycle();
     }
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException {
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        data.writeInt(which);
         mRemote.transact(REGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -5987,11 +6023,12 @@
     }
 
     @Override
-    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
+    public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeString(filename);
+        data.writeInt(userId);
         mRemote.transact(GET_TASK_DESCRIPTION_ICON_TRANSACTION, data, reply, 0);
         reply.readException();
         final Bitmap icon = reply.readInt() == 0 ? null : Bitmap.CREATOR.createFromParcel(reply);
@@ -6292,11 +6329,26 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
-        mRemote.transact(REMOVE_STACK, data, reply, 0);
+        mRemote.transact(REMOVE_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
         reply.recycle();
     }
 
+    @Override
+    public int getAppStartMode(int uid, String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(uid);
+        data.writeString(packageName);
+        mRemote.transact(GET_APP_START_MODE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 57900aa..cee1aa5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -64,11 +64,13 @@
     public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
 
     /**
-     * The bounds that the activity should be started in. Set to null explicitly
-     * for full screen. If the key is not found, previous bounds will be preserved.
+     * The bounds (window size) that the activity should be launched in. Set to null explicitly for
+     * full screen. If the key is not found, previous bounds will be preserved.
+     * NOTE: This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
      * @hide
      */
-    public static final String KEY_BOUNDS = "android:activity.bounds";
+    public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
 
     /**
      * Type of animation that arguments specify.
@@ -193,8 +195,8 @@
     public static final int ANIM_CLIP_REVEAL = 11;
 
     private String mPackageName;
-    private boolean mHasBounds;
-    private Rect mBounds;
+    private boolean mHasLaunchBounds;
+    private Rect mLaunchBounds;
     private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
     private int mCustomExitResId;
@@ -705,9 +707,9 @@
         } catch (RuntimeException e) {
             Slog.w(TAG, e);
         }
-        mHasBounds = opts.containsKey(KEY_BOUNDS);
-        if (mHasBounds) {
-            mBounds = opts.getParcelable(KEY_BOUNDS);
+        mHasLaunchBounds = opts.containsKey(KEY_LAUNCH_BOUNDS);
+        if (mHasLaunchBounds) {
+            mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS);
         }
         mAnimationType = opts.getInt(KEY_ANIM_TYPE);
         switch (mAnimationType) {
@@ -766,10 +768,15 @@
         }
     }
 
-    /** @hide */
-    public ActivityOptions setBounds(Rect bounds) {
-        mHasBounds = true;
-        mBounds = bounds;
+    /**
+     * Sets the bounds (window size) that the activity should be launched in. Set to null explicitly
+     * for full screen.
+     * NOTE: This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
+     */
+    public ActivityOptions setLaunchBounds(Rect launchBounds) {
+        mHasLaunchBounds = true;
+        mLaunchBounds = launchBounds;
         return this;
     }
 
@@ -778,14 +785,12 @@
         return mPackageName;
     }
 
-    /** @hide */
-    public boolean hasBounds() {
-        return mHasBounds;
+    public boolean hasLaunchBounds() {
+        return mHasLaunchBounds;
     }
 
-    /** @hide */
-    public Rect getBounds(){
-        return mBounds;
+    public Rect getLaunchBounds(){
+        return mLaunchBounds;
     }
 
     /** @hide */
@@ -997,8 +1002,8 @@
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
-        if (mHasBounds) {
-            b.putParcelable(KEY_BOUNDS, mBounds);
+        if (mHasLaunchBounds) {
+            b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
         }
         b.putInt(KEY_ANIM_TYPE, mAnimationType);
         if (mUsageTimeReport != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b3d6382..802880d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -78,6 +78,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.security.NetworkSecurityPolicy;
+import android.security.net.config.NetworkSecurityConfigProvider;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -4831,6 +4832,11 @@
             }
         }
 
+        // Install the Network Security Config Provider. This must happen before the application
+        // code is loaded to prevent issues with instances of TLS objects being created before
+        // the provider is installed.
+        NetworkSecurityConfigProvider.install(appContext);
+
         // Continue loading instrumentation.
         if (ii != null) {
             final ApplicationInfo instrApp = new ApplicationInfo();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 371c923..c075ed6 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -24,8 +24,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.graphics.SurfaceTexture;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.OperationCanceledException;
@@ -45,6 +43,17 @@
 import dalvik.system.CloseGuard;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.android.internal.annotations.GuardedBy;
+
 
 /** @hide */
 public class ActivityView extends ViewGroup {
@@ -53,9 +62,64 @@
 
     private static final int MSG_SET_SURFACE = 1;
 
-    DisplayMetrics mMetrics = new DisplayMetrics();
+    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final int MINIMUM_POOL_SIZE = 1;
+    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+    private static final int KEEP_ALIVE = 1;
+
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private final AtomicInteger mCount = new AtomicInteger(1);
+
+        public Thread newThread(Runnable r) {
+            return new Thread(r, "ActivityView #" + mCount.getAndIncrement());
+        }
+    };
+
+    private static final BlockingQueue<Runnable> sPoolWorkQueue =
+            new LinkedBlockingQueue<Runnable>(128);
+
+    /**
+     * An {@link Executor} that can be used to execute tasks in parallel.
+     */
+    private static final Executor sExecutor = new ThreadPoolExecutor(MINIMUM_POOL_SIZE,
+            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
+
+
+    private static class SerialExecutor implements Executor {
+        private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
+        private Runnable mActive;
+
+        public synchronized void execute(final Runnable r) {
+            mTasks.offer(new Runnable() {
+                public void run() {
+                    try {
+                        r.run();
+                    } finally {
+                        scheduleNext();
+                    }
+                }
+            });
+            if (mActive == null) {
+                scheduleNext();
+            }
+        }
+
+        protected synchronized void scheduleNext() {
+            if ((mActive = mTasks.poll()) != null) {
+                sExecutor.execute(mActive);
+            }
+        }
+    }
+
+    private final SerialExecutor mExecutor = new SerialExecutor();
+
+    private final int mDensityDpi;
     private final TextureView mTextureView;
+
+    @GuardedBy("mActivityContainerLock")
     private ActivityContainerWrapper mActivityContainer;
+    private Object mActivityContainerLock = new Object();
+
     private Activity mActivity;
     private int mWidth;
     private int mHeight;
@@ -63,8 +127,6 @@
     private int mLastVisibility;
     private ActivityViewCallback mActivityViewCallback;
 
-    private HandlerThread mThread = new HandlerThread("ActivityViewThread");
-    private Handler mHandler;
 
     public ActivityView(Context context) {
         this(context, null);
@@ -97,28 +159,14 @@
                     + e);
         }
 
-        mThread.start();
-        mHandler = new Handler(mThread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == MSG_SET_SURFACE) {
-                    try {
-                        mActivityContainer.setSurface((Surface) msg.obj, msg.arg1, msg.arg2,
-                                mMetrics.densityDpi);
-                    } catch (RemoteException e) {
-                        throw new RuntimeException(
-                                "ActivityView: Unable to set surface of ActivityContainer. " + e);
-                    }
-                }
-            }
-        };
         mTextureView = new TextureView(context);
         mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener());
         addView(mTextureView);
 
         WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getMetrics(mMetrics);
+        DisplayMetrics metrics = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(metrics);
+        mDensityDpi = metrics.densityDpi;
 
         mLastVisibility = getVisibility();
 
@@ -131,15 +179,13 @@
     }
 
     @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
+    protected void onVisibilityChanged(View changedView, final int visibility) {
         super.onVisibilityChanged(changedView, visibility);
 
         if (mSurface != null && (visibility == View.GONE || mLastVisibility == View.GONE)) {
-            Message msg = Message.obtain(mHandler, MSG_SET_SURFACE);
-            msg.obj = (visibility == View.GONE) ? null : mSurface;
-            msg.arg1 = mWidth;
-            msg.arg2 = mHeight;
-            mHandler.sendMessage(msg);
+            if (DEBUG) Log.v(TAG, "visibility changed; enqueing runnable");
+            final Surface surface = (visibility == View.GONE) ? null : mSurface;
+            setSurfaceAsync(surface, mWidth, mHeight, mDensityDpi, false);
         }
         mLastVisibility = visibility;
     }
@@ -230,8 +276,10 @@
             Log.e(TAG, "Duplicate call to release");
             return;
         }
-        mActivityContainer.release();
-        mActivityContainer = null;
+        synchronized (mActivityContainerLock) {
+            mActivityContainer.release();
+            mActivityContainer = null;
+        }
 
         if (mSurface != null) {
             mSurface.release();
@@ -239,25 +287,39 @@
         }
 
         mTextureView.setSurfaceTextureListener(null);
-
-        mThread.quit();
     }
 
-    private void attachToSurfaceWhenReady() {
-        final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
-        if (surfaceTexture == null || mSurface != null) {
-            // Either not ready to attach, or already attached.
-            return;
-        }
-
-        mSurface = new Surface(surfaceTexture);
-        try {
-            mActivityContainer.setSurface(mSurface, mWidth, mHeight, mMetrics.densityDpi);
-        } catch (RemoteException e) {
-            mSurface.release();
-            mSurface = null;
-            throw new RuntimeException("ActivityView: Unable to create ActivityContainer. " + e);
-        }
+    private void setSurfaceAsync(final Surface surface, final int width, final int height,
+            final int densityDpi, final boolean callback) {
+        mExecutor.execute(new Runnable() {
+            public void run() {
+                try {
+                    synchronized (mActivityContainerLock) {
+                        if (mActivityContainer != null) {
+                            mActivityContainer.setSurface(surface, width, height, densityDpi);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    throw new RuntimeException(
+                        "ActivityView: Unable to set surface of ActivityContainer. ",
+                        e);
+                }
+                if (callback) {
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (mActivityViewCallback != null) {
+                                if (surface != null) {
+                                    mActivityViewCallback.onSurfaceAvailable(ActivityView.this);
+                                } else {
+                                    mActivityViewCallback.onSurfaceDestroyed(ActivityView.this);
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        });
     }
 
     /**
@@ -308,10 +370,8 @@
                     + height);
             mWidth = width;
             mHeight = height;
-            attachToSurfaceWhenReady();
-            if (mActivityViewCallback != null) {
-                mActivityViewCallback.onSurfaceAvailable(ActivityView.this);
-            }
+            mSurface = new Surface(surfaceTexture);
+            setSurfaceAsync(mSurface, mWidth, mHeight, mDensityDpi, true);
         }
 
         @Override
@@ -331,15 +391,7 @@
             if (DEBUG) Log.d(TAG, "onSurfaceTextureDestroyed");
             mSurface.release();
             mSurface = null;
-            try {
-                mActivityContainer.setSurface(null, mWidth, mHeight, mMetrics.densityDpi);
-            } catch (RemoteException e) {
-                throw new RuntimeException(
-                        "ActivityView: Unable to set surface of ActivityContainer. " + e);
-            }
-            if (mActivityViewCallback != null) {
-                mActivityViewCallback.onSurfaceDestroyed(ActivityView.this);
-            }
+            setSurfaceAsync(null, mWidth, mHeight, mDensityDpi, true);
             return true;
         }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 77a9795..f0453e9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -237,8 +237,10 @@
     public static final int OP_TURN_SCREEN_ON = 61;
     /** @hide Get device accounts. */
     public static final int OP_GET_ACCOUNTS = 62;
+    /** @hide Control whether an application is allowed to run in the background. */
+    public static final int OP_RUN_IN_BACKGROUND = 63;
     /** @hide */
-    public static final int _NUM_OP = 63;
+    public static final int _NUM_OP = 64;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -409,6 +411,7 @@
             OP_WRITE_EXTERNAL_STORAGE,
             OP_TURN_SCREEN_ON,
             OP_GET_ACCOUNTS,
+            OP_RUN_IN_BACKGROUND,
     };
 
     /**
@@ -478,7 +481,8 @@
             OPSTR_READ_EXTERNAL_STORAGE,
             OPSTR_WRITE_EXTERNAL_STORAGE,
             null,
-            OPSTR_GET_ACCOUNTS
+            OPSTR_GET_ACCOUNTS,
+            null,
     };
 
     /**
@@ -549,6 +553,7 @@
             "WRITE_EXTERNAL_STORAGE",
             "TURN_ON_SCREEN",
             "GET_ACCOUNTS",
+            "RUN_IN_BACKGROUND",
     };
 
     /**
@@ -618,7 +623,8 @@
             Manifest.permission.READ_EXTERNAL_STORAGE,
             Manifest.permission.WRITE_EXTERNAL_STORAGE,
             null, // no permission for turning the screen on
-            Manifest.permission.GET_ACCOUNTS
+            Manifest.permission.GET_ACCOUNTS,
+            null, // no permission for running in background
     };
 
     /**
@@ -690,6 +696,7 @@
             null, // WRITE_EXTERNAL_STORAGE
             null, // TURN_ON_SCREEN
             null, // GET_ACCOUNTS
+            null, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -760,6 +767,7 @@
             false, // WRITE_EXTERNAL_STORAGE
             false, // TURN_ON_SCREEN
             false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -829,6 +837,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
             AppOpsManager.MODE_ALLOWED,
+            AppOpsManager.MODE_ALLOWED,  // OP_RUN_IN_BACKGROUND
     };
 
     /**
@@ -901,7 +910,8 @@
             false,
             false,
             false,
-            false
+            false,
+            false,
     };
 
     /**
@@ -1329,7 +1339,7 @@
             IAppOpsCallback cb = mModeWatchers.get(callback);
             if (cb == null) {
                 cb = new IAppOpsCallback.Stub() {
-                    public void opChanged(int op, String packageName) {
+                    public void opChanged(int op, int uid, String packageName) {
                         if (callback instanceof OnOpChangedInternalListener) {
                             ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
                         }
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 0745537..20f3495 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -248,6 +248,7 @@
             } else if (mTransitioningViews != null) {
                 mTransitioningViews.addAll(mSharedElements);
             }
+            moveSharedElementsFromOverlay();
             mSharedElementNames.clear();
             mSharedElements.clear();
             mAllSharedElementNames.clear();
@@ -574,14 +575,20 @@
         setGhostVisibility(View.INVISIBLE);
         mHasStopped = true;
         mIsCanceled = true;
+        clearState();
+        return super.cancelPendingTransitions();
+    }
+
+    @Override
+    protected void clearState() {
+        mSharedElementsBundle = null;
+        mEnterViewsTransition = null;
         mResultReceiver = null;
         if (mBackgroundAnimator != null) {
             mBackgroundAnimator.cancel();
             mBackgroundAnimator = null;
         }
-        mActivity = null;
-        clearState();
-        return super.cancelPendingTransitions();
+        super.clearState();
     }
 
     private void makeOpaque() {
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 4b670cd..e93b40e 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -470,6 +470,11 @@
             mActivity = null;
         }
         // Clear the state so that we can't hold any references accidentally and leak memory.
+        clearState();
+    }
+
+    @Override
+    protected void clearState() {
         mHandler = null;
         mSharedElementBundle = null;
         if (mBackgroundAnimator != null) {
@@ -477,7 +482,7 @@
             mBackgroundAnimator = null;
         }
         mExitSharedElementBundle = null;
-        clearState();
+        super.clearState();
     }
 
     @Override
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b69a480..db4f5c1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -390,6 +390,7 @@
     // Multi-user APIs
     public boolean switchUser(int userid) throws RemoteException;
     public boolean startUserInBackground(int userid) throws RemoteException;
+    public boolean unlockUser(int userid, byte[] token) throws RemoteException;
     public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
     public UserInfo getCurrentUser() throws RemoteException;
     public boolean isUserRunning(int userid, int flags) throws RemoteException;
@@ -400,7 +401,7 @@
     public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
     public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException;
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException;
     public void unregisterUidObserver(IUidObserver observer) throws RemoteException;
 
     public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException;
@@ -497,7 +498,7 @@
     public void resizeTask(int taskId, Rect bounds, int resizeMode) throws RemoteException;
 
     public Rect getTaskBounds(int taskId) throws RemoteException;
-    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
+    public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException;
 
     public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
             throws RemoteException;
@@ -542,6 +543,8 @@
 
     public void removeStack(int stackId) throws RemoteException;
 
+    public int getAppStartMode(int uid, String packageName) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -899,6 +902,8 @@
     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;
+    int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
+    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
+    int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
+    int UNLOCK_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 351;
 }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 30232da..c1d5b19 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,14 +47,11 @@
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
 
-    void setPackagePriority(String pkg, int uid, int priority);
-    int getPackagePriority(String pkg, int uid);
-
-    void setPackagePeekable(String pkg, int uid, boolean peekable);
-    boolean getPackagePeekable(String pkg, int uid);
-
-    void setPackageVisibilityOverride(String pkg, int uid, int visibility);
-    int getPackageVisibilityOverride(String pkg, int uid);
+    ParceledListSlice getTopics(String pkg, int uid);
+    void setTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
+    int getTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic);
+    void setTopicPriority(String pkg, int uid, in Notification.Topic topic, int priority);
+    int getTopicPriority(String pkg, int uid, in Notification.Topic topic);
 
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 308cb94..fa8d0c9 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,6 +18,24 @@
 
 /** {@hide} */
 oneway interface IUidObserver {
+    /**
+     * General report of a state change of an uid.
+     */
     void onUidStateChanged(int uid, int procState);
+
+    /**
+     * Report that there are no longer any processes running for a uid.
+     */
     void onUidGone(int uid);
+
+    /**
+     * Report that a uid is now active (no longer idle).
+     */
+    void onUidActive(int uid);
+
+    /**
+     * Report that a uid is idle -- it has either been running in the background for
+     * a sufficient period of time, or all of its processes have gone away.
+     */
+    void onUidIdle(int uid);
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8b7abb7..891558f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -275,7 +275,7 @@
                 if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
                     final String isa = VMRuntime.getRuntime().vmInstructionSet();
                     try {
-                        ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa);
+                        ActivityThread.getPackageManager().notifyPackageUse(mPackageName);
                     } catch (RemoteException re) {
                         // Ignored.
                     }
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 9d8129c..3f1d113 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,3 +17,4 @@
 package android.app;
 
 parcelable Notification;
+parcelable Notification.Topic;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0b77be3..848b33f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -838,13 +839,6 @@
     public static final String EXTRA_PEOPLE = "android.people";
 
     /**
-     * {@link #extras} key: used to provide hints about the appropriateness of
-     * displaying this notification as a heads-up notification.
-     * @hide
-     */
-    public static final String EXTRA_AS_HEADS_UP = "headsup";
-
-    /**
      * Allow certain system-generated notifications to appear before the device is provisioned.
      * Only available to notifications coming from the android package.
      * @hide
@@ -887,32 +881,6 @@
      */
     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
 
-    /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
-     * displayed in the heads up space.
-     *
-     * <p>
-     * If this notification has a {@link #fullScreenIntent}, then it will always launch the
-     * full-screen intent when posted.
-     * </p>
-     * @hide
-     */
-    public static final int HEADS_UP_NEVER = 0;
-
-    /**
-     * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be
-     * displayed as a heads up.
-     * @hide
-     */
-    public static final int HEADS_UP_ALLOWED = 1;
-
-    /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a
-     * good candidate for display as a heads up.
-     * @hide
-     */
-    public static final int HEADS_UP_REQUESTED = 2;
-
     private Icon mSmallIcon;
     private Icon mLargeIcon;
 
@@ -1466,10 +1434,13 @@
                 };
     }
 
-    private Topic[] topics;
+    @SystemApi
+    public static final String TOPIC_DEFAULT = "system_default_topic";
 
-    public Topic[] getTopics() {
-        return topics;
+    private Topic topic;
+
+    public Topic getTopic() {
+        return topic;
     }
 
     /**
@@ -1599,7 +1570,9 @@
 
         color = parcel.readInt();
 
-        topics = parcel.createTypedArray(Topic.CREATOR); // may be null
+        if (parcel.readInt() != 0) {
+            topic = Topic.CREATOR.createFromParcel(parcel);
+        }
     }
 
     @Override
@@ -1700,11 +1673,8 @@
 
         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 (this.topic != null) {
+            that.topic = this.topic.clone();
         }
 
         if (!heavy) {
@@ -1878,7 +1848,12 @@
 
         parcel.writeInt(color);
 
-        parcel.writeTypedArray(topics, 0); // null ok
+        if (topic != null) {
+            parcel.writeInt(1);
+            topic.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
     }
 
     /**
@@ -2008,17 +1983,9 @@
             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("]");
+        if (topic != null) {
+            sb.append("topic=");
+            sb.append(topic.toString());
         }
         sb.append(")");
         return sb.toString();
@@ -2136,7 +2103,6 @@
         private ArrayList<String> mPersonList = new ArrayList<String>();
         private NotificationColorUtil mColorUtil;
         private boolean mColorUtilInited = false;
-        private List<Topic> mTopics = new ArrayList<>();
 
         /**
          * The user that built the notification originally.
@@ -2187,10 +2153,6 @@
                     Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
                 }
 
-                if (mN.getTopics() != null) {
-                    Collections.addAll(mTopics, mN.getTopics());
-                }
-
                 String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
                 if (!TextUtils.isEmpty(templateClass)) {
                     final Class<? extends Style> styleClass
@@ -2962,15 +2924,15 @@
         }
 
         /**
-         * Add a topic to this notification. Topics are typically displayed in Notification
+         * Sets the topic of 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);
+        public Builder setTopic(Topic topic) {
+            mN.topic = topic;
             return this;
         }
 
@@ -3280,12 +3242,16 @@
          * Construct a RemoteViews for the final big notification layout.
          */
         public RemoteViews makeBigContentView() {
-            if (mStyle != null) {
+            if (mN.bigContentView != null) {
+                return mN.bigContentView;
+            } else if (mStyle != null) {
                 final RemoteViews styleView = mStyle.makeBigContentView();
                 if (styleView != null) {
                     return styleView;
                 }
-            } else if (mActions.size() == 0) return null;
+            } else if (mActions.size() == 0) {
+                return null;
+            }
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
@@ -3294,12 +3260,17 @@
          * Construct a RemoteViews for the final heads-up notification layout.
          */
         public RemoteViews makeHeadsUpContentView() {
-            if (mStyle != null) {
-                final RemoteViews styleView = mStyle.makeHeadsUpContentView();
-                if (styleView != null) {
-                    return styleView;
-                }
-            } else if (mActions.size() == 0) return null;
+            if (mN.headsUpContentView != null) {
+                return mN.headsUpContentView;
+            } else if (mStyle != null) {
+                    final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+                    if (styleView != null) {
+                        return styleView;
+                    }
+            } else if (mActions.size() == 0) {
+                return null;
+            }
+
 
             return applyStandardTemplateWithActions(getBigBaseLayoutResource());
         }
@@ -3452,9 +3423,9 @@
                 mN.extras.putStringArray(EXTRA_PEOPLE,
                         mPersonList.toArray(new String[mPersonList.size()]));
             }
-            if (mTopics.size() > 0) {
-                mN.topics = new Topic[mTopics.size()];
-                mTopics.toArray(mN.topics);
+            if (mN.topic == null) {
+                mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
+                        R.string.default_notification_topic_label));
             }
             return mN;
         }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 07b4d39..043c503 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -610,15 +610,39 @@
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
         public final int priorityMessageSenders;
 
+        public static final int SUPPRESSED_EFFECTS_UNSET = -1;
+        public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 0;
+        public static final int SUPPRESSED_EFFECT_PEEK = 1 << 1;
+
+        private static final int[] ALL_SUPPRESSED_EFFECTS = {
+                SUPPRESSED_EFFECT_LIGHTS,
+                SUPPRESSED_EFFECT_PEEK,
+        };
+
+        /**
+         * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode.
+         * Bitmask of SUPPRESSED_EFFECT_* constants.
+         */
+        public final int suppressedVisualEffects;
+
+
+        @Deprecated
         public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) {
+            this(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    SUPPRESSED_EFFECTS_UNSET);
+        }
+
+        public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
+                int suppressedVisualEffects) {
             this.priorityCategories = priorityCategories;
             this.priorityCallSenders = priorityCallSenders;
             this.priorityMessageSenders = priorityMessageSenders;
+            this.suppressedVisualEffects = suppressedVisualEffects;
         }
 
         /** @hide */
         public Policy(Parcel source) {
-            this(source.readInt(), source.readInt(), source.readInt());
+            this(source.readInt(), source.readInt(), source.readInt(), source.readInt());
         }
 
         @Override
@@ -626,6 +650,7 @@
             dest.writeInt(priorityCategories);
             dest.writeInt(priorityCallSenders);
             dest.writeInt(priorityMessageSenders);
+            dest.writeInt(suppressedVisualEffects);
         }
 
         @Override
@@ -635,7 +660,8 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders);
+            return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    suppressedVisualEffects);
         }
 
         @Override
@@ -645,7 +671,8 @@
             final Policy other = (Policy) o;
             return other.priorityCategories == priorityCategories
                     && other.priorityCallSenders == priorityCallSenders
-                    && other.priorityMessageSenders == priorityMessageSenders;
+                    && other.priorityMessageSenders == priorityMessageSenders
+                    && other.suppressedVisualEffects == suppressedVisualEffects;
         }
 
         @Override
@@ -654,9 +681,29 @@
                     + "priorityCategories=" + priorityCategoriesToString(priorityCategories)
                     + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders)
                     + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
+                    + ",suppressedVisualEffects="
+                    + suppressedEffectsToString(suppressedVisualEffects)
                     + "]";
         }
 
+        public static String suppressedEffectsToString(int effects) {
+            if (effects <= 0) return "";
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) {
+                final int effect = ALL_SUPPRESSED_EFFECTS[i];
+                if ((effects & effect) != 0) {
+                    if (sb.length() > 0) sb.append(',');
+                    sb.append(effectToString(effect));
+                }
+                effects &= ~effect;
+            }
+            if (effects != 0) {
+                if (sb.length() > 0) sb.append(',');
+                sb.append("UNKNOWN_").append(effects);
+            }
+            return sb.toString();
+        }
+
         public static String priorityCategoriesToString(int priorityCategories) {
             if (priorityCategories == 0) return "";
             final StringBuilder sb = new StringBuilder();
@@ -675,6 +722,15 @@
             return sb.toString();
         }
 
+        private static String effectToString(int effect) {
+            switch (effect) {
+                case SUPPRESSED_EFFECT_LIGHTS: return "SUPPRESSED_EFFECT_LIGHTS";
+                case SUPPRESSED_EFFECT_PEEK: return "SUPPRESSED_EFFECT_PEEK";
+                case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET";
+                default: return "UNKNOWN_" + effect;
+            }
+        }
+
         private static String priorityCategoryToString(int priorityCategory) {
             switch (priorityCategory) {
                 case PRIORITY_CATEGORY_REMINDERS: return "PRIORITY_CATEGORY_REMINDERS";
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a3b3022..aca0763 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -23,10 +23,8 @@
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TimePicker;
 import android.widget.TimePicker.OnTimeChangedListener;
-import android.widget.TimePicker.ValidationCallback;
 
 import com.android.internal.R;
 
@@ -64,7 +62,7 @@
          * @param hourOfDay the hour that was set
          * @param minute the minute that was set
          */
-        public void onTimeSet(TimePicker view, int hourOfDay, int minute);
+        void onTimeSet(TimePicker view, int hourOfDay, int minute);
     }
 
     /**
@@ -115,7 +113,6 @@
 
         final TypedValue outValue = new TypedValue();
         context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
-        final int layoutResId = outValue.resourceId;
 
         final LayoutInflater inflater = LayoutInflater.from(themeContext);
         final View view = inflater.inflate(R.layout.time_picker_dialog, null);
@@ -129,7 +126,6 @@
         mTimePicker.setCurrentHour(mInitialHourOfDay);
         mTimePicker.setCurrentMinute(mInitialMinute);
         mTimePicker.setOnTimeChangedListener(this);
-        mTimePicker.setValidationCallback(mValidationCallback);
     }
 
     @Override
@@ -181,14 +177,4 @@
         mTimePicker.setCurrentHour(hour);
         mTimePicker.setCurrentMinute(minute);
     }
-
-    private final ValidationCallback mValidationCallback = new ValidationCallback() {
-        @Override
-        public void onValidationChanged(boolean valid) {
-            final Button positive = getButton(BUTTON_POSITIVE);
-            if (positive != null) {
-                positive.setEnabled(valid);
-            }
-        }
-    };
 }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index efed2e0..f7848f9 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -21,9 +21,11 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.Region;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.IBinder;
 import android.os.Looper;
@@ -1020,6 +1022,12 @@
                 public boolean onKeyEvent(KeyEvent event) {
                     return false;
                 }
+
+                @Override
+                public void onMagnificationChanged(@NonNull Region region,
+                        float scale, float centerX, float centerY) {
+                    /* do nothing */
+                }
             });
         }
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a8ab1e1..89e974e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.net.ProxyInfo;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -122,11 +123,15 @@
      * Provisioning adds a managed profile and sets the MDM as the profile owner who has full
      * control over the profile.
      *
-     * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
+     * <p>It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
+     *
+     * <p>In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
      * extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
      * As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
      * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
      * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
+     * This intent may also contain the extra {@link #EXTRA_PROVISIONING_LOGO_URI}.
      *
      * <p> When managed provisioning has completed, broadcasts are sent to the application specified
      * in the provisioning intent. The
@@ -148,6 +153,31 @@
         = "android.app.action.PROVISION_MANAGED_PROFILE";
 
     /**
+     * @hide
+     * Activity action: Starts the provisioning flow which sets up a managed user.
+     *
+     * <p>This intent will typically be sent by a mobile device management application (MDM).
+     * Provisioning configures the current user as managed user and sets the MDM as the profile
+     * owner who has full control over the user. Provisioning can only happen before user setup has
+     * been completed. Use {@link #isProvisioningAllowed(String)} to check if provisioning is
+     * allowed.
+     *
+     * <p>This intent should contain the extra
+     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
+     *
+     * <p> If provisioning fails, the device returns to its previous state.
+     *
+     * <p>If launched with {@link android.app.Activity#startActivityForResult(Intent, int)} a
+     * result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part of
+     * the provisioning flow was successful, although this doesn't guarantee the full flow will
+     * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+     * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PROVISION_MANAGED_USER
+        = "android.app.action.PROVISION_MANAGED_USER";
+
+    /**
      * Activity action: Starts the provisioning flow which sets up a managed device.
      * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
      *
@@ -159,10 +189,56 @@
      * employee or client.
      *
      * <p> An intent with this action can be sent only on an unprovisioned device.
-     * It is possible to check if the device is provisioned or not by looking at
-     * {@link android.provider.Settings.Global#DEVICE_PROVISIONED}
+     * It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
      *
-     * The intent contains the following extras:
+     * <p>The intent contains the following extras:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
+     * </ul>
+     *
+     * <p> When device owner provisioning has completed, an intent of the type
+     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
+     * device owner.
+     *
+     * <p> If provisioning fails, the device is factory reset.
+     *
+     * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
+     * of the provisioning flow was successful, although this doesn't guarantee the full flow will
+     * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+     * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PROVISION_MANAGED_DEVICE
+        = "android.app.action.PROVISION_MANAGED_DEVICE";
+
+    /**
+     * Activity action: Starts the provisioning flow which sets up a managed device.
+     * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
+     *
+     * <p>NOTE: This is only supported on split system user devices, and puts the device into a
+     * management state that is distinct from that reached by
+     * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
+     * user, and only has control over device-wide policies, not individual users and their data.
+     * The primary benefit is that multiple non-system users are supported when provisioning using
+     * this form of device management.
+     *
+     * <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> An intent with this action can be sent only on an unprovisioned device.
+     * It is possible to check if provisioning is allowed or not by querying the method
+     * {@link #isProvisioningAllowed(String)}.
+     *
+     * <p>The intent contains the following extras:
      * <ul>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
@@ -180,10 +256,12 @@
      * of the provisioning flow was successful, although this doesn't guarantee the full flow will
      * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
      * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+     *
+     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_PROVISION_MANAGED_DEVICE
-        = "android.app.action.PROVISION_MANAGED_DEVICE";
+    public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
+        = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
 
     /**
      * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
@@ -498,6 +576,28 @@
              "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
 
     /**
+     * A {@link Uri} extra pointing to a logo image. This image will be shown during the
+     * provisioning. If this extra is not passed, a default image will be shown.
+     * <h5>The following URI schemes are accepted:</h5>
+     * <ul>
+     * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+     * </ul>
+     *
+     * <p> It is the responsability of the caller to provide an image with a reasonable
+     * pixed density for the device.
+     *
+     * <p> If a content: URI is passed, the intent should have the flag
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
+     * {@link android.content.ClipData} of the intent too.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
+     * {@link #ACTION_PROVISION_MANAGED_DEVICE}
+     */
+    public static final String EXTRA_PROVISIONING_LOGO_URI =
+            "android.app.extra.PROVISIONING_LOGO_URI";
+
+    /**
      * 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.
@@ -1636,7 +1736,16 @@
      * Force a new device unlock password (the password needed to access the
      * entire device, not for individual accounts) on the user.  This takes
      * effect immediately.
-     * The given password must be sufficient for the
+     *
+     * <p>Calling this from a managed profile that shares the password with the owner profile
+     * will throw a security exception.
+     *
+     * <p><em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
+     * device admins that are not device owner and not profile owner.
+     * The password can now only be changed if there is currently no password set.  Device owner
+     * and profile owner can still do this.</em>
+     *
+     * <p>The given password must be sufficient for the
      * current password quality and length constraints as returned by
      * {@link #getPasswordQuality(ComponentName)} and
      * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet
@@ -1646,19 +1755,20 @@
      * the currently active quality will be increased to match.
      *
      * <p>Calling with a null or empty password will clear any existing PIN,
-     * pattern or password if the current password constraints allow it.
+     * pattern or password if the current password constraints allow it. <em>Note: This will not
+     * work in {@link android.os.Build.VERSION_CODES#N} and later for device admins that are not
+     * device owner and not profile owner.  Once set, the password cannot be changed to null or
+     * empty, except by device owner or profile owner.</em>
      *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
      *
-     * <p>Calling this from a managed profile will throw a security exception.
-     *
      * @param password The new password for the user. Null or empty clears the password.
      * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
      *              {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
      * @return Returns true if the password was applied, or false if it is
-     * not acceptable for the current constraints.
+     * not acceptable for the current constraints or if the user has not been decrypted yet.
      */
     public boolean resetPassword(String password, int flags) {
         if (mService != null) {
@@ -1766,7 +1876,7 @@
     public void wipeData(int flags) {
         if (mService != null) {
             try {
-                mService.wipeData(flags, myUserId());
+                mService.wipeData(flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2642,14 +2752,14 @@
      * 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.isDeviceOwnerPackage(packageName);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed talking with device policy service", e);
-            }
+        if (packageName == null) {
+            return false;
         }
-        return false;
+        final ComponentName deviceOwner = getDeviceOwnerComponent();
+        if (deviceOwner == null) {
+            return false;
+        }
+        return packageName.equals(deviceOwner.getPackageName());
     }
 
     /**
@@ -2843,9 +2953,6 @@
      */
     public boolean setProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName,
             int userHandle) throws IllegalArgumentException {
-        if (admin == null) {
-            throw new NullPointerException("admin cannot be null");
-        }
         if (mService != null) {
             try {
                 if (ownerName == null) {
@@ -2861,6 +2968,42 @@
     }
 
     /**
+     * Sets the device owner information to be shown on the lock screen.
+     *
+     * <p>If the device owner information is {@code null} or empty then the device owner info is
+     * cleared and the user owner info is shown on the lock screen if it is set.
+     *
+     * @param admin The name of the admin component to check.
+     * @param info Device owner information which will be displayed instead of the user
+     * owner info.
+     * @return Whether the device owner information has been set.
+     */
+    public boolean setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, String info) {
+        if (mService != null) {
+            try {
+                return mService.setDeviceOwnerLockScreenInfo(admin, info);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed talking with device policy service", re);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return The device owner information. If it is not set returns {@code null}.
+     */
+    public String getDeviceOwnerLockScreenInfo() {
+        if (mService != null) {
+            try {
+                return mService.getDeviceOwnerLockScreenInfo();
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed talking with device policy service", re);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Sets the enabled state of the profile. A profile should be enabled only once it is ready to
      * be used. Only the profile owner can call this.
      *
@@ -3473,6 +3616,48 @@
     }
 
     /**
+     * Called by a device owner to get the list of apps to keep around as APKs even if no user has
+     * currently installed it.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     *
+     * @return List of package names to keep cached.
+     * @hide
+     */
+    public List<String> getKeepUninstalledPackages(@NonNull ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getKeepUninstalledPackages(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Called by a device owner to set a list of apps to keep around as APKs even if no user has
+     * currently installed it.
+     *
+     * <p>Please note that setting this policy does not imply that specified apps will be
+     * automatically pre-cached.</p>
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageNames List of package names to keep cached.
+     * @hide
+     */
+    public void setKeepUninstalledPackages(@NonNull ComponentName admin,
+            @NonNull List<String> packageNames) {
+        if (mService != null) {
+            try {
+                mService.setKeepUninstalledPackages(admin, packageNames);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
      * Called by a device owner to create a user with the specified name. The UserHandle returned
      * by this method should not be persisted as user handles are recycled as users are removed and
      * created. If you need to persist an identifier for this user, use
@@ -3645,12 +3830,18 @@
      * {@link UserManager#getUserRestrictions()}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @throws SecurityException if the {@code admin} is not an active admin.
      */
     public Bundle getUserRestrictions(@NonNull ComponentName admin) {
+        return getUserRestrictions(admin, myUserId());
+    }
+
+    /** @hide per-user version */
+    public Bundle getUserRestrictions(@NonNull ComponentName admin, int userHandle) {
         Bundle ret = null;
         if (mService != null) {
             try {
-                ret = mService.getUserRestrictions(admin);
+                ret = mService.getUserRestrictions(admin, userHandle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 4270e16..0a0d77d 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,8 +16,6 @@
 
 package android.app.admin;
 
-import android.os.Bundle;
-
 import java.util.List;
 
 /**
@@ -71,13 +69,4 @@
      * @return true if the uid is an active admin with the given policy.
      */
     public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
-
-    /**
-     * Takes a {@link Bundle} containing "base" user restrictions stored in
-     * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
-     * and the profile owner and returns the merged restrictions.
-     *
-     * This method always returns a new {@link Bundle}.
-     */
-    public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 95a22ef..c43fa9a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -81,7 +81,7 @@
 
     void lockNow();
 
-    void wipeData(int flags, int userHandle);
+    void wipeData(int flags);
 
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
@@ -114,7 +114,6 @@
     void reportSuccessfulPasswordAttempt(int userHandle);
 
     boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
-    boolean isDeviceOwnerPackage(String packageName);
     ComponentName getDeviceOwner();
     String getDeviceOwnerName();
     void clearDeviceOwner(String packageName);
@@ -127,6 +126,9 @@
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
 
+    boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
+    String getDeviceOwnerLockScreenInfo();
+
     boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
     void uninstallCaCerts(in ComponentName admin, in String[] aliases);
     void enforceCanManageCaCerts(in ComponentName admin);
@@ -147,7 +149,7 @@
     ComponentName getRestrictionsProvider(int userHandle);
 
     void setUserRestriction(in ComponentName who, in String key, boolean enable);
-    Bundle getUserRestrictions(in ComponentName who);
+    Bundle getUserRestrictions(in ComponentName who, int userId);
     void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
     void clearCrossProfileIntentFilters(in ComponentName admin);
 
@@ -229,4 +231,6 @@
             String permission, int grantState);
     int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
     boolean isProvisioningAllowed(String action);
+    void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
+    List<String> getKeepUninstalledPackages(in ComponentName admin);
 }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 25d9aa9..09a15de 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -685,6 +685,48 @@
     }
 
     /**
+     * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
+     * audio to the HF unless explicitly told to.
+     * This method should be used in cases where the SCO channel is shared between multiple profiles
+     * and must be delegated by a source knowledgeable
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
+     *
+     * @hide
+     */
+    public void setAudioRouteAllowed(boolean allowed) {
+        if (VDBG) log("setAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.setAudioRouteAllowed(allowed);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @hide
+     */
+    public boolean getAudioRouteAllowed() {
+        if (VDBG) log("getAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getAudioRouteAllowed();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
      * Check if Bluetooth SCO audio is connected.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 0e23fad..0bb4088 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,6 +50,8 @@
     boolean isAudioOn();
     boolean connectAudio();
     boolean disconnectAudio();
+    void setAudioRouteAllowed(boolean allowed);
+    boolean getAudioRouteAllowed();
     boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
     boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
     void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 68b77fe..4a7cbc7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.os.ResultReceiver;
+import android.os.ShellCommand;
 import android.provider.MediaStore;
 import android.util.ArraySet;
 
@@ -57,11 +58,13 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -1423,6 +1426,36 @@
     public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
 
     /**
+     * Activity Action: Launch ephemeral installer.
+     * <p>
+     * Input: The data must be a http: URI that the ephemeral application is registered
+     * to handle.
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+            = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+
+    /**
+     * Service Action: Resolve ephemeral application.
+     * <p>
+     * The system will have a persistent connection to this service.
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+            = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+
+    /**
      * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
      * package.  Specifies the installer package name; this package will receive the
      * {@link #ACTION_APP_ERROR} intent.
@@ -2923,8 +2956,8 @@
      * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
      * to indicate this.
      * <p>
-     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
-     * returned URIs can be opened with
+     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+     * URIs that can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
      * Output: The URI of the item that was picked, returned in
@@ -2959,8 +2992,8 @@
      * Callers can provide an initial display name through {@link #EXTRA_TITLE},
      * but the user may change this value before creating the file.
      * <p>
-     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
-     * returned URIs can be opened with
+     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+     * URIs that can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
      * Output: The URI of the item that was created. This must be a
@@ -3054,21 +3087,21 @@
 
     /**
      * Thermal state when the device is normal. This state is sent in the
-     * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_NORMAL = 0;
 
     /**
      * Thermal state where the device is approaching its maximum threshold. This state is sent in
-     * the {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_WARNING = 1;
 
     /**
      * Thermal state where the device has reached its maximum threshold. This state is sent in the
-     * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2;
@@ -5083,6 +5116,437 @@
         return intent;
     }
 
+    /** @hide */
+    public interface CommandOptionHandler {
+        boolean handleOption(String opt, ShellCommand cmd);
+    }
+
+    /** @hide */
+    public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
+            throws URISyntaxException {
+        Intent intent = new Intent();
+        Intent baseIntent = intent;
+        boolean hasIntentInfo = false;
+
+        Uri data = null;
+        String type = null;
+
+        String opt;
+        while ((opt=cmd.getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    intent.setAction(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-d":
+                    data = Uri.parse(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-t":
+                    type = cmd.getNextArgRequired();
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-c":
+                    intent.addCategory(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-e":
+                case "--es": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, value);
+                }
+                break;
+                case "--esn": {
+                    String key = cmd.getNextArgRequired();
+                    intent.putExtra(key, (String) null);
+                }
+                break;
+                case "--ei": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Integer.decode(value));
+                }
+                break;
+                case "--eu": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Uri.parse(value));
+                }
+                break;
+                case "--ecn": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    ComponentName cn = ComponentName.unflattenFromString(value);
+                    if (cn == null)
+                        throw new IllegalArgumentException("Bad component name: " + value);
+                    intent.putExtra(key, cn);
+                }
+                break;
+                case "--eia": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    int[] list = new int[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Integer.decode(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                }
+                break;
+                case "--eial": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Integer> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Integer.decode(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                }
+                break;
+                case "--el": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Long.valueOf(value));
+                }
+                break;
+                case "--ela": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    long[] list = new long[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Long.valueOf(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--elal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Long> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Long.valueOf(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--ef": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Float.valueOf(value));
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--efa": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    float[] list = new float[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Float.valueOf(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--efal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Float> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Float.valueOf(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--esa": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    // Split on commas unless they are preceeded by an escape.
+                    // The escape character must be escaped for the string and
+                    // again for the regex, thus four escape characters become one.
+                    String[] strings = value.split("(?<!\\\\),");
+                    intent.putExtra(key, strings);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--esal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    // Split on commas unless they are preceeded by an escape.
+                    // The escape character must be escaped for the string and
+                    // again for the regex, thus four escape characters become one.
+                    String[] strings = value.split("(?<!\\\\),");
+                    ArrayList<String> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--ez": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired().toLowerCase();
+                    // Boolean.valueOf() results in false for anything that is not "true", which is
+                    // error-prone in shell commands
+                    boolean arg;
+                    if ("true".equals(value) || "t".equals(value)) {
+                        arg = true;
+                    } else if ("false".equals(value) || "f".equals(value)) {
+                        arg = false;
+                    } else {
+                        try {
+                            arg = Integer.decode(value) != 0;
+                        } catch (NumberFormatException ex) {
+                            throw new IllegalArgumentException("Invalid boolean value: " + value);
+                        }
+                    }
+
+                    intent.putExtra(key, arg);
+                }
+                break;
+                case "-n": {
+                    String str = cmd.getNextArgRequired();
+                    ComponentName cn = ComponentName.unflattenFromString(str);
+                    if (cn == null)
+                        throw new IllegalArgumentException("Bad component name: " + str);
+                    intent.setComponent(cn);
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                }
+                break;
+                case "-p": {
+                    String str = cmd.getNextArgRequired();
+                    intent.setPackage(str);
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                }
+                break;
+                case "-f":
+                    String str = cmd.getNextArgRequired();
+                    intent.setFlags(Integer.decode(str).intValue());
+                    break;
+                case "--grant-read-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    break;
+                case "--grant-write-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    break;
+                case "--grant-persistable-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+                    break;
+                case "--grant-prefix-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+                    break;
+                case "--exclude-stopped-packages":
+                    intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+                    break;
+                case "--include-stopped-packages":
+                    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+                    break;
+                case "--debug-log-resolution":
+                    intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+                    break;
+                case "--activity-brought-to-front":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+                    break;
+                case "--activity-clear-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                    break;
+                case "--activity-clear-when-task-reset":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+                    break;
+                case "--activity-exclude-from-recents":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    break;
+                case "--activity-launched-from-history":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+                    break;
+                case "--activity-multiple-task":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+                    break;
+                case "--activity-no-animation":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                    break;
+                case "--activity-no-history":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+                    break;
+                case "--activity-no-user-action":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+                    break;
+                case "--activity-previous-is-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+                    break;
+                case "--activity-reorder-to-front":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                    break;
+                case "--activity-reset-task-if-needed":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                    break;
+                case "--activity-single-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+                    break;
+                case "--activity-clear-task":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                    break;
+                case "--activity-task-on-home":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+                    break;
+                case "--receiver-registered-only":
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
+                case "--receiver-replace-pending":
+                    intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                    break;
+                case "--selector":
+                    intent.setDataAndType(data, type);
+                    intent = new Intent();
+                    break;
+                default:
+                    if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
+                        // Okay, caller handled this option.
+                    } else {
+                        throw new IllegalArgumentException("Unknown option: " + opt);
+                    }
+                    break;
+            }
+        }
+        intent.setDataAndType(data, type);
+
+        final boolean hasSelector = intent != baseIntent;
+        if (hasSelector) {
+            // A selector was specified; fix up.
+            baseIntent.setSelector(intent);
+            intent = baseIntent;
+        }
+
+        String arg = cmd.getNextArg();
+        baseIntent = null;
+        if (arg == null) {
+            if (hasSelector) {
+                // If a selector has been specified, and no arguments
+                // have been supplied for the main Intent, then we can
+                // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
+                // need to have a component name specified yet, the
+                // selector will take care of that.
+                baseIntent = new Intent(Intent.ACTION_MAIN);
+                baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            }
+        } else if (arg.indexOf(':') >= 0) {
+            // The argument is a URI.  Fully parse it, and use that result
+            // to fill in any data not specified so far.
+            baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
+                    | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
+        } else if (arg.indexOf('/') >= 0) {
+            // The argument is a component name.  Build an Intent to launch
+            // it.
+            baseIntent = new Intent(Intent.ACTION_MAIN);
+            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            baseIntent.setComponent(ComponentName.unflattenFromString(arg));
+        } else {
+            // Assume the argument is a package name.
+            baseIntent = new Intent(Intent.ACTION_MAIN);
+            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            baseIntent.setPackage(arg);
+        }
+        if (baseIntent != null) {
+            Bundle extras = intent.getExtras();
+            intent.replaceExtras((Bundle)null);
+            Bundle uriExtras = baseIntent.getExtras();
+            baseIntent.replaceExtras((Bundle)null);
+            if (intent.getAction() != null && baseIntent.getCategories() != null) {
+                HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
+                for (String c : cats) {
+                    baseIntent.removeCategory(c);
+                }
+            }
+            intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
+            if (extras == null) {
+                extras = uriExtras;
+            } else if (uriExtras != null) {
+                uriExtras.putAll(extras);
+                extras = uriExtras;
+            }
+            intent.replaceExtras(extras);
+            hasIntentInfo = true;
+        }
+
+        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
+        return intent;
+    }
+
+    /** @hide */
+    public static void printIntentArgsHelp(PrintWriter pw, String prefix) {
+        final String[] lines = new String[] {
+                "<INTENT> specifications include these flags and arguments:",
+                "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]",
+                "    [-c <CATEGORY> [-c <CATEGORY>] ...]",
+                "    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]",
+                "    [--esn <EXTRA_KEY> ...]",
+                "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]",
+                "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]",
+                "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]",
+                "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]",
+                "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]",
+                "    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]",
+                "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+                "        (mutiple extras passed as Integer[])",
+                "    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+                "        (mutiple extras passed as List<Integer>)",
+                "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+                "        (mutiple extras passed as Long[])",
+                "    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+                "        (mutiple extras passed as List<Long>)",
+                "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+                "        (mutiple extras passed as Float[])",
+                "    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+                "        (mutiple extras passed as List<Float>)",
+                "    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+                "        (mutiple extras passed as String[]; to embed a comma into a string,",
+                "         escape it using \"\\,\")",
+                "    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+                "        (mutiple extras passed as List<String>; to embed a comma into a string,",
+                "         escape it using \"\\,\")",
+                "    [--grant-read-uri-permission] [--grant-write-uri-permission]",
+                "    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]",
+                "    [--debug-log-resolution] [--exclude-stopped-packages]",
+                "    [--include-stopped-packages]",
+                "    [--activity-brought-to-front] [--activity-clear-top]",
+                "    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]",
+                "    [--activity-launched-from-history] [--activity-multiple-task]",
+                "    [--activity-no-animation] [--activity-no-history]",
+                "    [--activity-no-user-action] [--activity-previous-is-top]",
+                "    [--activity-reorder-to-front] [--activity-reset-task-if-needed]",
+                "    [--activity-single-top] [--activity-clear-task]",
+                "    [--activity-task-on-home]",
+                "    [--receiver-registered-only] [--receiver-replace-pending]",
+                "    [--selector]",
+                "    [<URI> | <PACKAGE> | <COMPONENT>]"
+        };
+        for (String line : lines) {
+            pw.print(prefix);
+            pw.println(line);
+        }
+    }
+
     /**
      * Retrieve the general action to be performed, such as
      * {@link #ACTION_VIEW}.  The action describes the general way the rest of
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 52c2f9b..1996e0f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -22,7 +22,9 @@
 import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
 import android.util.Printer;
 
@@ -469,6 +471,22 @@
     public static final int PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED = 1 << 5;
 
     /**
+     * When set, assume that all components under the given app are encryption
+     * aware, unless otherwise specified.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6;
+
+    /**
+     * Value for {@link #privateFlags}: set to {@code true} if the application
+     * is AutoPlay.
+     *
+     * {@hide}
+     */
+    public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -963,7 +981,8 @@
                 .getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName)
                 .getAbsolutePath();
 
-        if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0) {
+        if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0
+                && StorageManager.isFileBasedEncryptionEnabled()) {
             dataDir = deviceEncryptedDataDir;
         } else {
             dataDir = credentialEncryptedDataDir;
@@ -1030,6 +1049,18 @@
                 && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
+    /** @hide */
+    public boolean isEncryptionAware() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE) != 0;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isAutoPlayApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java
index a5a8e3f..084bc18 100644
--- a/core/java/android/content/pm/AppsQueryHelper.java
+++ b/core/java/android/content/pm/AppsQueryHelper.java
@@ -18,13 +18,11 @@
 
 import android.Manifest;
 import android.app.AppGlobals;
-import android.content.Context;
 import android.content.Intent;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArraySet;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethod;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -44,36 +42,37 @@
     public static int GET_NON_LAUNCHABLE_APPS = 1;
 
     /**
-     * Return apps with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission
+     * Return apps with {@link Manifest.permission#INTERACT_ACROSS_USERS} permission
      */
     public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
 
     /**
-     * Return all input methods that are marked as default.
-     * <p>When this flag is set, {@code user} specified in
-     * {@link #queryApps(int, boolean, UserHandle)} must be
-     * {@link UserHandle#myUserId user of the current process}.
+     * Return all input methods available for the current user.
      */
-    public static int GET_DEFAULT_IMES = 1 << 2;
+    public static int GET_IMES = 1 << 2;
 
-    private final Context mContext;
+    private final IPackageManager mPackageManager;
     private List<ApplicationInfo> mAllApps;
 
-    public AppsQueryHelper(Context context) {
-        mContext = context;
+    public AppsQueryHelper(IPackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
+
+    public AppsQueryHelper() {
+        this(AppGlobals.getPackageManager());
     }
 
     /**
      * Return a List of all packages that satisfy a specified criteria.
      * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
-     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}.
+     * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_IMES}.
      * @param systemAppsOnly if true, only system apps will be returned
      * @param user user, whose apps are queried
      */
     public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
         boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
         boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
-        boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0;
+        boolean imes = (flags & GET_IMES) > 0;
         if (mAllApps == null) {
             mAllApps = getAllApps(user.getIdentifier());
         }
@@ -113,7 +112,6 @@
                 }
             }
         }
-
         if (interactAcrossUsers) {
             final List<PackageInfo> packagesHoldingPermissions = getPackagesHoldingPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS, user.getIdentifier());
@@ -129,29 +127,18 @@
             }
         }
 
-        if (defaultImes) {
-            if (UserHandle.myUserId() != user.getIdentifier()) {
-                throw new IllegalArgumentException("Specified user handle " + user
-                        + " is not a user of the current process.");
-            }
-            List<InputMethodInfo> imis = getInputMethodList();
-            int imisSize = imis.size();
-            ArraySet<String> defaultImePackages = new ArraySet<>();
-            for (int i = 0; i < imisSize; i++) {
-                InputMethodInfo imi = imis.get(i);
-                if (imi.isDefault(mContext)) {
-                    defaultImePackages.add(imi.getPackageName());
-                }
-            }
-            final int allAppsSize = mAllApps.size();
-            for (int i = 0; i < allAppsSize; i++) {
-                final ApplicationInfo appInfo = mAllApps.get(i);
-                if (systemAppsOnly && !appInfo.isSystemApp()) {
+        if (imes) {
+            final List<ResolveInfo> resolveInfos = queryIntentServicesAsUser(
+                    new Intent(InputMethod.SERVICE_INTERFACE), user.getIdentifier());
+            final int resolveInfosSize = resolveInfos.size();
+
+            for (int i = 0; i < resolveInfosSize; i++) {
+                ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+                if (systemAppsOnly && !serviceInfo.applicationInfo.isSystemApp()) {
                     continue;
                 }
-                final String packageName = appInfo.packageName;
-                if (defaultImePackages.contains(packageName)) {
-                    result.add(packageName);
+                if (!result.contains(serviceInfo.packageName)) {
+                    result.add(serviceInfo.packageName);
                 }
             }
         }
@@ -163,9 +150,8 @@
     @SuppressWarnings("unchecked")
     protected List<ApplicationInfo> getAllApps(int userId) {
         try {
-            return AppGlobals.getPackageManager().getInstalledApplications(
-                    PackageManager.GET_UNINSTALLED_PACKAGES
-                            | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
+            return mPackageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
+                    | PackageManager.GET_DISABLED_COMPONENTS, userId).getList();
         } catch (RemoteException e) {
             throw new IllegalStateException("Package manager has died", e);
         }
@@ -173,17 +159,22 @@
 
     @VisibleForTesting
     protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int userId) {
-        return mContext.getPackageManager()
-                .queryIntentActivitiesAsUser(intent, PackageManager.GET_DISABLED_COMPONENTS
-                        | PackageManager.GET_UNINSTALLED_PACKAGES, userId);
+        try {
+            return mPackageManager.queryIntentActivities(intent, null,
+                    PackageManager.GET_DISABLED_COMPONENTS
+                            | PackageManager.GET_UNINSTALLED_PACKAGES,
+                    userId);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager has died", e);
+        }
     }
 
     @VisibleForTesting
-    @SuppressWarnings("unchecked")
-    protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+    protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
         try {
-            return AppGlobals.getPackageManager().getPackagesHoldingPermissions(new String[]{perm},
-                    0, userId).getList();
+            return mPackageManager.queryIntentServices(intent, null,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
         } catch (RemoteException e) {
             throw new IllegalStateException("Package manager has died", e);
         }
@@ -191,9 +182,13 @@
 
     @VisibleForTesting
     @SuppressWarnings("unchecked")
-    protected List<InputMethodInfo> getInputMethodList() {
-        InputMethodManager imm = (InputMethodManager)
-                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
-        return imm.getInputMethodList();
+    protected List<PackageInfo> getPackagesHoldingPermission(String perm, int userId) {
+        try {
+            return mPackageManager.getPackagesHoldingPermissions(new String[]{perm}, 0,
+                    userId).getList();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager has died", e);
+        }
     }
+
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fec2c44..6fe1efd 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -420,10 +420,14 @@
     boolean hasSystemUidErrors();
 
     /**
-     * Ask the package manager to perform boot-time dex-opt of all
-     * existing packages.
+     * Ask the package manager to fstrim the disk if needed.
      */
-    void performBootDexOpt();
+    void performFstrimIfNeeded();
+
+    /**
+     * Notify the package manager that a package is going to be used.
+     */
+    void notifyPackageUse(String packageName);
 
     /**
      * Ask the package manager to perform dex-opt (if needed) on the given
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 1fbef7a..e7dc764 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import com.android.internal.util.HexDump;
+
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -118,7 +120,7 @@
         final int N = mDigest.length;
         for (int i = 0; i < N; i++) {
             final byte b = mDigest[i];
-            IntegralToString.appendByteAsHex(sb, b, false);
+            HexDump.appendByteAsHex(sb, b, false);
             sb.append(',');
         }
         sb.append('}');
@@ -142,4 +144,4 @@
         }
     };
 
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 566de4e..42fef3b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -240,16 +240,15 @@
     public static final int GET_ENCRYPTION_UNAWARE_COMPONENTS = 0x00040000;
 
     /**
-     * {@link PackageInfo} flag: return components as if the given user is
-     * running with amnesia. This typically limits the component to only those
-     * marked as {@link ComponentInfo#encryptionAware}, unless
+     * {@link PackageInfo} flag: return components that are marked as
+     * {@link ComponentInfo#encryptionAware}, unless
      * {@link #GET_ENCRYPTION_UNAWARE_COMPONENTS} is also specified.
      * <p>
      * This flag is for internal use only.
      *
      * @hide
      */
-    public static final int FLAG_USER_RUNNING_WITH_AMNESIA = 0x00080000;
+    public static final int MATCH_ENCRYPTION_AWARE_ONLY = 0x00080000;
 
     /**
      * Flag for {@link addCrossProfileIntentFilter}: if this flag is set:
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index bf70d6c..905ac5e 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,7 +16,7 @@
 
 package android.content.pm;
 
-import android.annotation.NonNull;
+import java.util.List;
 
 /**
  * Package manager local system service interface.
@@ -115,4 +115,11 @@
      */
     public abstract void grantDefaultPermissionsToDefaultSimCallManager(String packageName,
             int userId);
+
+    /**
+     * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has
+     * currently installed it. The apps are not preloaded.
+     * @param packageList List of package names to keep cached.
+     */
+    public abstract void setKeepUninstalledPackages(List<String> packageList);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f176d89..838da37 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2636,6 +2636,10 @@
                 && (flags & PARSE_IS_SYSTEM) != 0) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED;
         }
+        if (sa.getBoolean(R.styleable.AndroidManifestApplication_encryptionAware, false)
+                && (flags & PARSE_IS_SYSTEM) != 0) {
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE;
+        }
 
         String str;
         str = sa.getNonConfigurationString(
@@ -3236,7 +3240,8 @@
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
 
             a.info.encryptionAware = sa.getBoolean(
-                    R.styleable.AndroidManifestActivity_encryptionAware, false);
+                    R.styleable.AndroidManifestActivity_encryptionAware,
+                    owner.applicationInfo.isEncryptionAware());
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
@@ -3253,7 +3258,8 @@
             }
 
             a.info.encryptionAware = sa.getBoolean(
-                    R.styleable.AndroidManifestActivity_encryptionAware, false);
+                    R.styleable.AndroidManifestActivity_encryptionAware,
+                    owner.applicationInfo.isEncryptionAware());
         }
 
         sa.recycle();
@@ -3655,7 +3661,8 @@
         }
 
         p.info.encryptionAware = sa.getBoolean(
-                R.styleable.AndroidManifestProvider_encryptionAware, false);
+                R.styleable.AndroidManifestProvider_encryptionAware,
+                owner.applicationInfo.isEncryptionAware());
 
         sa.recycle();
 
@@ -3938,7 +3945,8 @@
         }
 
         s.info.encryptionAware = sa.getBoolean(
-                R.styleable.AndroidManifestService_encryptionAware, false);
+                R.styleable.AndroidManifestService_encryptionAware,
+                owner.applicationInfo.isEncryptionAware());
 
         sa.recycle();
 
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 477b62c..1a19a58 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1505,21 +1505,27 @@
      *
      * @hide
      */
-    public static String localeToResourceQualifier(Locale loc) {
+    public static String localesToResourceQualifier(LocaleList locs) {
         StringBuilder sb = new StringBuilder();
-        boolean l = (loc.getLanguage().length() != 0);
-        boolean c = (loc.getCountry().length() != 0);
-        boolean s = (loc.getScript().length() != 0);
-        boolean v = (loc.getVariant().length() != 0);
-        // TODO: take script and extensions into account
-        if (l) {
-            sb.append(loc.getLanguage());
-            if (c) {
-                sb.append("-r").append(loc.getCountry());
-                if (s) {
-                    sb.append("-s").append(loc.getScript());
-                    if (v) {
-                        sb.append("-v").append(loc.getVariant());
+        for (int i = 0; i < locs.size(); i++) {
+            Locale loc = locs.get(i);
+            boolean l = (loc.getLanguage().length() != 0);
+            boolean c = (loc.getCountry().length() != 0);
+            boolean s = (loc.getScript().length() != 0);
+            boolean v = (loc.getVariant().length() != 0);
+            // TODO: take script and extensions into account
+            if (l) {
+                if (sb.length() != 0) {
+                    sb.append(",");
+                }
+                sb.append(loc.getLanguage());
+                if (c) {
+                    sb.append("-r").append(loc.getCountry());
+                    if (s) {
+                        sb.append("-s").append(loc.getScript());
+                        if (v) {
+                            sb.append("-v").append(loc.getVariant());
+                        }
                     }
                 }
             }
@@ -1544,9 +1550,11 @@
             }
         }
 
-        // TODO: send the whole locale list
-        if (config.locale != null && !config.locale.getLanguage().isEmpty()) {
-            parts.add(localeToResourceQualifier(config.locale));
+        if (!config.mLocaleList.isEmpty()) {
+            final String resourceQualifier = localesToResourceQualifier(config.mLocaleList);
+            if (!resourceQualifier.isEmpty()) {
+                parts.add(resourceQualifier);
+            }
         }
 
         switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0606e35..7b3dde4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -48,7 +48,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Trace;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.LocaleList;
@@ -68,7 +67,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.ref.WeakReference;
 import java.util.Locale;
 
 /**
@@ -120,9 +118,6 @@
     private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
             sPreloadedColorStateLists = new LongSparseArray<>();
 
-    private static final String CACHE_NOT_THEMED = "";
-    private static final String CACHE_NULL_THEME = "null_theme";
-
     // Pool of TypedArrays targeted to this Resources object.
     final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
 
@@ -130,10 +125,11 @@
     static Resources mSystem = null;
 
     private static boolean sPreloaded;
-    private static int sPreloadedDensity;
+
+    /** Lock object used to protect access to caches and configuration. */
+    private final Object mAccessLock = new Object();
 
     // These are protected by mAccessLock.
-    private final Object mAccessLock = new Object();
     private final Configuration mTmpConfig = new Configuration();
     private final DrawableCache mDrawableCache = new DrawableCache(this);
     private final DrawableCache mColorDrawableCache = new DrawableCache(this);
@@ -147,7 +143,12 @@
     /** Used to inflate drawable objects from XML. */
     private DrawableInflater mDrawableInflater;
 
+    /** Lock object used to protect access to {@link #mTmpValue}. */
+    private final Object mTmpValueLock = new Object();
+
+    /** Single-item pool used to minimize TypedValue allocations. */
     private TypedValue mTmpValue = new TypedValue();
+
     private boolean mPreloading;
 
     private int mLastCachedXmlBlockIndex = -1;
@@ -249,6 +250,10 @@
         public NotFoundException(String name) {
             super(name);
         }
+
+        public NotFoundException(String name, Exception cause) {
+            super(name, cause);
+        }
     }
 
     /**
@@ -621,18 +626,15 @@
      * @see #getDimensionPixelSize
      */
     public float getDimension(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
                 return TypedValue.complexToDimension(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -656,19 +658,15 @@
      * @see #getDimensionPixelSize
      */
     public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
-                return TypedValue.complexToDimensionPixelOffset(
-                        value.data, mMetrics);
+                return TypedValue.complexToDimensionPixelOffset(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -693,19 +691,15 @@
      * @see #getDimensionPixelOffset
      */
     public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
-                return TypedValue.complexToDimensionPixelSize(
-                        value.data, mMetrics);
+                return TypedValue.complexToDimensionPixelSize(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -727,18 +721,15 @@
      * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
      */
     public float getFraction(@FractionRes int id, int base, int pbase) {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_FRACTION) {
                 return TypedValue.complexToFraction(value.data, base, pbase);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
     
@@ -801,24 +792,14 @@
      *         not exist.
      */
     @Nullable
-    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
+    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
+            throws NotFoundException {
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
+            return loadDrawable(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-        final Drawable res = loadDrawable(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
-        }
-        return res;
     }
 
     /**
@@ -849,7 +830,8 @@
      */
     @Deprecated
     @Nullable
-    public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException {
+    public Drawable getDrawableForDensity(@DrawableRes int id, int density)
+            throws NotFoundException {
         return getDrawableForDensity(id, density, null);
     }
 
@@ -869,14 +851,8 @@
      */
     @Nullable
     public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             getValueForDensity(id, density, value, true);
 
             /*
@@ -893,15 +869,11 @@
                     value.density = (value.density * mMetrics.densityDpi) / density;
                 }
             }
-        }
 
-        final Drawable res = loadDrawable(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
+            return loadDrawable(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-        return res;
     }
 
     /**
@@ -963,33 +935,21 @@
      */
     @ColorInt
     public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
                     && value.type <= TypedValue.TYPE_LAST_INT) {
-                mTmpValue = value;
                 return value.data;
             } else if (value.type != TypedValue.TYPE_STRING) {
-                throw new NotFoundException(
-                        "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                                + Integer.toHexString(value.type) + " is not valid");
+                throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                        + " type #0x" + Integer.toHexString(value.type) + " is not valid");
             }
-            mTmpValue = null;
-        }
 
-        final ColorStateList csl = loadColorStateList(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
+            final ColorStateList csl = loadColorStateList(value, id, theme);
+            return csl.getDefaultColor();
+        } finally {
+            releaseTempTypedValue(value);
         }
-
-        return csl.getDefaultColor();
     }
 
     /**
@@ -1043,25 +1003,12 @@
     @Nullable
     public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
             throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
+            return loadColorStateList(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-
-        final ColorStateList res = loadColorStateList(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
-        }
-
-        return res;
     }
 
     /**
@@ -1078,19 +1025,16 @@
      * @return Returns the boolean value contained in the resource.
      */
     public boolean getBoolean(@BoolRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
-                && value.type <= TypedValue.TYPE_LAST_INT) {
+                    && value.type <= TypedValue.TYPE_LAST_INT) {
                 return value.data != 0;
             }
-            throw new NotFoundException(
-                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1106,19 +1050,16 @@
      * @return Returns the integer value contained in the resource.
      */
     public int getInteger(@IntegerRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
-                && value.type <= TypedValue.TYPE_LAST_INT) {
+                    && value.type <= TypedValue.TYPE_LAST_INT) {
                 return value.data;
             }
-            throw new NotFoundException(
-                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1136,17 +1077,15 @@
      * @hide Pending API council approval.
      */
     public float getFloat(int id) {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_FLOAT) {
                 return value.getFloat();
             }
-            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1238,22 +1177,60 @@
      * 
      */
     public InputStream openRawResource(@RawRes int id) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
+        final TypedValue value = obtainTempTypedValue();
+        try {
+            return openRawResource(id, value);
+        } finally {
+            releaseTempTypedValue(value);
+        }
+    }
+
+    /**
+     * Returns a TypedValue populated with data for the specified resource ID
+     * that's suitable for temporary use. The obtained TypedValue should be
+     * released using {@link #releaseTempTypedValue(TypedValue)}.
+     *
+     * @param id the resource ID for which data should be obtained
+     * @return a populated typed value suitable for temporary use
+     */
+    private TypedValue obtainTempTypedValue(@AnyRes int id) {
+        final TypedValue value = obtainTempTypedValue();
+        getValue(id, value, true);
+        return value;
+    }
+
+    /**
+     * Returns a TypedValue suitable for temporary use. The obtained TypedValue
+     * should be released using {@link #releaseTempTypedValue(TypedValue)}.
+     *
+     * @return a typed value suitable for temporary use
+     */
+    private TypedValue obtainTempTypedValue() {
+        TypedValue tmpValue = null;
+        synchronized (mTmpValueLock) {
+            if (mTmpValue != null) {
+                tmpValue = mTmpValue;
                 mTmpValue = null;
             }
         }
-        InputStream res = openRawResource(id, value);
-        synchronized (mAccessLock) {
+        if (tmpValue == null) {
+            return new TypedValue();
+        }
+        return tmpValue;
+    }
+
+    /**
+     * Returns a TypedValue to the pool. After calling this method, the
+     * specified TypedValue should no longer be accessed.
+     *
+     * @param value the typed value to return to the pool
+     */
+    private void releaseTempTypedValue(TypedValue value) {
+        synchronized (mTmpValueLock) {
             if (mTmpValue == null) {
                 mTmpValue = value;
             }
         }
-        return res;
     }
 
     /**
@@ -1307,32 +1284,14 @@
      */
     public AssetFileDescriptor openRawResourceFd(@RawRes int id)
             throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
-        }
+        final TypedValue value = obtainTempTypedValue(id);
         try {
-            return mAssets.openNonAssetFd(
-                value.assetCookie, value.string.toString());
+            return mAssets.openNonAssetFd(value.assetCookie, value.string.toString());
         } catch (Exception e) {
-            NotFoundException rnf = new NotFoundException(
-                "File " + value.string.toString()
-                + " from drawable resource ID #0x"
-                + Integer.toHexString(id));
-            rnf.initCause(e);
-            throw rnf;
+            throw new NotFoundException("File " + value.string.toString() + " from drawable "
+                    + "resource ID #0x" + Integer.toHexString(id), e);
         } finally {
-            synchronized (mAccessLock) {
-                if (mTmpValue == null) {
-                    mTmpValue = value;
-                }
-            }
+            releaseTempTypedValue(value);
         }
     }
 
@@ -2015,8 +1974,8 @@
                     Build.VERSION.RESOURCES_SDK_INT);
 
             if (DEBUG_CONFIG) {
-                Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration
-                        + " final compat is " + mCompatibilityInfo);
+                Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+                        + mConfiguration + " final compat is " + mCompatibilityInfo);
             }
 
             mDrawableCache.onConfigurationChange(configChanges);
@@ -2402,8 +2361,7 @@
             }
             sPreloaded = true;
             mPreloading = true;
-            sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
-            mConfiguration.densityDpi = sPreloadedDensity;
+            mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
             updateConfiguration(null, null);
         }
     }
@@ -2740,19 +2698,16 @@
 
     /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
             throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_STRING) {
                 return loadXmlResourceParser(value.string.toString(), id,
                         value.assetCookie, type);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
     
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 1fc69c0..73bb426 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3182,8 +3182,8 @@
         }
 
         /**
-         * Sets GPS processing method. It will store up to 32 characters
-         * in JPEG EXIF header.
+         * Sets GPS processing method. The method will be stored in a UTF-8 string up to 31 bytes
+         * long, in the JPEG EXIF header.
          *
          * @param processing_method The processing method to get this location.
          */
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 35a1d96..f61892e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -472,13 +472,13 @@
      * <li>The maximum available resolution for RAW_SENSOR streams
      *   will match either the value in
      *   {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize} or
-     *   {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</li>
+     *   {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</li>
      * <li>All DNG-related optional metadata entries are provided
      *   by the camera device.</li>
      * </ul>
      *
-     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3f566eb..67835a0 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -556,6 +556,10 @@
          * Set a capture request field to a value. The field definitions can be
          * found in {@link CaptureRequest}.
          *
+         * <p>Setting a field to {@code null} will remove that field from the capture request.
+         * Unless the field is optional, removing it will likely produce an error from the camera
+         * device when the request is submitted.</p>
+         *
          * @param key The metadata field to write.
          * @param value The value to set the field to, which must be of a matching
          * type to the key.
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index bb08be2..2137c3a 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -87,12 +87,21 @@
     public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
 
     /**
-     * Default tag value for {@link BackupManager} traffic.
+     * Default tag value for {@link BackupManager} backup traffic; that is,
+     * traffic from the device to the storage backend.
      *
      * @hide
      */
     public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
 
+    /**
+     * Default tag value for {@link BackupManager} restore traffic; that is,
+     * app data retrieved from the storage backend at install time.
+     *
+     * @hide
+     */
+    public static final int TAG_SYSTEM_RESTORE = 0xFFFFFF04;
+
     private static INetworkStatsService sStatsService;
 
     private synchronized static INetworkStatsService getStatsService() {
@@ -142,6 +151,16 @@
     }
 
     /**
+     * System API for restore-related support components to tag network traffic
+     * appropriately.
+     * @hide
+     */
+    @SystemApi
+    public static void setThreadStatsTagRestore() {
+        setThreadStatsTag(TAG_SYSTEM_RESTORE);
+    }
+
+    /**
      * Get the active tag used when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * {@link #tagSocket(Socket)}.
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 5b60c0d..2715af0 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -16,6 +16,8 @@
 
 package android.net.http;
 
+import com.android.internal.util.HexDump;
+
 import android.content.Context;
 import android.os.Bundle;
 import android.text.format.DateFormat;
@@ -285,7 +287,7 @@
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < bytes.length; i++) {
             byte b = bytes[i];
-            IntegralToString.appendByteAsHex(sb, b, true);
+            HexDump.appendByteAsHex(sb, b, true);
             if (i+1 != bytes.length) {
                 sb.append(':');
             }
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index 25ef8b5..6729347 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -20,6 +20,9 @@
 
 import com.android.org.conscrypt.TrustManagerImpl;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.List;
@@ -36,7 +39,11 @@
  */
 public class X509TrustManagerExtensions {
 
-    final TrustManagerImpl mDelegate;
+    private final TrustManagerImpl mDelegate;
+    // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
+    private final X509TrustManager mTrustManager;
+    private final Method mCheckServerTrusted;
+    private final Method mIsUserAddedCertificate;
 
     /**
      * Constructs a new X509TrustManagerExtensions wrapper.
@@ -47,10 +54,31 @@
     public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
         if (tm instanceof TrustManagerImpl) {
             mDelegate = (TrustManagerImpl) tm;
-        } else {
-            mDelegate = null;
-            throw new IllegalArgumentException("tm is an instance of " + tm.getClass().getName() +
-                    " which is not a supported type of X509TrustManager");
+            mTrustManager = null;
+            mCheckServerTrusted = null;
+            mIsUserAddedCertificate = null;
+            return;
+        }
+        // Use duck typing if possible.
+        mDelegate = null;
+        mTrustManager = tm;
+        // Check that the hostname aware checkServerTrusted is present.
+        try {
+            mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
+                    X509Certificate[].class,
+                    String.class,
+                    String.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException("Required method"
+                    + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+        }
+        // Check that isUserAddedCertificate is present.
+        try {
+            mIsUserAddedCertificate = tm.getClass().getMethod("isUserAddedCertificate",
+                    X509Certificate.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                    "Required method isUserAddedCertificate(X509Certificate) missing");
         }
     }
 
@@ -66,7 +94,24 @@
      */
     public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
                                                     String host) throws CertificateException {
-        return mDelegate.checkServerTrusted(chain, authType, host);
+        if (mDelegate != null) {
+            return mDelegate.checkServerTrusted(chain, authType, host);
+        } else {
+            try {
+                return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
+                        authType, host);
+            } catch (IllegalAccessException e) {
+                throw new CertificateException("Failed to call checkServerTrusted", e);
+            } catch (InvocationTargetException e) {
+                if (e.getCause() instanceof CertificateException) {
+                    throw (CertificateException) e.getCause();
+                }
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                }
+                throw new CertificateException("checkServerTrusted failed", e.getCause());
+            }
+        }
     }
 
     /**
@@ -80,7 +125,21 @@
      * otherwise.
      */
     public boolean isUserAddedCertificate(X509Certificate cert) {
-        return mDelegate.isUserAddedCertificate(cert);
+        if (mDelegate != null) {
+            return mDelegate.isUserAddedCertificate(cert);
+        } else {
+            try {
+                return (Boolean) mIsUserAddedCertificate.invoke(mTrustManager, cert);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Failed to call isUserAddedCertificate", e);
+            } catch (InvocationTargetException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                } else {
+                    throw new RuntimeException("isUserAddedCertificate failed", e.getCause());
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index f55883a..7a1a6a2 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -35,4 +35,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
     void exitIdle(String reason);
+    void downloadServiceActive(IBinder token);
+    void downloadServiceInactive();
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 5852f5f..f2aea08 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -624,6 +624,32 @@
     }
 
     /**
+     * {@hide}
+     * This will be the new name for writeFileDescriptor, for consistency.
+     **/
+    public final void writeRawFileDescriptor(FileDescriptor val) {
+        nativeWriteFileDescriptor(mNativePtr, val);
+    }
+
+    /**
+     * {@hide}
+     * Write an array of FileDescriptor objects into the Parcel.
+     *
+     * @param value The array of objects to be written.
+     */
+    public final void writeRawFileDescriptorArray(FileDescriptor[] value) {
+        if (value != null) {
+            int N = value.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeRawFileDescriptor(value[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
      * Write a byte value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
@@ -1700,6 +1726,41 @@
         return nativeReadFileDescriptor(mNativePtr);
     }
 
+    /**
+     * {@hide}
+     * Read and return a new array of FileDescriptors from the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    public final FileDescriptor[] createRawFileDescriptorArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        FileDescriptor[] f = new FileDescriptor[N];
+        for (int i = 0; i < N; i++) {
+            f[i] = readRawFileDescriptor();
+        }
+        return f;
+    }
+
+    /**
+     * {@hide}
+     * Read an array of FileDescriptors from a parcel.
+     * The passed array must be exactly the length of the array in the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readRawFileDescriptor();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+
     /*package*/ static native FileDescriptor openFileDescriptor(String file,
             int mode) throws FileNotFoundException;
     /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 4ac361d0..54bfca3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -632,9 +632,6 @@
             if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
                 argsForZygote.add("--enable-checkjni");
             }
-            if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) {
-                argsForZygote.add("--enable-jit");
-            }
             if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
                 argsForZygote.add("--generate-debug-info");
             }
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 73c2c80..6f12b62 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,8 +19,11 @@
 import android.util.Slog;
 import com.android.internal.util.FastPrintWriter;
 
+import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 import java.io.PrintWriter;
 
 /**
@@ -43,20 +46,37 @@
 
     private FastPrintWriter mOutPrintWriter;
     private FastPrintWriter mErrPrintWriter;
+    private InputStream mInputStream;
 
-    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, ResultReceiver resultReceiver) {
+    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, int firstArgPos) {
         mTarget = target;
         mIn = in;
         mOut = out;
         mErr = err;
         mArgs = args;
-        mResultReceiver = resultReceiver;
-        mCmd = args != null && args.length > 0 ? args[0] : null;
-        mArgPos = 1;
+        mResultReceiver = null;
+        mCmd = null;
+        mArgPos = firstArgPos;
         mCurArgData = null;
         mOutPrintWriter = null;
         mErrPrintWriter = null;
+    }
+
+    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) {
+        String cmd;
+        int start;
+        if (args != null && args.length > 0) {
+            cmd = args[0];
+            start = 1;
+        } else {
+            cmd = null;
+            start = 0;
+        }
+        init(target, in, out, err, args, start);
+        mCmd = cmd;
+        mResultReceiver = resultReceiver;
 
         if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
         int res = -1;
@@ -111,6 +131,13 @@
         return mErrPrintWriter;
     }
 
+    public InputStream getInputStream() {
+        if (mInputStream == null) {
+            mInputStream = new BufferedInputStream(new FileInputStream(mIn));
+        }
+        return mInputStream;
+    }
+
     /**
      * 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.
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 95da438..f946ca7 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -256,6 +256,23 @@
         }
     }
 
+    /** @hide */
+    public static int parseUserArg(String arg) {
+        int userId;
+        if ("all".equals(arg)) {
+            userId = UserHandle.USER_ALL;
+        } else if ("current".equals(arg) || "cur".equals(arg)) {
+            userId = UserHandle.USER_CURRENT;
+        } else {
+            try {
+                userId = Integer.parseInt(arg);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Bad user number: " + arg);
+            }
+        }
+        return userId;
+    }
+
     /**
      * Returns the user id of the current process
      * @return user id of the current process
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 9178ec6..26c7a1e 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,9 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * @hide Only for use within the system server.
  */
@@ -31,32 +34,18 @@
     }
 
     /**
-     * Lock that must be held when calling certain methods in this class.
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+     * to set per-user as well as global user restrictions.
      *
-     * This is used to avoid dead lock between
-     * {@link com.android.server.pm.UserManagerService} and
-     * {@link com.android.server.devicepolicy.DevicePolicyManagerService}.  This lock should not
-     * be newly taken while holding the DPMS lock, which would cause a dead lock.  Take this
-     * lock first before taking the DPMS lock to avoid that.
+     * @param userId target user id for the local restrictions.
+     * @param localRestrictions per-user restrictions.
+     *     Caller must not change it once passed to this method.
+     * @param globalRestrictions global restrictions set by DO.  Must be null when PO changed user
+     *     restrictions, in which case global restrictions won't change.
+     *     Caller must not change it once passed to this method.
      */
-    public abstract Object getUserRestrictionsLock();
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsLR(int userId);
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
-
+    public abstract void setDevicePolicyUserRestrictions(int userId,
+            @NonNull Bundle localRestrictions, @Nullable Bundle globalRestrictions);
     /**
      * Returns the "base" user restrictions.
      *
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 5d45ade..c6510f0 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -21,8 +21,12 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.RemoteException;
 
+import java.io.FileDescriptor;
+
 /**
  * WARNING! Update IMountService.h and IMountService.cpp if you change this
  * file. In particular, the ordering of the methods below must match the
@@ -1198,14 +1202,14 @@
             }
 
             @Override
-            public void createNewUserDir(int userHandle, String path) throws RemoteException {
+            public void createUserKey(int userId, int serialNumber) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeInt(userHandle);
-                    _data.writeString(path);
-                    mRemote.transact(Stub.TRANSACTION_createNewUserDir, _data, _reply, 0);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    mRemote.transact(Stub.TRANSACTION_createUserKey, _data, _reply, 0);
                     _reply.readException();
                 } finally {
                     _reply.recycle();
@@ -1214,13 +1218,13 @@
             }
 
             @Override
-            public void deleteUserKey(int userHandle) throws RemoteException {
+            public void destroyUserKey(int userId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeInt(userHandle);
-                    mRemote.transact(Stub.TRANSACTION_deleteUserKey, _data, _reply, 0);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_destroyUserKey, _data, _reply, 0);
                     _reply.readException();
                 } finally {
                     _reply.recycle();
@@ -1229,13 +1233,46 @@
             }
 
             @Override
-            public boolean isPerUserEncryptionEnabled() throws RemoteException {
+            public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    _data.writeByteArray(token);
+                    mRemote.transact(Stub.TRANSACTION_unlockUserKey, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void lockUserKey(int userId) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_lockUserKey, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public boolean isUserKeyUnlocked(int userId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 boolean _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    mRemote.transact(Stub.TRANSACTION_isPerUserEncryptionEnabled, _data, _reply, 0);
+                    _data.writeInt(userId);
+                    mRemote.transact(Stub.TRANSACTION_isUserKeyUnlocked, _data, _reply, 0);
                     _reply.readException();
                     _result = 0 != _reply.readInt();
                 } finally {
@@ -1244,6 +1281,43 @@
                 }
                 return _result;
             }
+
+            @Override
+            public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
+                    throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volumeUuid);
+                    _data.writeInt(userId);
+                    _data.writeInt(serialNumber);
+                    mRemote.transact(Stub.TRANSACTION_prepareUserStorage, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                ParcelFileDescriptor _result = null;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(name);
+                    mRemote.transact(Stub.TRANSACTION_mountAppFuse, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.<ParcelFileDescriptor>readParcelable(
+                            ClassLoader.getSystemClassLoader());
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -1359,13 +1433,18 @@
         static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59;
         static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60;
 
-        static final int TRANSACTION_createNewUserDir = IBinder.FIRST_CALL_TRANSACTION + 62;
+        static final int TRANSACTION_createUserKey = IBinder.FIRST_CALL_TRANSACTION + 61;
+        static final int TRANSACTION_destroyUserKey = IBinder.FIRST_CALL_TRANSACTION + 62;
 
-        static final int TRANSACTION_deleteUserKey = IBinder.FIRST_CALL_TRANSACTION + 63;
+        static final int TRANSACTION_unlockUserKey = IBinder.FIRST_CALL_TRANSACTION + 63;
+        static final int TRANSACTION_lockUserKey = IBinder.FIRST_CALL_TRANSACTION + 64;
+        static final int TRANSACTION_isUserKeyUnlocked = IBinder.FIRST_CALL_TRANSACTION + 65;
 
-        static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 64;
+        static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66;
 
-        static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 65;
+        static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
+
+        static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69;
 
         /**
          * Cast an IBinder object into an IMountService interface, generating a
@@ -1929,26 +2008,60 @@
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_createNewUserDir: {
+                case TRANSACTION_createUserKey: {
                     data.enforceInterface(DESCRIPTOR);
-                    int userHandle = data.readInt();
-                    String path = data.readString();
-                    createNewUserDir(userHandle, path);
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    createUserKey(userId, serialNumber);
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_deleteUserKey: {
+                case TRANSACTION_destroyUserKey: {
                     data.enforceInterface(DESCRIPTOR);
-                    int userHandle = data.readInt();
-                    deleteUserKey(userHandle);
+                    int userId = data.readInt();
+                    destroyUserKey(userId);
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_isPerUserEncryptionEnabled: {
+                case TRANSACTION_unlockUserKey: {
                     data.enforceInterface(DESCRIPTOR);
-                    boolean result = isPerUserEncryptionEnabled();
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    byte[] token = data.createByteArray();
+                    unlockUserKey(userId, serialNumber, token);
                     reply.writeNoException();
-                    reply.writeInt((result ? 1 : 0));
+                    return true;
+                }
+                case TRANSACTION_lockUserKey: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int userId = data.readInt();
+                    lockUserKey(userId);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_isUserKeyUnlocked: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int userId = data.readInt();
+                    boolean result = isUserKeyUnlocked(userId);
+                    reply.writeNoException();
+                    reply.writeInt(result ? 1 : 0);
+                    return true;
+                }
+                case TRANSACTION_prepareUserStorage: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volumeUuid = data.readString();
+                    int userId = data.readInt();
+                    int serialNumber = data.readInt();
+                    prepareUserStorage(volumeUuid, userId, serialNumber);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_mountAppFuse: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String name = data.readString();
+                    ParcelFileDescriptor fd = mountAppFuse(name);
+                    reply.writeNoException();
+                    reply.writeParcelable(fd, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     return true;
                 }
             }
@@ -2263,24 +2376,15 @@
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
             throws RemoteException;
 
-    /**
-     * Creates the user data directory, possibly encrypted
-     * @param userHandle Handle of the user whose directory we are creating
-     * @param path Path at which to create the directory.
-     */
-    public void createNewUserDir(int userHandle, String path)
-        throws RemoteException;
+    public void createUserKey(int userId, int serialNumber) throws RemoteException;
+    public void destroyUserKey(int userId) throws RemoteException;
 
-    /**
-     * Securely delete the user's encryption key
-     * @param userHandle Handle of the user whose key we are deleting
-     */
-    public void deleteUserKey(int userHandle)
-        throws RemoteException;
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException;
+    public void lockUserKey(int userId) throws RemoteException;
+    public boolean isUserKeyUnlocked(int userId) throws RemoteException;
 
-    /**
-     * Returns whether the current encryption type is per user.
-     */
-    public boolean isPerUserEncryptionEnabled()
-        throws RemoteException;
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
+            throws RemoteException;
+
+    public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2ca0b20..db12564 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,8 +30,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -77,6 +79,8 @@
     public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
     /** {@hide} */
     public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
+    /** {@hide} */
+    public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -85,6 +89,8 @@
 
     /** {@hide} */
     public static final int DEBUG_FORCE_ADOPTABLE = 1 << 0;
+    /** {@hide} */
+    public static final int DEBUG_EMULATE_FBE = 1 << 1;
 
     /** {@hide} */
     public static final int FLAG_FOR_WRITE = 1 << 0;
@@ -287,7 +293,7 @@
     /**
      * Constructs a StorageManager object through which an application can
      * can communicate with the systems mount service.
-     * 
+     *
      * @param tgtLooper The {@link android.os.Looper} which events will be received on.
      *
      * <p>Applications can get instance of this class by calling
@@ -403,7 +409,7 @@
      * file matches a package ID that is owned by the calling program's UID.
      * That is, shared UID applications can attempt to mount any other
      * application's OBB that shares its UID.
-     * 
+     *
      * @param rawPath the path to the OBB file
      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
      *            encryption was used on the OBB.
@@ -441,7 +447,7 @@
      * That is, shared UID applications can obtain access to any other
      * application's OBB that shares its UID.
      * <p>
-     * 
+     *
      * @param rawPath path to the OBB file
      * @param force whether to kill any programs using this in order to unmount
      *            it
@@ -465,7 +471,7 @@
 
     /**
      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
-     * 
+     *
      * @param rawPath path to OBB image
      * @return true if OBB is mounted; false if not mounted or on error
      */
@@ -485,7 +491,7 @@
      * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
      * give you the path to where you can obtain access to the internals of the
      * OBB.
-     * 
+     *
      * @param rawPath path to OBB image
      * @return absolute path to mounted OBB image data or <code>null</code> if
      *         not mounted or exception encountered trying to read status
@@ -960,33 +966,66 @@
     }
 
     /** {@hide} */
-    public void createNewUserDir(int userHandle, File path) {
+    public void createUserKey(int userId, int serialNumber) {
         try {
-            mMountService.createNewUserDir(userHandle, path.getAbsolutePath());
+            mMountService.createUserKey(userId, serialNumber);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /** {@hide} */
-    public void deleteUserKey(int userHandle) {
+    public void destroyUserKey(int userId) {
         try {
-            mMountService.deleteUserKey(userHandle);
+            mMountService.destroyUserKey(userId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /** {@hide} */
-    public boolean isPerUserEncryptionEnabled() {
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) {
         try {
-            return mMountService.isPerUserEncryptionEnabled();
+            mMountService.unlockUserKey(userId, serialNumber, token);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /** {@hide} */
+    public void lockUserKey(int userId) {
+        try {
+            mMountService.lockUserKey(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) {
+        try {
+            mMountService.prepareUserStorage(volumeUuid, userId, serialNumber);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public boolean isUserKeyUnlocked(int userId) {
+        try {
+            return mMountService.isUserKeyUnlocked(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public static boolean isFileBasedEncryptionEnabled() {
+        return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
+                || SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
+    }
+
+    /** {@hide} */
     public static File maybeTranslateEmulatedPathToInternal(File path) {
         final IMountService mountService = IMountService.Stub.asInterface(
                 ServiceManager.getService("mount"));
@@ -1007,6 +1046,15 @@
         return path;
     }
 
+    /** {@hide} */
+    public ParcelFileDescriptor mountAppFuse(String name) {
+        try {
+            return mMountService.mountAppFuse(name);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     /// Consts to match the password types in cryptfs.h
     /** @hide */
     public static final int CRYPT_TYPE_PASSWORD = 0;
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index c368e5a..5997515 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -435,7 +435,7 @@
             return null;
         }
 
-        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
+        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setData(uri);
         intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index db04c71..3e496b6 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -23,14 +23,15 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
 import android.widget.ListView;
 
 /**
@@ -179,6 +180,27 @@
     }
 
     @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        TypedArray a = getActivity().obtainStyledAttributes(null,
+                com.android.internal.R.styleable.PreferenceFragment,
+                com.android.internal.R.attr.preferenceFragmentStyle,
+                0);
+
+        ListView lv = (ListView) view.findViewById(android.R.id.list);
+        if (lv != null) {
+            Drawable divider =
+                    a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider);
+            if (divider != null) {
+                lv.setDivider(divider);
+            }
+        }
+
+        a.recycle();
+    }
+
+    @Override
     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 9c3cefc..aa8674e 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -122,7 +122,7 @@
     protected void onBindView(View view) {
         super.onBindView(view);
 
-        View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
+        View checkableView = view.findViewById(com.android.internal.R.id.switch_widget);
         if (checkableView != null && checkableView instanceof Checkable) {
             if (checkableView instanceof Switch) {
                 final Switch switchView = (Switch) checkableView;
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 90d30d6..2afbb99 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -60,7 +60,7 @@
     private Margins mMinMargins;
 
     private int mColorMode;
-    private int mDuplexMode = DUPLEX_MODE_NONE;
+    private int mDuplexMode;
 
     PrintAttributes() {
         /* hide constructor */
@@ -403,7 +403,7 @@
         mResolution = null;
         mMinMargins = null;
         mColorMode = 0;
-        mDuplexMode = DUPLEX_MODE_NONE;
+        mDuplexMode = 0;
     }
 
     /**
@@ -1427,10 +1427,6 @@
 
         /**
          * Creates a new {@link PrintAttributes} instance.
-         * <p>
-         * If you do not specify a duplex mode, the default
-         * {@link #DUPLEX_MODE_NONE} will be used.
-         * </p>
          *
          * @return The new instance.
          */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index e3694e8..b860bfb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -394,6 +394,14 @@
                 Uri.withAppendedPath(AUTHORITY_URI, "directories");
 
         /**
+         * The content:// style URI for enterprise Directory table. Requests to this URI can be
+         * performed on the UI thread because they are always unblocking.
+         *
+         */
+        public static final Uri CORP_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+                "directories_corp");
+
+        /**
          * The MIME-type of {@link #CONTENT_URI} providing a directory of
          * contact directories.
          */
@@ -417,6 +425,22 @@
         public static final long LOCAL_INVISIBLE = 1;
 
         /**
+         * _ID of the work profile default directory, which represents locally stored contacts.
+         *
+         * @hide
+         */
+        public static final long ENTERPRISE_DEFAULT = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+                + DEFAULT;
+
+        /**
+         * _ID of the work profile directory that represents locally stored invisible contacts.
+         *
+         * @hide
+         */
+        public static final long ENTERPRISE_LOCAL_INVISIBLE = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+                + LOCAL_INVISIBLE;
+
+        /**
          * The name of the package that owns this directory. Contacts Provider
          * fill it in with the name of the package containing the directory provider.
          * If the package is later uninstalled, the directories it owns are
@@ -472,6 +496,15 @@
         public static final String ACCOUNT_NAME = "accountName";
 
         /**
+         * Mimimal ID for corp directory returned from
+         * {@link Directory#CORP_CONTENT_URI}.
+         *
+         * @hide
+         */
+        // slightly smaller than 2 ** 30
+        public static final long ENTERPRISE_DIRECTORY_ID_BASE = 1000000000;
+
+        /**
          * One of {@link #EXPORT_SUPPORT_NONE}, {@link #EXPORT_SUPPORT_ANY_ACCOUNT},
          * {@link #EXPORT_SUPPORT_SAME_ACCOUNT_ONLY}. This is the expectation the
          * directory has for data exported from it.  Clients must obey this setting.
@@ -555,6 +588,24 @@
         public static final int PHOTO_SUPPORT_FULL = 3;
 
         /**
+         * Return TRUE if it is a remote stored directory.
+         */
+        public static boolean isRemoteDirectory(long directoryId) {
+            return directoryId != Directory.DEFAULT
+                    && directoryId != Directory.LOCAL_INVISIBLE
+                    && directoryId != Directory.ENTERPRISE_DEFAULT
+                    && directoryId != Directory.ENTERPRISE_LOCAL_INVISIBLE;
+        }
+
+        /**
+         * Return TRUE if a directory ID is from the contacts provider on the enterprise profile.
+         *
+         */
+        public static boolean isEnterpriseDirectoryId(long directoryId) {
+            return directoryId >= ENTERPRISE_DIRECTORY_ID_BASE;
+        }
+
+        /**
          * Notifies the system of a change in the list of directories handled by
          * a particular directory provider. The Contacts provider will turn around
          * and send a query to the directory provider for the full list of directories,
@@ -1589,6 +1640,14 @@
                 CONTENT_URI, "filter");
 
         /**
+         * It supports the same semantics as {@link #CONTENT_FILTER_URI} and returns the same
+         * columns. If there is a corp profile linked to the current profile, it will query corp
+         * profile, otherwise it will return null.
+         */
+        public static final Uri CORP_CONTENT_FILTER_URI = Uri.withAppendedPath(
+                CORP_CONTENT_URI, "filter");
+
+        /**
          * The content:// style URI for this table joined with useful data from
          * {@link ContactsContract.Data}, filtered to include only starred contacts
          * and the most frequently contacted contacts.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 159ca01..8ae899f 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -125,8 +125,7 @@
     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
 
     /** {@hide} */
-    public static final String
-            ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT";
+    public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
 
     /** {@hide} */
     public static final String
@@ -234,6 +233,7 @@
          * @see #FLAG_SUPPORTS_TYPED_DOCUMENT
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
+         * @see #FLAG_VIRTUAL_DOCUMENT
          */
         public static final String COLUMN_FLAGS = "flags";
 
@@ -357,6 +357,17 @@
         public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9;
 
         /**
+         * Flag indicating that a document is virtual, and doesn't have byte
+         * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see #COLUMN_MIME_TYPE
+         * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
+         *      android.os.CancellationSignal)
+         */
+        public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 10;
+
+        /**
          * Flag indicating that document titles should be hidden when viewing
          * this directory in a larger format grid. For example, a directory
          * containing only images may want the image thumbnails to speak for
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 37f2c04..f560f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6285,7 +6285,10 @@
        public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
 
        /**
-        * Whether the device has been provisioned (0 = false, 1 = true)
+        * Whether the device has been provisioned (0 = false, 1 = true).
+        * <p>On a multiuser device with a separate system user, the screen may be locked
+        * as soon as this is set to true and further activities cannot be launched on the
+        * system user unless they are marked to show over keyguard.
         */
        public static final String DEVICE_PROVISIONED = "device_provisioned";
 
@@ -7755,6 +7758,7 @@
             AUTO_TIME_ZONE,
             POWER_SOUNDS_ENABLED,
             DOCK_SOUNDS_ENABLED,
+            CHARGING_SOUNDS_ENABLED,
             USB_MASS_STORAGE_ENABLED,
             ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 9bf344a..48359d47 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -30,6 +30,9 @@
  * @hide
  */
 public final class ApplicationConfig {
+    private static ApplicationConfig sInstance;
+    private static Object sLock = new Object();
+
     private Set<Pair<Domain, NetworkSecurityConfig>> mConfigs;
     private NetworkSecurityConfig mDefaultConfig;
     private X509TrustManager mTrustManager;
@@ -129,4 +132,30 @@
             mInitialized = true;
         }
     }
+
+    public static void setDefaultInstance(ApplicationConfig config) {
+        synchronized (sLock) {
+            sInstance = config;
+        }
+    }
+
+    public static ApplicationConfig getDefaultInstance() {
+        synchronized (sLock) {
+            return sInstance;
+        }
+    }
+
+    /** @hide */
+    public static ApplicationConfig getPlatformDefault() {
+        return new ApplicationConfig(new ConfigSource() {
+            @Override
+            public NetworkSecurityConfig getDefaultConfig() {
+                return NetworkSecurityConfig.DEFAULT;
+            }
+            @Override
+            public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+                return null;
+            }
+        });
+    }
 }
diff --git a/core/java/android/security/net/config/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java
new file mode 100644
index 0000000..1973ef1
--- /dev/null
+++ b/core/java/android/security/net/config/KeyStoreCertificateSource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.net.config;
+
+import android.util.ArraySet;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.Set;
+
+/**
+ * {@link CertificateSource} which provides certificates from trusted certificate entries of a
+ * {@link KeyStore}.
+ */
+class KeyStoreCertificateSource implements CertificateSource {
+    private final Object mLock = new Object();
+    private final KeyStore mKeyStore;
+    private Set<X509Certificate> mCertificates;
+
+    public KeyStoreCertificateSource(KeyStore ks) {
+        mKeyStore = ks;
+    }
+
+    @Override
+    public Set<X509Certificate> getCertificates() {
+        synchronized (mLock) {
+            if (mCertificates != null) {
+                return mCertificates;
+            }
+            try {
+                Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
+                for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
+                    String alias = en.nextElement();
+                    if (!mKeyStore.isCertificateEntry(alias)) {
+                        continue;
+                    }
+                    X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
+                    if (cert != null) {
+                        certificates.add(cert);
+                    }
+                }
+                mCertificates = certificates;
+                return mCertificates;
+            } catch (KeyStoreException e) {
+                throw new RuntimeException("Failed to load certificates from KeyStore", e);
+            }
+        }
+    }
+}
diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java
new file mode 100644
index 0000000..8d4f098
--- /dev/null
+++ b/core/java/android/security/net/config/KeyStoreConfigSource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.net.config;
+
+import android.util.Pair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.util.Set;
+
+/**
+ * {@link ConfigSource} with a single default config based on a {@link KeyStore} and no per domain
+ * configs.
+ */
+class KeyStoreConfigSource implements ConfigSource {
+    private final NetworkSecurityConfig mConfig;
+
+    public KeyStoreConfigSource(KeyStore ks) {
+        mConfig = new NetworkSecurityConfig.Builder()
+                .addCertificatesEntryRef(
+                        // Use the KeyStore and do not override pins (of which there are none).
+                        new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false))
+                .build();
+    }
+
+    @Override
+    public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+        return null;
+    }
+
+    @Override
+    public NetworkSecurityConfig getDefaultConfig() {
+        return mConfig;
+    }
+}
+
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 1c787b4..9eab80c 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -16,11 +16,14 @@
 
 package android.security.net.config;
 
+import android.util.ArrayMap;
 import android.util.ArraySet;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.net.ssl.X509TrustManager;
@@ -41,7 +44,7 @@
     private final List<CertificatesEntryRef> mCertificatesEntryRefs;
     private Set<TrustAnchor> mAnchors;
     private final Object mAnchorsLock = new Object();
-    private X509TrustManager mTrustManager;
+    private NetworkSecurityTrustManager mTrustManager;
     private final Object mTrustManagerLock = new Object();
 
     private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
@@ -57,12 +60,24 @@
             if (mAnchors != null) {
                 return mAnchors;
             }
-            Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
+            // Merge trust anchors based on the X509Certificate.
+            // If we see the same certificate in two TrustAnchors, one with overridesPins and one
+            // without, the one with overridesPins wins.
+            Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
             for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
-                anchors.addAll(ref.getTrustAnchors());
+                Set<TrustAnchor> anchors = ref.getTrustAnchors();
+                for (TrustAnchor anchor : anchors) {
+                    if (anchor.overridesPins) {
+                        anchorMap.put(anchor.certificate, anchor);
+                    } else if (!anchorMap.containsKey(anchor.certificate)) {
+                        anchorMap.put(anchor.certificate, anchor);
+                    }
+                }
             }
+            ArraySet<TrustAnchor> anchors = new ArraySet<TrustAnchor>(anchorMap.size());
+            anchors.addAll(anchorMap.values());
             mAnchors = anchors;
-            return anchors;
+            return mAnchors;
         }
     }
 
@@ -78,7 +93,7 @@
         return mPins;
     }
 
-    public X509TrustManager getTrustManager() {
+    public NetworkSecurityTrustManager getTrustManager() {
         synchronized(mTrustManagerLock) {
             if (mTrustManager == null) {
                 mTrustManager = new NetworkSecurityTrustManager(this);
@@ -227,10 +242,14 @@
             return Collections.<CertificatesEntryRef>emptyList();
         }
 
-        public boolean hasCertificateEntryRefs() {
+        public boolean hasCertificatesEntryRefs() {
             return mCertificatesEntryRefs != null;
         }
 
+        List<CertificatesEntryRef> getCertificatesEntryRefs() {
+            return mCertificatesEntryRefs;
+        }
+
         public NetworkSecurityConfig build() {
             boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();
             boolean hstsEnforced = getEffectiveHstsEnforced();
diff --git a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
new file mode 100644
index 0000000..ac762ef
--- /dev/null
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security.net.config;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import java.security.Security;
+import java.security.Provider;
+
+/** @hide */
+public final class NetworkSecurityConfigProvider extends Provider {
+    private static final String LOG_TAG = "NetworkSecurityConfig";
+    private static final String PREFIX =
+            NetworkSecurityConfigProvider.class.getPackage().getName() + ".";
+    public static final String META_DATA_NETWORK_SECURITY_CONFIG =
+            "android.security.net.config";
+    private static final boolean DBG = true;
+
+    public NetworkSecurityConfigProvider() {
+        // TODO: More clever name than this
+        super("AndroidNSSP", 1.0, "Android Network Security Policy Provider");
+        put("TrustManagerFactory.PKIX", PREFIX + "RootTrustManagerFactorySpi");
+        put("Alg.Alias.TrustManagerFactory.X509", "PKIX");
+    }
+
+    public static void install(Context context) {
+        ApplicationInfo info = null;
+        // TODO: This lookup shouldn't be done in the app startup path, it should be done lazily.
+        try {
+            info = context.getPackageManager().getApplicationInfo(context.getPackageName(),
+                    PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException("Failed to look up ApplicationInfo", e);
+        }
+        int configResourceId = 0;
+        if (info != null && info.metaData != null) {
+            configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
+        }
+
+        ApplicationConfig config;
+        if (configResourceId != 0) {
+            boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+            if (DBG) {
+                Log.d(LOG_TAG, "Using Network Security Config from resource "
+                        + context.getResources().getResourceEntryName(configResourceId)
+                        + " debugBuild: " + debugBuild);
+            }
+            ConfigSource source = new XmlConfigSource(context, configResourceId, debugBuild);
+            config = new ApplicationConfig(source);
+        } else {
+            if (DBG) {
+                Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
+            }
+            config = ApplicationConfig.getPlatformDefault();
+        }
+
+        ApplicationConfig.setDefaultInstance(config);
+        int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);
+        if (pos != 1) {
+            throw new RuntimeException("Failed to install provider as highest priority provider."
+                    + " Provider was installed at position " + pos);
+        }
+    }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index e69082d..7f5b3ca 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -71,9 +71,28 @@
     @Override
     public void checkServerTrusted(X509Certificate[] certs, String authType)
             throws CertificateException {
-        List<X509Certificate> trustedChain =
-                mDelegate.checkServerTrusted(certs, authType, (String) null);
+        checkServerTrusted(certs, authType, null);
+    }
+
+    /**
+     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+     * modify without modifying those callers.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+            String host) throws CertificateException {
+        List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
         checkPins(trustedChain);
+        return trustedChain;
+    }
+
+    /**
+     * Check if the provided certificate is a user added certificate authority.
+     * This is required by android.net.http.X509TrustManagerExtensions.
+     */
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        // TODO: Figure out the right way to handle this, and if it is still even used.
+        return false;
     }
 
     private void checkPins(List<X509Certificate> chain) throws CertificateException {
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index 1338b9f..b87bf1f 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -18,6 +18,7 @@
 
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.List;
 
 import javax.net.ssl.X509TrustManager;
 
@@ -61,10 +62,24 @@
         config.getTrustManager().checkServerTrusted(certs, authType);
     }
 
-    public void checkServerTrusted(X509Certificate[] certs, String authType, String hostname)
-            throws CertificateException {
+    /**
+     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
+     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
+     * modify without modifying those callers.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
+            String hostname) throws CertificateException {
         NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
-        config.getTrustManager().checkServerTrusted(certs, authType);
+        return config.getTrustManager().checkServerTrusted(certs, authType, hostname);
+    }
+
+    /**
+     * Check if the provided certificate is a user added certificate authority.
+     * This is required by android.net.http.X509TrustManagerExtensions.
+     */
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        // TODO: Figure out the right way to handle this, and if it is still even used.
+        return false;
     }
 
     @Override
diff --git a/core/java/android/security/net/config/RootTrustManagerFactorySpi.java b/core/java/android/security/net/config/RootTrustManagerFactorySpi.java
new file mode 100644
index 0000000..0a1fe88
--- /dev/null
+++ b/core/java/android/security/net/config/RootTrustManagerFactorySpi.java
@@ -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.
+ */
+
+package android.security.net.config;
+
+import android.util.Pair;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Set;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.TrustManagerFactorySpi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** @hide */
+public class RootTrustManagerFactorySpi extends TrustManagerFactorySpi {
+    private ApplicationConfig mApplicationConfig;
+    private NetworkSecurityConfig mConfig;
+
+    @Override
+    public void engineInit(ManagerFactoryParameters spec)
+            throws InvalidAlgorithmParameterException {
+        if (!(spec instanceof ApplicationConfigParameters)) {
+            throw new InvalidAlgorithmParameterException("Unsupported spec: " +  spec + ". Only "
+                    + ApplicationConfigParameters.class.getName() + " supported");
+
+        }
+        mApplicationConfig = ((ApplicationConfigParameters) spec).config;
+    }
+
+    @Override
+    public void engineInit(KeyStore ks) throws KeyStoreException {
+        if (ks != null) {
+            mApplicationConfig = new ApplicationConfig(new KeyStoreConfigSource(ks));
+        } else {
+            mApplicationConfig = ApplicationConfig.getDefaultInstance();
+        }
+    }
+
+    @Override
+    public TrustManager[] engineGetTrustManagers() {
+        if (mApplicationConfig == null) {
+            throw new IllegalStateException("TrustManagerFactory not initialized");
+        }
+        return new TrustManager[] { mApplicationConfig.getTrustManager() };
+    }
+
+    @VisibleForTesting
+    public static final class ApplicationConfigParameters implements ManagerFactoryParameters {
+        public final ApplicationConfig config;
+        public ApplicationConfigParameters(ApplicationConfig config) {
+            this.config = config;
+        }
+    }
+}
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index 6abfb66..1706e95 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -27,8 +27,13 @@
  * @hide
  */
 public class XmlConfigSource implements ConfigSource {
+    private static final int CONFIG_BASE = 0;
+    private static final int CONFIG_DOMAIN = 1;
+    private static final int CONFIG_DEBUG = 2;
+
     private final Object mLock = new Object();
     private final int mResourceId;
+    private final boolean mDebugBuild;
 
     private boolean mInitialized;
     private NetworkSecurityConfig mDefaultConfig;
@@ -36,8 +41,13 @@
     private Context mContext;
 
     public XmlConfigSource(Context context, int resourceId) {
+        this(context, resourceId, false);
+    }
+
+    public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
         mResourceId = resourceId;
         mContext = context;
+        mDebugBuild = debugBuild;
     }
 
     public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -50,6 +60,19 @@
         return mDefaultConfig;
     }
 
+    private static final String getConfigString(int configType) {
+        switch (configType) {
+            case CONFIG_BASE:
+                return "base-config";
+            case CONFIG_DOMAIN:
+                return "domain-config";
+            case CONFIG_DEBUG:
+                return "debug-overrides";
+            default:
+                throw new IllegalArgumentException("Unknown config type: " + configType);
+        }
+    }
+
     private void ensureInitialized() {
         synchronized (mLock) {
             if (mInitialized) {
@@ -147,9 +170,11 @@
         return new Domain(domain, includeSubdomains);
     }
 
-    private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser)
+    private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser,
+            boolean defaultOverridePins)
             throws IOException, XmlPullParserException, ParserException {
-        boolean overridePins = parser.getAttributeBooleanValue(null, "overridePins", false);
+        boolean overridePins =
+                parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
         int sourceId = parser.getAttributeResourceValue(null, "src", -1);
         String sourceString = parser.getAttributeValue(null, "src");
         CertificateSource source = null;
@@ -171,14 +196,15 @@
         return new CertificatesEntryRef(source, overridePins);
     }
 
-    private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser)
+    private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser,
+            boolean defaultOverridePins)
             throws IOException, XmlPullParserException, ParserException {
         int outerDepth = parser.getDepth();
         List<CertificatesEntryRef> anchors = new ArrayList<>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             String tagName = parser.getName();
             if (tagName.equals("certificates")) {
-                anchors.add(parseCertificatesEntry(parser));
+                anchors.add(parseCertificatesEntry(parser, defaultOverridePins));
             } else {
                 XmlUtils.skipCurrentTag(parser);
             }
@@ -188,7 +214,7 @@
 
     private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
             XmlResourceParser parser, Set<String> seenDomains,
-            NetworkSecurityConfig.Builder parentBuilder, boolean baseConfig)
+            NetworkSecurityConfig.Builder parentBuilder, int configType)
             throws IOException, XmlPullParserException, ParserException {
         List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
         NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
@@ -196,6 +222,7 @@
         Set<Domain> domains = new ArraySet<>();
         boolean seenPinSet = false;
         boolean seenTrustAnchors = false;
+        boolean defaultOverridePins = configType == CONFIG_DEBUG;
         String configName = parser.getName();
         int outerDepth = parser.getDepth();
         // Add this builder now so that this builder occurs before any of its children. This
@@ -219,8 +246,9 @@
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             String tagName = parser.getName();
             if ("domain".equals(tagName)) {
-                if (baseConfig) {
-                    throw new ParserException(parser, "domain element not allowed in base-config");
+                if (configType != CONFIG_DOMAIN) {
+                    throw new ParserException(parser,
+                            "domain element not allowed in " + getConfigString(configType));
                 }
                 Domain domain = parseDomain(parser, seenDomains);
                 domains.add(domain);
@@ -229,12 +257,13 @@
                     throw new ParserException(parser,
                             "Multiple trust-anchor elements not allowed");
                 }
-                builder.addCertificatesEntryRefs(parseTrustAnchors(parser));
+                builder.addCertificatesEntryRefs(
+                        parseTrustAnchors(parser, defaultOverridePins));
                 seenTrustAnchors = true;
             } else if ("pin-set".equals(tagName)) {
-                if (baseConfig) {
+                if (configType != CONFIG_DOMAIN) {
                     throw new ParserException(parser,
-                            "pin-set element not allowed in base-config");
+                            "pin-set element not allowed in " + getConfigString(configType));
                 }
                 if (seenPinSet) {
                     throw new ParserException(parser, "Multiple pin-set elements not allowed");
@@ -242,41 +271,68 @@
                 builder.setPinSet(parsePinSet(parser));
                 seenPinSet = true;
             } else if ("domain-config".equals(tagName)) {
-                if (baseConfig) {
+                if (configType != CONFIG_DOMAIN) {
                     throw new ParserException(parser,
-                            "Nested domain-config not allowed in base-config");
+                            "Nested domain-config not allowed in " + getConfigString(configType));
                 }
-                builders.addAll(parseConfigEntry(parser, seenDomains, builder, false));
+                builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType));
             } else {
                 XmlUtils.skipCurrentTag(parser);
             }
         }
-        if (!baseConfig && domains.isEmpty()) {
+        if (configType == CONFIG_DOMAIN && domains.isEmpty()) {
             throw new ParserException(parser, "No domain elements in domain-config");
         }
         return builders;
     }
 
+    private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder,
+            NetworkSecurityConfig.Builder builder) {
+        if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) {
+            return;
+        }
+        // Don't add trust anchors if not already present, the builder will inherit the anchors
+        // from its parent, and that's where the trust anchors should be added.
+        if (!builder.hasCertificatesEntryRefs()) {
+            return;
+        }
+
+        builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs());
+    }
+
     private void parseNetworkSecurityConfig(XmlResourceParser parser)
             throws IOException, XmlPullParserException, ParserException {
         Set<String> seenDomains = new ArraySet<>();
         List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
         NetworkSecurityConfig.Builder baseConfigBuilder = null;
+        NetworkSecurityConfig.Builder debugConfigBuilder = null;
         boolean seenDebugOverrides = false;
         boolean seenBaseConfig = false;
 
         XmlUtils.beginDocument(parser, "network-security-config");
         int outerDepth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-            // TODO: support debug-override.
             if ("base-config".equals(parser.getName())) {
                 if (seenBaseConfig) {
                     throw new ParserException(parser, "Only one base-config allowed");
                 }
                 seenBaseConfig = true;
-                baseConfigBuilder = parseConfigEntry(parser, seenDomains, null, true).get(0).first;
+                baseConfigBuilder =
+                        parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first;
             } else if ("domain-config".equals(parser.getName())) {
-                builders.addAll(parseConfigEntry(parser, seenDomains, baseConfigBuilder, false));
+                builders.addAll(
+                        parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN));
+            } else if ("debug-overrides".equals(parser.getName())) {
+                if (seenDebugOverrides) {
+                    throw new ParserException(parser, "Only one debug-overrides allowed");
+                }
+                if (mDebugBuild) {
+                    debugConfigBuilder =
+                            parseConfigEntry(parser, seenDomains, null, CONFIG_DEBUG).get(0).first;
+                } else {
+                    XmlUtils.skipCurrentTag(parser);
+                }
+                seenDebugOverrides = true;
             } else {
                 XmlUtils.skipCurrentTag(parser);
             }
@@ -286,8 +342,10 @@
         // there. If there is no base config use the platform default.
         NetworkSecurityConfig.Builder platformDefaultBuilder =
                 NetworkSecurityConfig.getDefaultBuilder();
+        addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
         if (baseConfigBuilder != null) {
             baseConfigBuilder.setParent(platformDefaultBuilder);
+            addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder);
         } else {
             baseConfigBuilder = platformDefaultBuilder;
         }
@@ -306,6 +364,7 @@
             if (builder.getParent() == null) {
                 builder.setParent(baseConfigBuilder);
             }
+            addDebugAnchorsIfNeeded(debugConfigBuilder, builder);
             NetworkSecurityConfig config = builder.build();
             for (Domain domain : domains) {
                 configs.add(new Pair<>(domain, config));
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7e7b5fc..ee97e8e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -108,6 +108,11 @@
      * This does not change the interruption filter, only the effects. **/
     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
 
+    public static final int SUPPRESSED_EFFECT_LIGHTS =
+            NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+    public static final int SUPPRESSED_EFFECT_PEEK =
+            NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
     /**
      * The full trim of the StatusBarNotification including all its features.
      *
@@ -822,6 +827,7 @@
         private boolean mIsAmbient;
         private boolean mMatchesInterruptionFilter;
         private int mVisibilityOverride;
+        private int mSuppressedVisualEffects;
 
         public Ranking() {}
 
@@ -861,6 +867,14 @@
             return mVisibilityOverride;
         }
 
+        /**
+         * Returns the type(s) of visual effects that should be suppressed for this notification.
+         * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK}}.
+         */
+        public int getSuppressedVisualEffects() {
+            return mSuppressedVisualEffects;
+        }
+
 
         /**
          * Returns whether the notification matches the user's interruption
@@ -874,12 +888,14 @@
         }
 
         private void populate(String key, int rank, boolean isAmbient,
-                boolean matchesInterruptionFilter, int visibilityOverride) {
+                boolean matchesInterruptionFilter, int visibilityOverride,
+                int suppressedVisualEffects) {
             mKey = key;
             mRank = rank;
             mIsAmbient = isAmbient;
             mMatchesInterruptionFilter = matchesInterruptionFilter;
             mVisibilityOverride = visibilityOverride;
+            mSuppressedVisualEffects = suppressedVisualEffects;
         }
     }
 
@@ -896,6 +912,7 @@
         private ArrayMap<String,Integer> mRanks;
         private ArraySet<Object> mIntercepted;
         private ArrayMap<String, Integer> mVisibilityOverrides;
+        private ArrayMap<String, Integer> mSuppressedVisualEffects;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -921,7 +938,7 @@
         public boolean getRanking(String key, Ranking outRanking) {
             int rank = getRank(key);
             outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
-                    getVisibilityOverride(key));
+                    getVisibilityOverride(key), getSuppressedVisualEffects(key));
             return rank >= 0;
         }
 
@@ -959,11 +976,24 @@
                     buildVisibilityOverridesLocked();
                 }
             }
-            Integer overide = mVisibilityOverrides.get(key);
-            if (overide == null) {
+            Integer override = mVisibilityOverrides.get(key);
+            if (override == null) {
                 return Ranking.VISIBILITY_NO_OVERRIDE;
             }
-            return overide.intValue();
+            return override.intValue();
+        }
+
+        private int getSuppressedVisualEffects(String key) {
+            synchronized (this) {
+                if (mSuppressedVisualEffects == null) {
+                    buildSuppressedVisualEffectsLocked();
+                }
+            }
+            Integer suppressed = mSuppressedVisualEffects.get(key);
+            if (suppressed == null) {
+                return 0;
+            }
+            return suppressed.intValue();
         }
 
         // Locked by 'this'
@@ -992,6 +1022,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildSuppressedVisualEffectsLocked() {
+            Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
+            mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
+            for (String key: suppressedBundle.keySet()) {
+                mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 6fba900..1282fb1 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,13 +28,15 @@
     private final String[] mInterceptedKeys;
     private final int mFirstAmbientIndex;
     private final Bundle mVisibilityOverrides;
+    private final Bundle mSuppressedVisualEffects;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
-            Bundle visibilityOverrides, int firstAmbientIndex) {
+            Bundle visibilityOverrides, int firstAmbientIndex, Bundle suppressedVisualEffects) {
         mKeys = keys;
         mFirstAmbientIndex = firstAmbientIndex;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
+        mSuppressedVisualEffects = suppressedVisualEffects;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -42,6 +44,7 @@
         mFirstAmbientIndex = in.readInt();
         mInterceptedKeys = in.readStringArray();
         mVisibilityOverrides = in.readBundle();
+        mSuppressedVisualEffects = in.readBundle();
     }
 
     @Override
@@ -55,6 +58,7 @@
         out.writeInt(mFirstAmbientIndex);
         out.writeStringArray(mInterceptedKeys);
         out.writeBundle(mVisibilityOverrides);
+        out.writeBundle(mSuppressedVisualEffects);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -83,4 +87,8 @@
     public Bundle getVisibilityOverrides() {
         return mVisibilityOverrides;
     }
+
+    public Bundle getSuppressedVisualEffects() {
+        return mSuppressedVisualEffects;
+    }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 82f1b28..b3399d0 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -77,6 +77,8 @@
     private static final boolean DEFAULT_ALLOW_REMINDERS = true;
     private static final boolean DEFAULT_ALLOW_EVENTS = true;
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
+    private static final boolean DEFAULT_ALLOW_PEEK = true;
+    private static final boolean DEFAULT_ALLOW_LIGHTS = true;
 
     private static final int XML_VERSION = 2;
     private static final String ZEN_TAG = "zen";
@@ -91,6 +93,8 @@
     private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom";
     private static final String ALLOW_ATT_REMINDERS = "reminders";
     private static final String ALLOW_ATT_EVENTS = "events";
+    private static final String ALLOW_ATT_PEEK = "peek";
+    private static final String ALLOW_ATT_LIGHTS = "lights";
 
     private static final String CONDITION_TAG = "condition";
     private static final String CONDITION_ATT_COMPONENT = "component";
@@ -122,6 +126,8 @@
     public int allowCallsFrom = DEFAULT_SOURCE;
     public int allowMessagesFrom = DEFAULT_SOURCE;
     public int user = UserHandle.USER_SYSTEM;
+    public boolean allowPeek = DEFAULT_ALLOW_PEEK;
+    public boolean allowLights = DEFAULT_ALLOW_LIGHTS;
 
     public ZenRule manualRule;
     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
@@ -148,6 +154,8 @@
                 automaticRules.put(ids[i], rules[i]);
             }
         }
+        allowPeek = source.readInt() == 1;
+        allowLights = source.readInt() == 1;
     }
 
     @Override
@@ -175,22 +183,26 @@
         } else {
             dest.writeInt(0);
         }
+        dest.writeInt(allowPeek ? 1 : 0);
+        dest.writeInt(allowLights ? 1 : 0);
     }
 
     @Override
     public String toString() {
         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
-            .append("user=").append(user)
-            .append(",allowCalls=").append(allowCalls)
-            .append(",allowRepeatCallers=").append(allowRepeatCallers)
-            .append(",allowMessages=").append(allowMessages)
-            .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
-            .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
-            .append(",allowReminders=").append(allowReminders)
-            .append(",allowEvents=").append(allowEvents)
-            .append(",automaticRules=").append(automaticRules)
-            .append(",manualRule=").append(manualRule)
-            .append(']').toString();
+                .append("user=").append(user)
+                .append(",allowCalls=").append(allowCalls)
+                .append(",allowRepeatCallers=").append(allowRepeatCallers)
+                .append(",allowMessages=").append(allowMessages)
+                .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
+                .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
+                .append(",allowReminders=").append(allowReminders)
+                .append(",allowEvents=").append(allowEvents)
+                .append(",allowPeek=").append(allowPeek)
+                .append(",allowLights=").append(allowLights)
+                .append(",automaticRules=").append(automaticRules)
+                .append(",manualRule=").append(manualRule)
+                .append(']').toString();
     }
 
     private Diff diff(ZenModeConfig to) {
@@ -222,6 +234,12 @@
         if (allowEvents != to.allowEvents) {
             d.addLine("allowEvents", allowEvents, to.allowEvents);
         }
+        if (allowPeek != to.allowPeek) {
+            d.addLine("allowPeek", allowPeek, to.allowPeek);
+        }
+        if (allowLights != to.allowLights) {
+            d.addLine("allowLights", allowLights, to.allowLights);
+        }
         final ArraySet<String> allRules = new ArraySet<>();
         addKeys(allRules, automaticRules);
         addKeys(allRules, to.automaticRules);
@@ -319,6 +337,8 @@
                 && other.allowMessagesFrom == allowMessagesFrom
                 && other.allowReminders == allowReminders
                 && other.allowEvents == allowEvents
+                && other.allowPeek == allowPeek
+                && other.allowLights == allowLights
                 && other.user == user
                 && Objects.equals(other.automaticRules, automaticRules)
                 && Objects.equals(other.manualRule, manualRule);
@@ -327,7 +347,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
-                allowMessagesFrom, allowReminders, allowEvents, user, automaticRules, manualRule);
+                allowMessagesFrom, allowReminders, allowEvents, allowPeek, allowLights,
+                user, automaticRules, manualRule);
     }
 
     private static String toDayList(int[] days) {
@@ -412,6 +433,8 @@
                         rt.allowCallsFrom = DEFAULT_SOURCE;
                         rt.allowMessagesFrom = DEFAULT_SOURCE;
                     }
+                    rt.allowPeek = safeBoolean(parser, ALLOW_ATT_PEEK, DEFAULT_ALLOW_PEEK);
+                    rt.allowLights = safeBoolean(parser, ALLOW_ATT_LIGHTS, DEFAULT_ALLOW_LIGHTS);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
                 } else if (AUTOMATIC_TAG.equals(tag)) {
@@ -440,6 +463,8 @@
         out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
         out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom));
         out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
+        out.attribute(null, ALLOW_ATT_PEEK, Boolean.toString(allowPeek));
+        out.attribute(null, ALLOW_ATT_LIGHTS, Boolean.toString(allowLights));
         out.endTag(null, ALLOW_TAG);
 
         if (manualRule != null) {
@@ -611,9 +636,17 @@
         if (allowRepeatCallers) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
         }
+        int suppressedVisualEffects = 0;
+        if (!allowPeek) {
+            suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK;
+        }
+        if (!allowLights) {
+            suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+        }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
-        return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders);
+        return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                suppressedVisualEffects);
     }
 
     private static int sourceToPrioritySenders(int source, int def) {
@@ -645,6 +678,10 @@
         allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
         allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders,
                 allowMessagesFrom);
+        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+            allowPeek = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) == 0;
+            allowLights = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) == 0;
+        }
     }
 
     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
new file mode 100644
index 0000000..087eb61
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+/**
+ * @hide
+ */
+interface IQSService {
+    void updateQsTile(in Tile tile);
+}
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
new file mode 100644
index 0000000..6b46bee5
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.IQSService;
+
+/**
+ * @hide
+ */
+oneway interface IQSTileService {
+    void setQSTile(in Tile tile);
+    void onTileAdded();
+    void onTileRemoved();
+    void onStartListening();
+    void onStopListening();
+    void onClick();
+}
diff --git a/core/java/android/service/quicksettings/Tile.aidl b/core/java/android/service/quicksettings/Tile.aidl
new file mode 100644
index 0000000..0373326
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.aidl
@@ -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.
+ */
+
+package android.service.quicksettings;
+
+parcelable Tile;
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
new file mode 100644
index 0000000..c8ae171
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.service.quicksettings;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A Tile holds the state of a tile that will be displayed
+ * in Quick Settings.
+ *
+ * A tile in Quick Settings exists as an icon with an accompanied label.
+ * It also may have content description for accessibility usability.
+ * The style and layout of the tile may change to match a given
+ * device.
+ */
+public final class Tile implements Parcelable {
+
+    private static final String TAG = "Tile";
+
+    private ComponentName mComponentName;
+    private IQSService mService;
+    private Icon mIcon;
+    private CharSequence mLabel;
+    private CharSequence mContentDescription;
+
+    /**
+     * @hide
+     */
+    public Tile(Parcel source) {
+        readFromParcel(source);
+    }
+
+    /**
+     * @hide
+     */
+    public Tile(ComponentName componentName, IQSService service) {
+        mComponentName = componentName;
+        mService = service;
+    }
+
+    /**
+     * @hide
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Gets the current icon for the tile.
+     */
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Sets the current icon for the tile.
+     *
+     * This icon is expected to be white on alpha, and may be
+     * tinted by the system to match it's theme.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param icon New icon to show.
+     */
+    public void setIcon(Icon icon) {
+        this.mIcon = icon;
+    }
+
+    /**
+     * Gets the current label for the tile.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Sets the current label for the tile.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param label New label to show.
+     */
+    public void setLabel(CharSequence label) {
+        this.mLabel = label;
+    }
+
+    /**
+     * Gets the current content description for the tile.
+     */
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sets the current content description for the tile.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param contentDescription New content description to use.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        this.mContentDescription = contentDescription;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Pushes the state of the Tile to Quick Settings to be displayed.
+     */
+    public void updateTile() {
+        try {
+            mService.updateQsTile(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't update tile");
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mService);
+        if (mComponentName != null) {
+            dest.writeByte((byte) 1);
+            mComponentName.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        if (mIcon != null) {
+            dest.writeByte((byte) 1);
+            mIcon.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        TextUtils.writeToParcel(mLabel, dest, flags);
+        TextUtils.writeToParcel(mContentDescription, dest, flags);
+    }
+
+    private void readFromParcel(Parcel source) {
+        mService = IQSService.Stub.asInterface(source.readStrongBinder());
+        if (source.readByte() != 0) {
+            mComponentName = ComponentName.CREATOR.createFromParcel(source);
+        } else {
+            mComponentName = null;
+        }
+        if (source.readByte() != 0) {
+            mIcon = Icon.CREATOR.createFromParcel(source);
+        } else {
+            mIcon = null;
+        }
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    public static final Creator<Tile> CREATOR = new Creator<Tile>() {
+        @Override
+        public Tile createFromParcel(Parcel source) {
+            return new Tile(source);
+        }
+
+        @Override
+        public Tile[] newArray(int size) {
+            return new Tile[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
new file mode 100644
index 0000000..eba4c6f
--- /dev/null
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.service.quicksettings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A QSTileService provides the user a tile that can be added to Quick Settings.
+ * Quick Settings is a space provided that allows the user to change settings and
+ * take quick actions without leaving the context of their current app.
+ *
+ * <p>The lifecycle of a QSTileService is different from some other services in
+ * that it may be unbound during parts of its lifecycle.  Any of the following
+ * lifecycle events can happen indepently in a separate binding/creation of the
+ * service.</p>
+ *
+ * <ul>
+ * <li>When a tile is added by the user its QSTileService will be bound to and
+ * {@link #onTileAdded()} will be called.</li>
+ *
+ * <li>When a tile should be up to date and listing will be indicated by
+ * {@link #onStartListening()} and {@link #onStopListening()}.</li>
+ *
+ * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
+ * will be called.</li>
+ * </ul>
+ * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
+ * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
+ * The label and icon for the service will be used as the default label and
+ * icon for the tile. Here is an example QSTileService declaration.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ *     android:name=".MyQSTileService"
+ *     android:label="@string/my_default_tile_label"
+ *     android:icon="@drawable/my_default_icon_label"
+ *     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ *     <intent-filter>
+ *         <action android:name="android.intent.action.QS_TILE" />
+ *     </intent-filter>
+ * </service>}
+ * </pre>
+ *
+ * @see Tile Tile for details about the UI of a Quick Settings Tile.
+ */
+public class TileService extends Service {
+
+    /**
+     * Action that identifies a Service as being a QSTileService.
+     */
+    public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+
+    private final H mHandler = new H(Looper.getMainLooper());
+
+    private boolean mListening = false;
+    private Tile mTile;
+
+    /**
+     * Called when the user adds this tile to Quick Settings.
+     * <p/>
+     * Note that this is not guaranteed to be called between {@link #onCreate()}
+     * and {@link #onStartListening()}, it will only be called when the tile is added
+     * and not on subsequent binds.
+     */
+    public void onTileAdded() {
+    }
+
+    /**
+     * Called when the user removes this tile from Quick Settings.
+     */
+    public void onTileRemoved() {
+    }
+
+    /**
+     * Called when this tile moves into a listening state.
+     * <p/>
+     * When this tile is in a listening state it is expected to keep the
+     * UI up to date.  Any listeners or callbacks needed to keep this tile
+     * up to date should be registered here and unregistered in {@link #onStopListening()}.
+     *
+     * @see #getQsTile()
+     * @see Tile#updateTile()
+     */
+    public void onStartListening() {
+    }
+
+    /**
+     * Called when this tile moves out of the listening state.
+     */
+    public void onStopListening() {
+    }
+
+    /**
+     * Called when the user clicks on this tile.
+     */
+    public void onClick() {
+    }
+
+    /**
+     * Gets the {@link Tile} for this service.
+     * <p/>
+     * This tile may be used to get or set the current state for this
+     * tile. This tile is only valid for updates between {@link #onStartListening()}
+     * and {@link #onStopListening()}.
+     */
+    public final Tile getQsTile() {
+        return mTile;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IQSTileService.Stub() {
+            @Override
+            public void setQSTile(Tile tile) throws RemoteException {
+                mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
+            }
+
+            @Override
+            public void onTileRemoved() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
+            }
+
+            @Override
+            public void onTileAdded() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
+            }
+
+            @Override
+            public void onStopListening() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
+            }
+
+            @Override
+            public void onStartListening() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
+            }
+
+            @Override
+            public void onClick() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED);
+            }
+        };
+    }
+
+    private class H extends Handler {
+        private static final int MSG_SET_TILE = 1;
+        private static final int MSG_START_LISTENING = 2;
+        private static final int MSG_STOP_LISTENING = 3;
+        private static final int MSG_TILE_ADDED = 4;
+        private static final int MSG_TILE_REMOVED = 5;
+        private static final int MSG_TILE_CLICKED = 6;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_TILE:
+                    mTile = (Tile) msg.obj;
+                    break;
+                case MSG_TILE_ADDED:
+                    TileService.this.onTileRemoved();
+                    break;
+                case MSG_TILE_REMOVED:
+                    TileService.this.onTileAdded();
+                    break;
+                case MSG_START_LISTENING:
+                    if (mListening) {
+                        mListening = false;
+                        TileService.this.onStopListening();
+                    }
+                    break;
+                case MSG_STOP_LISTENING:
+                    if (!mListening) {
+                        mListening = true;
+                        TileService.this.onStartListening();
+                    }
+                    break;
+                case MSG_TILE_CLICKED:
+                    TileService.this.onClick();
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index 463eb5b..ebe3f47 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -52,9 +52,21 @@
     }
 
     public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
-            throws PackageManager.NameNotFoundException, RemoteException {
-        this(pm, AppGlobals.getPackageManager().getServiceInfo(comp,
-                PackageManager.GET_META_DATA, userHandle));
+            throws PackageManager.NameNotFoundException {
+        this(pm, getServiceInfoOrThrow(comp, userHandle));
+    }
+
+    static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
+            throws PackageManager.NameNotFoundException {
+        try {
+            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(comp,
+                    PackageManager.GET_META_DATA, userHandle);
+            if (si != null) {
+                return si;
+            }
+        } catch (RemoteException e) {
+        }
+        throw new PackageManager.NameNotFoundException(comp.toString());
     }
 
     public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 0d5c135..c1d23bb 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -49,6 +49,7 @@
         return location < mList.length ? mList[location] : null;
     }
 
+    @Nullable
     public Locale getPrimary() {
         return mList.length == 0 ? null : get(0);
     }
@@ -179,6 +180,12 @@
         }
     }
 
+    @Nullable
+    public Locale getBestMatch(String[] locales) {
+        // TODO: Fix this to actually do locale negotiation and choose the best match
+        return getPrimary();
+    }
+
     private final static Object sLock = new Object();
 
     @GuardedBy("sLock")
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index cebb8f0..f17a16c 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -15,10 +15,6 @@
 package android.util;
 
 import android.graphics.Path;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * @hide
@@ -27,678 +23,112 @@
     static final String LOGTAG = PathParser.class.getSimpleName();
 
     /**
-     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @param pathString The string representing a path, the same as "d" string in svg file.
      * @return the generated Path object.
      */
-    public static Path createPathFromPathData(String pathData) {
+    public static Path createPathFromPathData(String pathString) {
+        if (pathString == null) {
+            throw new IllegalArgumentException("Path string can not be null.");
+        }
         Path path = new Path();
-        PathDataNode[] nodes = createNodesFromPathData(pathData);
-        if (nodes != null) {
-            try {
-                PathDataNode.nodesToPath(nodes, path);
-            } catch (RuntimeException e) {
-                throw new RuntimeException("Error in parsing " + pathData, e);
-            }
-            return path;
+        boolean hasValidPathData = nParseStringForPath(path.mNativePath, pathString,
+                pathString.length());
+        if (!hasValidPathData) {
+            throw new IllegalArgumentException("Path string: " + pathString +
+                    " does not contain valid path data");
         }
-        return null;
+        return path;
     }
 
     /**
-     * @param pathData The string representing a path, the same as "d" string in svg file.
-     * @return an array of the PathDataNode.
+     * Interpret PathData as path commands and insert the commands to the given path.
+     *
+     * @param data The source PathData to be converted.
+     * @param outPath The Path object where path commands will be inserted.
      */
-    public static PathDataNode[] createNodesFromPathData(String pathData) {
-        if (pathData == null) {
-            return null;
-        }
-        int start = 0;
-        int end = 1;
-
-        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
-        while (end < pathData.length()) {
-            end = nextStart(pathData, end);
-            String s = pathData.substring(start, end).trim();
-            if (s.length() > 0) {
-                float[] val = getFloats(s);
-                addNode(list, s.charAt(0), val);
-            }
-
-            start = end;
-            end++;
-        }
-        if ((end - start) == 1 && start < pathData.length()) {
-            addNode(list, pathData.charAt(start), new float[0]);
-        }
-        return list.toArray(new PathDataNode[list.size()]);
+    public static void createPathFromPathData(Path outPath, PathData data) {
+        nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData);
     }
 
     /**
-     * @param source The array of PathDataNode to be duplicated.
-     * @return a deep copy of the <code>source</code>.
-     */
-    public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
-        if (source == null) {
-            return null;
-        }
-        PathDataNode[] copy = new PathParser.PathDataNode[source.length];
-        for (int i = 0; i < source.length; i ++) {
-            copy[i] = new PathDataNode(source[i]);
-        }
-        return copy;
-    }
-
-    /**
-     * @param nodesFrom The source path represented in an array of PathDataNode
-     * @param nodesTo The target path represented in an array of PathDataNode
+     * @param pathDataFrom The source path represented in PathData
+     * @param pathDataTo The target path represented in PathData
      * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
      */
-    public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
-        if (nodesFrom == null || nodesTo == null) {
-            return false;
-        }
-
-        if (nodesFrom.length != nodesTo.length) {
-            return false;
-        }
-
-        for (int i = 0; i < nodesFrom.length; i ++) {
-            if (nodesFrom[i].mType != nodesTo[i].mType
-                    || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
-                return false;
-            }
-        }
-        return true;
+    public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) {
+        return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData);
     }
 
     /**
-     * Update the target's data to match the source.
-     * Before calling this, make sure canMorph(target, source) is true.
+     * PathData class is a wrapper around the native PathData object, which contains
+     * the result of parsing a path string. Specifically, there are verbs and points
+     * associated with each verb stored in PathData. This data can then be used to
+     * generate commands to manipulate a Path.
+     */
+    public static class PathData {
+        long mNativePathData = 0;
+        public PathData() {
+            mNativePathData = nCreateEmptyPathData();
+        }
+
+        public PathData(PathData data) {
+            mNativePathData = nCreatePathData(data.mNativePathData);
+        }
+
+        public PathData(String pathString) {
+            mNativePathData = nCreatePathDataFromString(pathString, pathString.length());
+            if (mNativePathData == 0) {
+                throw new IllegalArgumentException("Invalid pathData: " + pathString);
+            }
+        }
+
+        /**
+         * Update the path data to match the source.
+         * Before calling this, make sure canMorph(target, source) is true.
+         *
+         * @param source The source path represented in PathData
+         */
+        public void setPathData(PathData source) {
+            nSetPathData(mNativePathData, source.mNativePathData);
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mNativePathData != 0) {
+                nFinalize(mNativePathData);
+                mNativePathData = 0;
+            }
+            super.finalize();
+        }
+    }
+
+    /**
+     * Interpolate between the <code>fromData</code> and <code>toData</code> according to the
+     * <code>fraction</code>, and put the resulting path data into <code>outData</code>.
      *
-     * @param target The target path represented in an array of PathDataNode
-     * @param source The source path represented in an array of PathDataNode
+     * @param outData The resulting PathData of the interpolation
+     * @param fromData The start value as a PathData.
+     * @param toData The end value as a PathData
+     * @param fraction The fraction to interpolate.
      */
-    public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
-        for (int i = 0; i < source.length; i ++) {
-            target[i].mType = source[i].mType;
-            for (int j = 0; j < source[i].mParams.length; j ++) {
-                target[i].mParams[j] = source[i].mParams[j];
-            }
-        }
+    public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData,
+            float fraction) {
+        return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData,
+                toData.mNativePathData, fraction);
     }
 
-    private static int nextStart(String s, int end) {
-        char c;
-
-        while (end < s.length()) {
-            c = s.charAt(end);
-            // Note that 'e' or 'E' are not valid path commands, but could be
-            // used for floating point numbers' scientific notation.
-            // Therefore, when searching for next command, we should ignore 'e'
-            // and 'E'.
-            if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
-                    && c != 'e' && c != 'E') {
-                return end;
-            }
-            end++;
-        }
-        return end;
-    }
-
-    private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
-        list.add(new PathDataNode(cmd, val));
-    }
-
-    private static class ExtractFloatResult {
-        // We need to return the position of the next separator and whether the
-        // next float starts with a '-' or a '.'.
-        int mEndPosition;
-        boolean mEndWithNegOrDot;
-    }
-
-    /**
-     * Parse the floats in the string.
-     * This is an optimized version of parseFloat(s.split(",|\\s"));
-     *
-     * @param s the string containing a command and list of floats
-     * @return array of floats
-     */
-    private static float[] getFloats(String s) {
-        if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
-            return new float[0];
-        }
-        try {
-            float[] results = new float[s.length()];
-            int count = 0;
-            int startPosition = 1;
-            int endPosition = 0;
-
-            ExtractFloatResult result = new ExtractFloatResult();
-            int totalLength = s.length();
-
-            // The startPosition should always be the first character of the
-            // current number, and endPosition is the character after the current
-            // number.
-            while (startPosition < totalLength) {
-                extract(s, startPosition, result);
-                endPosition = result.mEndPosition;
-
-                if (startPosition < endPosition) {
-                    results[count++] = Float.parseFloat(
-                            s.substring(startPosition, endPosition));
-                }
-
-                if (result.mEndWithNegOrDot) {
-                    // Keep the '-' or '.' sign with next number.
-                    startPosition = endPosition;
-                } else {
-                    startPosition = endPosition + 1;
-                }
-            }
-            return Arrays.copyOf(results, count);
-        } catch (NumberFormatException e) {
-            throw new RuntimeException("error in parsing \"" + s + "\"", e);
-        }
-    }
-
-    /**
-     * Calculate the position of the next comma or space or negative sign
-     * @param s the string to search
-     * @param start the position to start searching
-     * @param result the result of the extraction, including the position of the
-     * the starting position of next number, whether it is ending with a '-'.
-     */
-    private static void extract(String s, int start, ExtractFloatResult result) {
-        // Now looking for ' ', ',', '.' or '-' from the start.
-        int currentIndex = start;
-        boolean foundSeparator = false;
-        result.mEndWithNegOrDot = false;
-        boolean secondDot = false;
-        boolean isExponential = false;
-        for (; currentIndex < s.length(); currentIndex++) {
-            boolean isPrevExponential = isExponential;
-            isExponential = false;
-            char currentChar = s.charAt(currentIndex);
-            switch (currentChar) {
-                case ' ':
-                case ',':
-                    foundSeparator = true;
-                    break;
-                case '-':
-                    // The negative sign following a 'e' or 'E' is not a separator.
-                    if (currentIndex != start && !isPrevExponential) {
-                        foundSeparator = true;
-                        result.mEndWithNegOrDot = true;
-                    }
-                    break;
-                case '.':
-                    if (!secondDot) {
-                        secondDot = true;
-                    } else {
-                        // This is the second dot, and it is considered as a separator.
-                        foundSeparator = true;
-                        result.mEndWithNegOrDot = true;
-                    }
-                    break;
-                case 'e':
-                case 'E':
-                    isExponential = true;
-                    break;
-            }
-            if (foundSeparator) {
-                break;
-            }
-        }
-        // When there is nothing found, then we put the end position to the end
-        // of the string.
-        result.mEndPosition = currentIndex;
-    }
-
-    /**
-     * Each PathDataNode represents one command in the "d" attribute of the svg
-     * file.
-     * An array of PathDataNode can represent the whole "d" attribute.
-     */
-    public static class PathDataNode {
-        private char mType;
-        private float[] mParams;
-
-        private PathDataNode(char type, float[] params) {
-            mType = type;
-            mParams = params;
-        }
-
-        private PathDataNode(PathDataNode n) {
-            mType = n.mType;
-            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
-        }
-
-        /**
-         * Convert an array of PathDataNode to Path.
-         *
-         * @param node The source array of PathDataNode.
-         * @param path The target Path object.
-         */
-        public static void nodesToPath(PathDataNode[] node, Path path) {
-            float[] current = new float[6];
-            char previousCommand = 'm';
-            for (int i = 0; i < node.length; i++) {
-                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
-                previousCommand = node[i].mType;
-            }
-        }
-
-        /**
-         * The current PathDataNode will be interpolated between the
-         * <code>nodeFrom</code> and <code>nodeTo</code> according to the
-         * <code>fraction</code>.
-         *
-         * @param nodeFrom The start value as a PathDataNode.
-         * @param nodeTo The end value as a PathDataNode
-         * @param fraction The fraction to interpolate.
-         */
-        public void interpolatePathDataNode(PathDataNode nodeFrom,
-                PathDataNode nodeTo, float fraction) {
-            for (int i = 0; i < nodeFrom.mParams.length; i++) {
-                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
-                        + nodeTo.mParams[i] * fraction;
-            }
-        }
-
-        private static void addCommand(Path path, float[] current,
-                char previousCmd, char cmd, float[] val) {
-
-            int incr = 2;
-            float currentX = current[0];
-            float currentY = current[1];
-            float ctrlPointX = current[2];
-            float ctrlPointY = current[3];
-            float currentSegmentStartX = current[4];
-            float currentSegmentStartY = current[5];
-            float reflectiveCtrlPointX;
-            float reflectiveCtrlPointY;
-
-            switch (cmd) {
-                case 'z':
-                case 'Z':
-                    path.close();
-                    // Path is closed here, but we need to move the pen to the
-                    // closed position. So we cache the segment's starting position,
-                    // and restore it here.
-                    currentX = currentSegmentStartX;
-                    currentY = currentSegmentStartY;
-                    ctrlPointX = currentSegmentStartX;
-                    ctrlPointY = currentSegmentStartY;
-                    path.moveTo(currentX, currentY);
-                    break;
-                case 'm':
-                case 'M':
-                case 'l':
-                case 'L':
-                case 't':
-                case 'T':
-                    incr = 2;
-                    break;
-                case 'h':
-                case 'H':
-                case 'v':
-                case 'V':
-                    incr = 1;
-                    break;
-                case 'c':
-                case 'C':
-                    incr = 6;
-                    break;
-                case 's':
-                case 'S':
-                case 'q':
-                case 'Q':
-                    incr = 4;
-                    break;
-                case 'a':
-                case 'A':
-                    incr = 7;
-                    break;
-            }
-
-            for (int k = 0; k < val.length; k += incr) {
-                switch (cmd) {
-                    case 'm': // moveto - Start a new sub-path (relative)
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        if (k > 0) {
-                            // According to the spec, if a moveto is followed by multiple
-                            // pairs of coordinates, the subsequent pairs are treated as
-                            // implicit lineto commands.
-                            path.rLineTo(val[k + 0], val[k + 1]);
-                        } else {
-                            path.rMoveTo(val[k + 0], val[k + 1]);
-                            currentSegmentStartX = currentX;
-                            currentSegmentStartY = currentY;
-                        }
-                        break;
-                    case 'M': // moveto - Start a new sub-path
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        if (k > 0) {
-                            // According to the spec, if a moveto is followed by multiple
-                            // pairs of coordinates, the subsequent pairs are treated as
-                            // implicit lineto commands.
-                            path.lineTo(val[k + 0], val[k + 1]);
-                        } else {
-                            path.moveTo(val[k + 0], val[k + 1]);
-                            currentSegmentStartX = currentX;
-                            currentSegmentStartY = currentY;
-                        }
-                        break;
-                    case 'l': // lineto - Draw a line from the current point (relative)
-                        path.rLineTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'L': // lineto - Draw a line from the current point
-                        path.lineTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
-                        path.rLineTo(val[k + 0], 0);
-                        currentX += val[k + 0];
-                        break;
-                    case 'H': // horizontal lineto - Draws a horizontal line
-                        path.lineTo(val[k + 0], currentY);
-                        currentX = val[k + 0];
-                        break;
-                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
-                        path.rLineTo(0, val[k + 0]);
-                        currentY += val[k + 0];
-                        break;
-                    case 'V': // vertical lineto - Draws a vertical line from the current point
-                        path.lineTo(currentX, val[k + 0]);
-                        currentY = val[k + 0];
-                        break;
-                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
-                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-
-                        ctrlPointX = currentX + val[k + 2];
-                        ctrlPointY = currentY + val[k + 3];
-                        currentX += val[k + 4];
-                        currentY += val[k + 5];
-
-                        break;
-                    case 'C': // curveto - Draws a cubic Bézier curve
-                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-                        currentX = val[k + 4];
-                        currentY = val[k + 5];
-                        ctrlPointX = val[k + 2];
-                        ctrlPointY = val[k + 3];
-                        break;
-                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1],
-                                val[k + 2], val[k + 3]);
-
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 'q': // Draws a quadratic Bézier (relative)
-                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'Q': // Draws a quadratic Bézier
-                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = currentX + reflectiveCtrlPointX;
-                        ctrlPointY = currentY + reflectiveCtrlPointY;
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = reflectiveCtrlPointX;
-                        ctrlPointY = reflectiveCtrlPointY;
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'a': // Draws an elliptical arc
-                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5] + currentX,
-                                val[k + 6] + currentY,
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX += val[k + 5];
-                        currentY += val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                    case 'A': // Draws an elliptical arc
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5],
-                                val[k + 6],
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX = val[k + 5];
-                        currentY = val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                }
-                previousCmd = cmd;
-            }
-            current[0] = currentX;
-            current[1] = currentY;
-            current[2] = ctrlPointX;
-            current[3] = ctrlPointY;
-            current[4] = currentSegmentStartX;
-            current[5] = currentSegmentStartY;
-        }
-
-        private static void drawArc(Path p,
-                float x0,
-                float y0,
-                float x1,
-                float y1,
-                float a,
-                float b,
-                float theta,
-                boolean isMoreThanHalf,
-                boolean isPositiveArc) {
-
-            /* Convert rotation angle from degrees to radians */
-            double thetaD = Math.toRadians(theta);
-            /* Pre-compute rotation matrix entries */
-            double cosTheta = Math.cos(thetaD);
-            double sinTheta = Math.sin(thetaD);
-            /* Transform (x0, y0) and (x1, y1) into unit space */
-            /* using (inverse) rotation, followed by (inverse) scale */
-            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
-            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
-            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
-            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
-            /* Compute differences and averages */
-            double dx = x0p - x1p;
-            double dy = y0p - y1p;
-            double xm = (x0p + x1p) / 2;
-            double ym = (y0p + y1p) / 2;
-            /* Solve for intersecting unit circles */
-            double dsq = dx * dx + dy * dy;
-            if (dsq == 0.0) {
-                Log.w(LOGTAG, " Points are coincident");
-                return; /* Points are coincident */
-            }
-            double disc = 1.0 / dsq - 1.0 / 4.0;
-            if (disc < 0.0) {
-                Log.w(LOGTAG, "Points are too far apart " + dsq);
-                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
-                drawArc(p, x0, y0, x1, y1, a * adjust,
-                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
-                return; /* Points are too far apart */
-            }
-            double s = Math.sqrt(disc);
-            double sdx = s * dx;
-            double sdy = s * dy;
-            double cx;
-            double cy;
-            if (isMoreThanHalf == isPositiveArc) {
-                cx = xm - sdy;
-                cy = ym + sdx;
-            } else {
-                cx = xm + sdy;
-                cy = ym - sdx;
-            }
-
-            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
-            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
-            double sweep = (eta1 - eta0);
-            if (isPositiveArc != (sweep >= 0)) {
-                if (sweep > 0) {
-                    sweep -= 2 * Math.PI;
-                } else {
-                    sweep += 2 * Math.PI;
-                }
-            }
-
-            cx *= a;
-            cy *= b;
-            double tcx = cx;
-            cx = cx * cosTheta - cy * sinTheta;
-            cy = tcx * sinTheta + cy * cosTheta;
-
-            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-        }
-
-        /**
-         * Converts an arc to cubic Bezier segments and records them in p.
-         *
-         * @param p The target for the cubic Bezier segments
-         * @param cx The x coordinate center of the ellipse
-         * @param cy The y coordinate center of the ellipse
-         * @param a The radius of the ellipse in the horizontal direction
-         * @param b The radius of the ellipse in the vertical direction
-         * @param e1x E(eta1) x coordinate of the starting point of the arc
-         * @param e1y E(eta2) y coordinate of the starting point of the arc
-         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
-         * @param start The start angle of the arc on the ellipse
-         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
-         */
-        private static void arcToBezier(Path p,
-                double cx,
-                double cy,
-                double a,
-                double b,
-                double e1x,
-                double e1y,
-                double theta,
-                double start,
-                double sweep) {
-            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
-            // and http://www.spaceroots.org/documents/ellipse/node22.html
-
-            // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
-
-            double eta1 = start;
-            double cosTheta = Math.cos(theta);
-            double sinTheta = Math.sin(theta);
-            double cosEta1 = Math.cos(eta1);
-            double sinEta1 = Math.sin(eta1);
-            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
-            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
-            double anglePerSegment = sweep / numSegments;
-            for (int i = 0; i < numSegments; i++) {
-                double eta2 = eta1 + anglePerSegment;
-                double sinEta2 = Math.sin(eta2);
-                double cosEta2 = Math.cos(eta2);
-                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
-                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
-                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
-                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
-                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
-                double alpha =
-                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
-                double q1x = e1x + alpha * ep1x;
-                double q1y = e1y + alpha * ep1y;
-                double q2x = e2x - alpha * ep2x;
-                double q2y = e2y - alpha * ep2y;
-
-                p.cubicTo((float) q1x,
-                        (float) q1y,
-                        (float) q2x,
-                        (float) q2y,
-                        (float) e2x,
-                        (float) e2y);
-                eta1 = eta2;
-                e1x = e2x;
-                e1y = e2y;
-                ep1x = ep2x;
-                ep1y = ep2y;
-            }
-        }
-    }
+    // Native functions are defined below.
+    private static native boolean nParseStringForPath(long pathPtr, String pathString,
+            int stringLength);
+    private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+    private static native long nCreateEmptyPathData();
+    private static native long nCreatePathData(long nativePtr);
+    private static native long nCreatePathDataFromString(String pathString, int stringLength);
+    private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+            long toDataPtr, float fraction);
+    private static native void nFinalize(long nativePtr);
+    private static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+    private static native void nSetPathData(long outDataPtr, long fromDataPtr);
 }
+
+
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index c2c247e..d563f51 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -17,6 +17,9 @@
 package android.view;
 
 import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -173,6 +176,7 @@
             // Note: This sort is stable.
             mSequentialFocusComparator.setRoot(root);
             mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl());
+            mSequentialFocusComparator.setFocusables(focusables);
             Collections.sort(focusables, mSequentialFocusComparator);
         } finally {
             mSequentialFocusComparator.recycle();
@@ -598,8 +602,16 @@
                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
     }
 
+    private static final boolean isValidId(final int id) {
+        return id != 0 && id != View.NO_ID;
+    }
+
     /**
      * Sorts views according to their visual layout and geometry for default tab order.
+     * If views are part of a focus chain (nextFocusForwardId), then they are all grouped
+     * together. The head of the chain is used to determine the order of the chain and is
+     * first in the order and the tail of the chain is the last in the order. The views
+     * in the middle of the chain can be arbitrary order.
      * This is used for sequential focus traversal.
      */
     private static final class SequentialFocusComparator implements Comparator<View> {
@@ -607,9 +619,15 @@
         private final Rect mSecondRect = new Rect();
         private ViewGroup mRoot;
         private boolean mIsLayoutRtl;
+        private final SparseArray<View> mFocusables = new SparseArray<View>();
+        private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray();
+        private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
 
         public void recycle() {
             mRoot = null;
+            mFocusables.clear();
+            mHeadsOfChains.clear();
+            mIsConnectedTo.clear();
         }
 
         public void setRoot(ViewGroup root) {
@@ -620,11 +638,72 @@
             mIsLayoutRtl = b;
         }
 
+        public void setFocusables(ArrayList<View> focusables) {
+            for (int i = focusables.size() - 1; i >= 0; i--) {
+                final View view = focusables.get(i);
+                final int id = view.getId();
+                if (isValidId(id)) {
+                    mFocusables.put(id, view);
+                }
+                final int nextId = view.getNextFocusForwardId();
+                if (isValidId(nextId)) {
+                    mIsConnectedTo.put(nextId, true);
+                }
+            }
+
+            for (int i = focusables.size() - 1; i >= 0; i--) {
+                final View view = focusables.get(i);
+                final int nextId = view.getNextFocusForwardId();
+                if (isValidId(nextId) && !mIsConnectedTo.get(view.getId())) {
+                    setHeadOfChain(view);
+                }
+            }
+        }
+
+        private void setHeadOfChain(View head) {
+            for (View view = head; view != null;
+                    view = mFocusables.get(view.getNextFocusForwardId())) {
+                final View otherHead = mHeadsOfChains.get(view);
+                if (otherHead != null) {
+                    if (otherHead == head) {
+                        return; // This view has already had its head set properly
+                    }
+                    // A hydra -- multi-headed focus chain (e.g. A->C and B->C)
+                    // Use the one we've already chosen instead and reset this chain.
+                    view = head;
+                    head = otherHead;
+                }
+                mHeadsOfChains.put(view, head);
+            }
+        }
+
         public int compare(View first, View second) {
             if (first == second) {
                 return 0;
             }
+            // Order between views within a chain is immaterial -- next/previous is
+            // within a chain is handled elsewhere.
+            View firstHead = mHeadsOfChains.get(first);
+            View secondHead = mHeadsOfChains.get(second);
+            if (firstHead == secondHead && firstHead != null) {
+                if (first == firstHead) {
+                    return -1; // first is the head, it should be first
+                } else if (second == firstHead) {
+                    return 1; // second is the head, it should be first
+                } else if (isValidId(first.getNextFocusForwardId())) {
+                    return -1; // first is not the end of the chain
+                } else {
+                    return 1; // first is end of chain
+                }
+            }
+            if (firstHead != null) {
+                first = firstHead;
+            }
+            if (secondHead != null) {
+                second = secondHead;
+            }
 
+            // First see if they belong to the same focus chain.
             getRect(first, mFirstRect);
             getRect(second, mSecondRect);
 
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1be2f95..b3cd8c11 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -101,16 +101,24 @@
      *  for the parent window appears. This allows for synchronizing movement of a child
      *  to repainting the contents of the parent.
      *
+     *  "width" and "height" correspond to the width and height members of
+     *  WindowManager.LayoutParams in the {@link #relayout relayout()} case.
+     *  This may differ from the surface buffer size in the
+     *  case of {@link LayoutParams#FLAG_SCALED} and {@link #relayout relayout()}
+     *  must be used with requestedWidth/height if this must be changed.
+     *
      *  @param window The window being modified. Must be attached to a parent window
      *  or this call will fail.
      *  @param x The new x position
      *  @param y The new y position
+     *  @param width The new width
+     *  @param height The new height
      *  @param deferTransactionUntilFrame Frame number from our parent (attached) to
      *  defer this action until.
      *  @param outFrame Rect in which is placed the new position/size on screen.
      */
-    void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
-            out Rect outFrame);
+    void repositionChild(IWindow childWindow, int left, int top, int right, int bottom,
+            long deferTransactionUntilFrame, out Rect outFrame);
 
     /**
      * If a call to relayout() asked to have the surface destroy deferred,
@@ -178,6 +186,11 @@
 	void reportDropResult(IWindow window, boolean consumed);
 
     /**
+     * Cancel the current drag operation.
+     */
+    void cancelDragAndDrop(IBinder dragToken);
+
+    /**
      * Tell the OS that we've just dragged into a View that is willing to accept the drop
      */
     void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
index 0ee6714..49242bb 100644
--- a/core/java/android/view/MagnificationSpec.java
+++ b/core/java/android/view/MagnificationSpec.java
@@ -28,10 +28,21 @@
 public class MagnificationSpec implements Parcelable {
     private static final int MAX_POOL_SIZE = 20;
     private static final SynchronizedPool<MagnificationSpec> sPool =
-            new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+            new SynchronizedPool<>(MAX_POOL_SIZE);
 
+    /** The magnification scaling factor. */
     public float scale = 1.0f;
+
+    /**
+     * The X coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
     public float offsetX;
+
+    /**
+     * The Y coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
     public float offsetY;
 
     private MagnificationSpec() {
@@ -75,6 +86,12 @@
        offsetY = 0.0f;
     }
 
+    public void setTo(MagnificationSpec other) {
+        scale = other.scale;
+        offsetX = other.offsetX;
+        offsetY = other.offsetY;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -89,6 +106,28 @@
     }
 
     @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        final MagnificationSpec s = (MagnificationSpec) other;
+        return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+        result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+        result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+        return result;
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("<scale:");
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 88e9a95..b61706e 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
+import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
@@ -142,6 +143,10 @@
     private Bitmap mBitmap;
     private float mHotSpotX;
     private float mHotSpotY;
+    // The bitmaps for the additional frame of animated pointer icon. Note that the first frame
+    // will be stored in mBitmap.
+    private Bitmap mBitmapFrames[];
+    private int mDurationPerFrame;
 
     private PointerIcon(int style) {
         mStyle = style;
@@ -472,6 +477,36 @@
         } else {
             drawable = context.getDrawable(bitmapRes);
         }
+        if (drawable instanceof AnimationDrawable) {
+            // Extract animation frame bitmaps.
+            final AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+            final int frames = animationDrawable.getNumberOfFrames();
+            drawable = animationDrawable.getFrame(0);
+            if (frames == 1) {
+                Log.w(TAG, "Animation icon with single frame -- simply treating the first "
+                        + "frame as a normal bitmap icon.");
+            } else {
+                // Assumes they have the exact duration.
+                mDurationPerFrame = animationDrawable.getDuration(0);
+                mBitmapFrames = new Bitmap[frames - 1];
+                final int width = drawable.getIntrinsicWidth();
+                final int height = drawable.getIntrinsicHeight();
+                for (int i = 1; i < frames; ++i) {
+                    Drawable drawableFrame = animationDrawable.getFrame(i);
+                    if (!(drawableFrame instanceof BitmapDrawable)) {
+                        throw new IllegalArgumentException("Frame of an animated pointer icon "
+                                + "must refer to a bitmap drawable.");
+                    }
+                    if (drawableFrame.getIntrinsicWidth() != width ||
+                        drawableFrame.getIntrinsicHeight() != height) {
+                        throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
+                                + "is different. All frames should have the exact same size and "
+                                + "share the same hotspot.");
+                    }
+                    mBitmapFrames[i - 1] = ((BitmapDrawable)drawableFrame).getBitmap();
+                }
+            }
+        }
         if (!(drawable instanceof BitmapDrawable)) {
             throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
                     + "refer to a bitmap drawable.");
@@ -509,8 +544,7 @@
             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;
+                return com.android.internal.R.styleable.Pointer_pointerIconWait;
             case STYLE_CELL:
                 return com.android.internal.R.styleable.Pointer_pointerIconCell;
             case STYLE_CROSSHAIR:
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 514f88b..589c0dc 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -157,10 +157,10 @@
     long mLastLockTime = 0;
 
     boolean mVisible = false;
-    int mLeft = -1;
-    int mTop = -1;
-    int mWidth = -1;
-    int mHeight = -1;
+    int mWindowSpaceLeft = -1;
+    int mWindowSpaceTop = -1;
+    int mWindowSpaceWidth = -1;
+    int mWindowSpaceHeight = -1;
     int mFormat = -1;
     final Rect mSurfaceFrame = new Rect();
     int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
@@ -445,32 +445,33 @@
         getLocationInWindow(mLocation);
         final boolean creating = mWindow == null;
         final boolean formatChanged = mFormat != mRequestedFormat;
-        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
+        final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
         final boolean visibleChanged = mVisible != mRequestedVisible;
-        final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
-        final boolean positionChanged = mLeft != mLocation[0] || mTop != mLocation[1];
+        final boolean layoutSizeChanged = getWidth() != mLayout.width
+                || getHeight() != mLayout.height;
+        final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1];
 
         if (force || creating || formatChanged || sizeChanged || visibleChanged
-            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
+            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
             if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
                     + " visible=" + visibleChanged
-                    + " left=" + (mLeft != mLocation[0])
-                    + " top=" + (mTop != mLocation[1]));
+                    + " left=" + (mWindowSpaceLeft != mLocation[0])
+                    + " top=" + (mWindowSpaceTop != mLocation[1]));
 
             try {
                 final boolean visible = mVisible = mRequestedVisible;
-                mLeft = mLocation[0];
-                mTop = mLocation[1];
-                mWidth = myWidth;
-                mHeight = myHeight;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mWindowSpaceWidth = myWidth;
+                mWindowSpaceHeight = myHeight;
                 mFormat = mRequestedFormat;
 
                 // Scaling/Translate window's layout here because mLayout is not used elsewhere.
 
                 // Places the window relative
-                mLayout.x = mLeft;
-                mLayout.y = mTop;
+                mLayout.x = mWindowSpaceLeft;
+                mLayout.y = mWindowSpaceTop;
                 mLayout.width = getWidth();
                 mLayout.height = getHeight();
                 if (mTranslator != null) {
@@ -485,6 +486,14 @@
                               | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                               | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                               ;
+                if (!creating && !force && !mUpdateWindowNeeded) {
+                    mLayout.privateFlags |=
+                            WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
+                } else {
+                    mLayout.privateFlags &=
+                            ~WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
+                }
+
                 if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
                     mLayout.privateFlags |=
                             WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -516,7 +525,7 @@
                     if (DEBUG) Log.i(TAG, "Cur surface: " + mSurface);
 
                     relayoutResult = mSession.relayout(
-                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
+                        mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight,
                             visible ? VISIBLE : GONE,
                             WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                             mWinFrame, mOverscanInsets, mContentInsets,
@@ -621,11 +630,19 @@
                 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
                 " w=" + mLayout.width + " h=" + mLayout.height +
                 ", frame=" + mSurfaceFrame);
-        } else if (positionChanged) { // Only the position has changed
-            mLeft = mLocation[0];
-            mTop = mLocation[1];
+        } else if (positionChanged || layoutSizeChanged) { // Only the position has changed
+            mWindowSpaceLeft = mLocation[0];
+            mWindowSpaceTop = mLocation[1];
+            // For our size changed check, we keep mLayout.width and mLayout.height
+            // in view local space.
+            mLocation[0] = mLayout.width = getWidth();
+            mLocation[1] = mLayout.height = getHeight();
+
+            transformFromViewToWindowSpace(mLocation);
+
             try {
-                mSession.repositionChild(mWindow, mLeft, mTop,
+                mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
+                        mLocation[0], mLocation[1],
                         viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
                         mWinFrame);
             } catch (RemoteException ex) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c2af9f7..66381f9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -83,6 +83,7 @@
 import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
 import android.view.AccessibilityIterators.WordTextSegmentIterator;
 import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -2416,6 +2417,8 @@
      *                    1              PFLAG3_SCROLL_INDICATOR_END
      *                   1               PFLAG3_ASSIST_BLOCKED
      *            1111111                PFLAG3_POINTER_ICON_MASK
+     *           1                       PFLAG3_PARTIAL_LAYOUT_REQUESTED
+     *          1                        PFLAG3_LAYOUT_PARAMS_CHANGED
      * |-------|-------|-------|-------|
      */
 
@@ -2504,6 +2507,7 @@
      */
     static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
 
+
     /* End of masks for mPrivateFlags3 */
 
     static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -2642,6 +2646,19 @@
     private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
 
     /**
+     * Flag indicating that this view has requested a partial layout and
+     * is added to the AttachInfo's list of views that need a partial layout
+     * request handled on the next traversal.
+     */
+    static final int PFLAG3_PARTIAL_LAYOUT_REQUESTED = 0x800000;
+
+    /**
+     * Flag indicating that this view's LayoutParams have been explicitly changed
+     * since the last layout pass.
+     */
+    static final int PFLAG3_LAYOUT_PARAMS_CHANGED = 0x1000000;
+
+    /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
      *
@@ -3679,7 +3696,7 @@
 
     /**
      * Flag indicating that a drag can cross window boundaries.  When
-     * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, all visible applications will be able to participate
      * in the drag operation and receive the dragged content.
      *
@@ -3724,7 +3741,7 @@
 
     /**
      * Flag indicating that the drag shadow will be opaque.  When
-     * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, the drag shadow will be opaque, otherwise, it will be semitransparent.
      */
     public static final int DRAG_FLAG_OPAQUE = 1 << 9;
@@ -6329,8 +6346,8 @@
 
         position.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
 
-        outRect.set((int) (position.left + 0.5f), (int) (position.top + 0.5f),
-                (int) (position.right + 0.5f), (int) (position.bottom + 0.5f));
+        outRect.set(Math.round(position.left), Math.round(position.top),
+                Math.round(position.right), Math.round(position.bottom));
     }
 
     /**
@@ -12622,10 +12639,14 @@
      * ViewGroup.LayoutParams, and these correspond to the different subclasses
      * of ViewGroup that are responsible for arranging their children.
      *
-     * This method may return null if this View is not attached to a parent
+     * <p>This method may return null if this View is not attached to a parent
      * ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
      * was not invoked successfully. When a View is attached to a parent
-     * ViewGroup, this method must not return null.
+     * ViewGroup, this method must not return null.</p>
+     *
+     * <p>Callers that modify the returned LayoutParams object should call
+     * {@link #setLayoutParams(LayoutParams)} to explicitly inform the view that
+     * LayoutParams have changed.</p>
      *
      * @return The LayoutParams associated with this view, or null if no
      *         parameters have been set yet
@@ -12642,6 +12663,9 @@
      * correspond to the different subclasses of ViewGroup that are responsible
      * for arranging their children.
      *
+     * <p>If the View's existing LayoutParams object as obtained by {@link #getLayoutParams()} is
+     * modified, you should call this method to inform the view that it has changed.</p>
+     *
      * @param params The layout parameters for this view, cannot be null
      */
     public void setLayoutParams(ViewGroup.LayoutParams params) {
@@ -12649,6 +12673,7 @@
             throw new NullPointerException("Layout parameters cannot be null");
         }
         mLayoutParams = params;
+        mPrivateFlags3 |= PFLAG3_LAYOUT_PARAMS_CHANGED;
         resolveLayoutParams();
         if (mParent instanceof ViewGroup) {
             ((ViewGroup) mParent).onSetLayoutParams(this, params);
@@ -12968,10 +12993,6 @@
 
             mPrivateFlags |= PFLAG_DIRTY;
 
-            // Release any resources in-case we don't end up drawing again
-            // as anything cached is no longer valid
-            resetDisplayList();
-
             if (invalidateCache) {
                 mPrivateFlags |= PFLAG_INVALIDATED;
                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
@@ -14328,7 +14349,12 @@
             mParent.requestTransparentRegion(this);
         }
 
-        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+        if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+            initialAwakenScrollBars();
+            mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
+        }
+
+        mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED);
 
         jumpDrawablesToCurrentState();
 
@@ -14666,8 +14692,13 @@
      */
     @CallSuper
     protected void onDetachedFromWindowInternal() {
+        if (mAttachInfo != null && isPartialLayoutRequested()) {
+            mAttachInfo.mPartialLayoutViews.remove(this);
+        }
+
         mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
-        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+        mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED
+                | PFLAG3_LAYOUT_PARAMS_CHANGED);
 
         removeUnsetPressCallback();
         removeLongPressCallback();
@@ -16854,6 +16885,29 @@
     }
 
     /**
+     * Indicates whether or not this view has requested a partial layout that
+     * may not affect its size or position within its parent. This state will be reset
+     * the next time this view is laid out.
+     *
+     * @return true if partial layout has been requested
+     */
+    public final boolean isPartialLayoutRequested() {
+        return (mPrivateFlags3 & PFLAG3_PARTIAL_LAYOUT_REQUESTED)
+                == PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+    }
+
+    /**
+     * Returns true if this view's {@link ViewGroup.LayoutParams LayoutParams} changed
+     * since the last time this view was successfully laid out. Typically this happens as a
+     * result of a call to {@link #setLayoutParams(LayoutParams)}.
+     *
+     * @return true if this view's LayoutParams changed since last layout.
+     */
+    public final boolean didLayoutParamsChange() {
+        return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
+    }
+
+    /**
      * Return true if o is a ViewGroup that is laying out using optical bounds.
      * @hide
      */
@@ -16910,6 +16964,7 @@
         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
             onLayout(changed, l, t, r, b);
             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+            mPrivateFlags3 &= ~PFLAG3_LAYOUT_PARAMS_CHANGED;
 
             ListenerInfo li = mListenerInfo;
             if (li != null && li.mOnLayoutChangeListeners != null) {
@@ -16923,6 +16978,7 @@
         }
 
         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+        mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED;
         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
     }
 
@@ -18498,21 +18554,29 @@
      * must be an array of two integers. After the method returns, the array
      * contains the x and y location in that order.</p>
      *
-     * @param location an array of two integers in which to hold the coordinates
+     * @param outWindowSpace an array of two integers in which to hold the coordinates
      */
-    public void getLocationInWindow(@Size(2) int[] location) {
-        if (location == null || location.length < 2) {
-            throw new IllegalArgumentException("location must be an array of two integers");
+    public void getLocationInWindow(@Size(2) int[] outWindowSpace) {
+        outWindowSpace[0] = 0;
+        outWindowSpace[1] = 0;
+
+        transformFromViewToWindowSpace(outWindowSpace);
+    }
+
+    void transformFromViewToWindowSpace(@Size(2) int[] inOutLocation) {
+        if (inOutLocation == null || inOutLocation.length < 2) {
+            throw new IllegalArgumentException("inOutLocation must be an array of two integers");
         }
 
         if (mAttachInfo == null) {
             // When the view is not attached to a window, this method does not make sense
-            location[0] = location[1] = 0;
+            inOutLocation[0] = inOutLocation[1] = 0;
             return;
         }
 
-        float[] position = mAttachInfo.mTmpTransformLocation;
-        position[0] = position[1] = 0.0f;
+        float position[] = mAttachInfo.mTmpTransformLocation;
+        position[0] = inOutLocation[0];
+        position[1] = inOutLocation[1];
 
         if (!hasIdentityMatrix()) {
             getMatrix().mapPoints(position);
@@ -18544,8 +18608,8 @@
             position[1] -= vr.mCurScrollY;
         }
 
-        location[0] = (int) (position[0] + 0.5f);
-        location[1] = (int) (position[1] + 0.5f);
+        inOutLocation[0] = Math.round(position[0]);
+        inOutLocation[1] = Math.round(position[1]);
     }
 
     /**
@@ -19008,7 +19072,7 @@
         mPrivateFlags |= PFLAG_INVALIDATED;
 
         if (mParent != null && !mParent.isLayoutRequested()) {
-            mParent.requestLayout();
+            mParent.requestLayoutForChild(this);
         }
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
             mAttachInfo.mViewRequestingLayout = null;
@@ -19027,6 +19091,11 @@
         mPrivateFlags |= PFLAG_INVALIDATED;
     }
 
+    void forcePartialLayout() {
+        forceLayout();
+        mPrivateFlags3 |= PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+    }
+
     /**
      * <p>
      * This is called to find out how big a view should be. The parent
@@ -19841,6 +19910,15 @@
     }
 
     /**
+     * @deprecated Use {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)
+     * startDragAndDrop()} for newer platform versions.
+     */
+    public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+                                   Object myLocalState, int flags) {
+        return startDragAndDrop(data, shadowBuilder, myLocalState, flags);
+    }
+
+    /**
      * Starts a drag and drop operation. When your application calls this method, it passes a
      * {@link android.view.View.DragShadowBuilder} object to the system. The
      * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
@@ -19857,9 +19935,10 @@
      *  {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
      * </p>
      * <p>
-     * Your application can invoke startDrag() on any attached View object. The View object does not
-     * need to be the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to
-     * be related to the View the user selected for dragging.
+     * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
+     * int) startDragAndDrop()} on any attached View object. The View object does not need to be
+     * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
+     * to the View the user selected for dragging.
      * </p>
      * @param data A {@link android.content.ClipData} object pointing to the data to be
      * transferred by the drag and drop operation.
@@ -19879,10 +19958,10 @@
      * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
      * do a drag, and so no drag operation is in progress.
      */
-    public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+    public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
             Object myLocalState, int flags) {
         if (ViewDebug.DEBUG_DRAG) {
-            Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " flags=" + flags);
+            Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
         }
         boolean okay = false;
 
@@ -19901,11 +19980,11 @@
         }
         Surface surface = new Surface();
         try {
-            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+            mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
                     flags, shadowSize.x, shadowSize.y, surface);
-            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
-                    + " surface=" + surface);
-            if (token != null) {
+            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
+                    + mAttachInfo.mDragToken + " surface=" + surface);
+            if (mAttachInfo.mDragToken != null) {
                 Canvas canvas = surface.lockCanvas(null);
                 try {
                     canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19922,7 +20001,7 @@
                 // repurpose 'shadowSize' for the last touch point
                 root.getLastTouchPoint(shadowSize);
 
-                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
                         shadowSize.x, shadowSize.y,
                         shadowTouchPoint.x, shadowTouchPoint.y, data);
                 if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -19940,6 +20019,39 @@
     }
 
     /**
+     * Cancels an ongoing drag and drop operation.
+     * <p>
+     * A {@link android.view.DragEvent} object with
+     * {@link android.view.DragEvent#getAction()} value of
+     * {@link android.view.DragEvent#ACTION_DRAG_ENDED} and
+     * {@link android.view.DragEvent#getResult()} value of {@code false}
+     * will be sent to every
+     * View that received {@link android.view.DragEvent#ACTION_DRAG_STARTED}
+     * even if they are not currently visible.
+     * </p>
+     * <p>
+     * This method can be called on any View in the same window as the View on which
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int) startDragAndDrop}
+     * was called.
+     * </p>
+     */
+    public final void cancelDragAndDrop() {
+        if (ViewDebug.DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "cancelDragAndDrop");
+        }
+        if (mAttachInfo.mDragToken != null) {
+            try {
+                mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken);
+            } catch (Exception e) {
+                Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+            }
+            mAttachInfo.mDragToken = null;
+        } else {
+            Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+        }
+    }
+
+    /**
      * Starts a move from {startX, startY}, the amount of the movement will be the offset
      * between {startX, startY} and the new cursor positon.
      * @param startX horizontal coordinate where the move started.
@@ -19961,7 +20073,8 @@
 
     /**
      * Handles drag events sent by the system following a call to
-     * {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}.
+     * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
      *<p>
      * When the system calls this method, it passes a
      * {@link android.view.DragEvent} object. A call to
@@ -21847,6 +21960,7 @@
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
+            void schedulePartialLayout();
         }
 
         /**
@@ -22217,6 +22331,17 @@
         View mViewRequestingLayout;
 
         /**
+         * Used to track views that need (at least) a partial relayout at their current size
+         * during the next traversal.
+         */
+        final List<View> mPartialLayoutViews = new ArrayList<View>();
+
+        /**
+         * Used to track the identity of the current drag operation.
+         */
+        IBinder mDragToken;
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index db978a6..11df9a3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -60,6 +60,8 @@
 import java.util.List;
 import java.util.Map;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 /**
  * <p>
@@ -222,7 +224,7 @@
      * NOTE: If you change the flags below make sure to reflect the changes
      *       the DisplayList class
      */
-    
+
     // When set, ViewGroup invalidates only the child's rectangle
     // Set by default
     static final int FLAG_CLIP_CHILDREN = 0x1;
@@ -267,7 +269,7 @@
     /**
      * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
      * to get the index of the child to draw for that iteration.
-     * 
+     *
      * @hide
      */
     protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
@@ -1325,7 +1327,7 @@
             children[i].dispatchConfigurationChanged(newConfig);
         }
     }
-    
+
     /**
      * {@inheritDoc}
      */
@@ -2212,7 +2214,7 @@
                         final float y = ev.getY(actionIndex);
                         // Find a child that can receive the event.
                         // Scan children from front to back.
-                        final ArrayList<View> preorderedList = buildOrderedChildList();
+                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                         final boolean customOrder = preorderedList == null
                                 && isChildrenDrawingOrderEnabled();
                         final View[] children = mChildren;
@@ -2345,6 +2347,18 @@
     }
 
     /**
+     * Provide custom ordering of views in which the touch will be dispatched.
+     *
+     * This is called within a tight loop, so you are not allowed to allocate objects, including
+     * the return array. Instead, you should return a pre-allocated list that will be cleared
+     * after the dispatch is finished.
+     * @hide
+     */
+    public ArrayList<View> buildTouchDispatchChildList() {
+        return buildOrderedChildList();
+    }
+
+    /**
      * Finds the child which has accessibility focus.
      *
      * @return The child that has focus.
@@ -2785,7 +2799,7 @@
      * @see #FOCUS_BEFORE_DESCENDANTS
      * @see #FOCUS_AFTER_DESCENDANTS
      * @see #FOCUS_BLOCK_DESCENDANTS
-     * @see #onRequestFocusInDescendants(int, android.graphics.Rect) 
+     * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
      */
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
@@ -4102,7 +4116,7 @@
     /**
      * <p>Adds a child view. If no layout parameters are already set on the child, the
      * default parameters for this ViewGroup are set on the child.</p>
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4118,7 +4132,7 @@
     /**
      * Adds a child view. If no layout parameters are already set on the child, the
      * default parameters for this ViewGroup are set on the child.
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4558,7 +4572,7 @@
 
     /**
      * {@inheritDoc}
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4577,7 +4591,7 @@
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
-     * 
+     *
      * @param view the view to remove from the group
      */
     public void removeViewInLayout(View view) {
@@ -4605,7 +4619,7 @@
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
-     * 
+     *
      * @param index the position in the group of the view to remove
      */
     public void removeViewAt(int index) {
@@ -4794,7 +4808,7 @@
     /**
      * Call this method to remove all child views from the
      * ViewGroup.
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -5114,10 +5128,10 @@
                     transformMatrix = childMatrix;
                 }
                 transformMatrix.mapRect(boundingRect);
-                dirty.set((int) (boundingRect.left - 0.5f),
-                        (int) (boundingRect.top - 0.5f),
-                        (int) (boundingRect.right + 0.5f),
-                        (int) (boundingRect.bottom + 0.5f));
+                dirty.set((int) Math.floor(boundingRect.left),
+                        (int) Math.floor(boundingRect.top),
+                        (int) Math.ceil(boundingRect.right),
+                        (int) Math.ceil(boundingRect.bottom));
             }
 
             do {
@@ -5154,10 +5168,10 @@
                         RectF boundingRect = attachInfo.mTmpTransformRect;
                         boundingRect.set(dirty);
                         m.mapRect(boundingRect);
-                        dirty.set((int) (boundingRect.left - 0.5f),
-                                (int) (boundingRect.top - 0.5f),
-                                (int) (boundingRect.right + 0.5f),
-                                (int) (boundingRect.bottom + 0.5f));
+                        dirty.set((int) Math.floor(boundingRect.left),
+                                (int) Math.floor(boundingRect.top),
+                                (int) Math.ceil(boundingRect.right),
+                                (int) Math.ceil(boundingRect.bottom));
                     }
                 }
             } while (parent != null);
@@ -5457,8 +5471,8 @@
                 position[0] = offset.x;
                 position[1] = offset.y;
                 child.getMatrix().mapPoints(position);
-                offset.x = (int) (position[0] + 0.5f);
-                offset.y = (int) (position[1] + 0.5f);
+                offset.x = Math.round(position[0]);
+                offset.y = Math.round(position[1]);
             }
             offset.x += dx;
             offset.y += dy;
@@ -5485,8 +5499,8 @@
             rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
                     mClipBounds.bottom);
         }
-        r.set((int) (rect.left + 0.5f), (int) (rect.top + 0.5f), (int) (rect.right + 0.5f),
-                (int) (rect.bottom + 0.5f));
+        r.set((int) Math.floor(rect.left), (int) Math.floor(rect.top),
+                (int) Math.ceil(rect.right), (int) Math.ceil(rect.bottom));
         if (rectIsVisible && mParent != null) {
             rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
         }
@@ -5517,6 +5531,172 @@
             int l, int t, int r, int b);
 
     /**
+     * {@inheritDoc}
+     *
+     * <p>Most subclasses should not need to override this method. The default implementation
+     * will call {@link #findDependentLayoutAxes(View, int)} to determine how
+     * to optimally proceed. If neither horizontal nor vertical layout depends on the given
+     * child, this method will call {@link #requestPartialLayoutForChild(View)}. If one or both
+     * do, it will call {@link #requestLayout()}.</p>
+     *
+     * @param child Child requesting a layout
+     */
+    @Override
+    public void requestLayoutForChild(View child) {
+        if (child == null || child.getParent() != this) {
+            throw new IllegalArgumentException(
+                    "child parameter must be a direct child view of this ViewGroup");
+        }
+
+        // If we don't have a parent ourselves, record that we need a full layout.
+        // Our whole subtree is detached.
+        final ViewParent parent = getParent();
+        if (parent == null) {
+            requestLayout();
+            return;
+        }
+
+        // We can optimize the layout request for this child into a partial layout
+        // if the child has already been laid out at least once and neither horizontal nor
+        // vertical layout within ourselves is dependent on pending layout changes within
+        // this child. Otherwise we need to request a full layout for ourselves and continue
+        // to recurse up the view hierarchy.
+        if (child.isLaidOut() && findDependentLayoutAxes(child, FLAG_LAYOUT_AXIS_ANY) == 0) {
+            requestPartialLayoutForChild(child);
+        } else {
+            requestLayout();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * Optimized implementations for specific ViewGroup subclasses may check if the child's
+     * {@link View#didLayoutParamsChange() LayoutParams changed} and in what ways.</p>
+     *
+     * @param child Direct child of this ViewParent to check
+     * @param axisFilter Which axes to check for dependencies. Can be
+     *                   {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+     *                   or {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * @return Axes of this ViewParent that depend on the given child's layout changes
+     */
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return FLAG_LAYOUT_AXIS_ANY;
+    }
+
+    /**
+     * This is a helper implementation for {@link #findDependentLayoutAxes(View, int)} that
+     * is not the default implementation in ViewGroup. This is to preserve compatibility with
+     * existing app-side ViewGroup subclasses that existed before the partial layout system was
+     * added to Android. It explicitly checks that the LayoutParams of the child are of the
+     * expected type so that subclasses of standard framework layouts do not erroneously
+     * start believing that it's safe to do a partial layout when that assertion can't
+     * reasonably be confirmed.
+     *
+     * <p>If you're reading this as an author of a custom ViewGroup's findDependentLayoutAxes
+     * method you might be frustrated to discover that it is not a part of the Android public API.
+     * Many ViewGroup implementations will need to make small but important modifications
+     * to an implementation like this one in order to be correct. Instead of encouraging
+     * view authors to call this method, then make their own redundant recursive calls to
+     * <code>getParent().findDependentLayoutAxes(...)</code> in addition to the one
+     * that can happen here, this method is hidden and only used internally.</p>
+     *
+     * <p>Do feel free to copy this implementation and adapt it to suit your own purposes.</p>
+     *
+     * @hide
+     */
+    protected final int findDependentLayoutAxesHelper(View child, int axisFilter,
+            Class<?> layoutParamsClass) {
+        if (!checkPartialLayoutParams(child, layoutParamsClass)) return axisFilter;
+        if (child.didLayoutParamsChange()) {
+            // Anything could have changed about our previous assumptions.
+            return axisFilter;
+        }
+
+        final LayoutParams lp = child.getLayoutParams();
+
+        // Our layout can always end up depending on a WRAP_CONTENT child.
+        final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (wrapAxisFilter == axisFilter) {
+            // We know all queried axes are affected, just return early.
+            return wrapAxisFilter;
+        }
+
+        // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+        // that our layout will remain stable within our parent. We need to ask.
+        final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (matchAxisFilter != 0) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                // If our parent depends on us for an axis, then our layout can also be affected
+                // by a MATCH_PARENT child along that axis.
+                return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+                        | wrapAxisFilter;
+            }
+
+            // If we don't have a parent, assume we're affected
+            // in any determined affected direction.
+            return matchAxisFilter | wrapAxisFilter;
+        }
+
+        // Two exact sizes and LayoutParams didn't change. We're safe.
+        return 0;
+    }
+
+    /**
+     * Throw an IllegalArgumentException if the supplied view is not a direct child of
+     * this ViewGroup and return false if this view's LayoutParams is not of class lpClass.
+     * Implementations of {@link ViewGroup#findDependentLayoutAxes(View, int)} use this
+     * to check input parameters and defensively return the full axis filter mask themselves
+     * if the LayoutParams class is not of the exact expected type; e.g. it is a subclass
+     * of one of the standard framework layouts and we can't make assumptions.
+     * @hide
+     */
+    protected final boolean checkPartialLayoutParams(View child, Class<?> lpClass) {
+        if (child.getParent() != this) {
+            throw new IllegalArgumentException("View " + child
+                    + " is not a direct child of " + this);
+        }
+        final ViewGroup.LayoutParams lp = child.getLayoutParams();
+        return lp != null || lp.getClass() == lpClass;
+    }
+
+    /**
+     * Called when a child of this ViewParent requires a relayout before the next frame
+     * is drawn, but the caller can guarantee that the size of the child will not change
+     * during a measure and layout pass.
+     *
+     * <p>A call to this method will schedule a partial layout for the supplied view as long as
+     * it is a direct child of this ViewGroup and this ViewGroup is attached to a window.
+     * On the next scheduled view hierarchy traversal the given child view will be re-measured
+     * at its current measured size and re-laid out at its current position within its parent.</p>
+     *
+     * @param child Child that requires a partial layout
+     */
+    public void requestPartialLayoutForChild(View child) {
+        if (!child.isPartialLayoutRequested()) {
+            child.forcePartialLayout();
+            if (mAttachInfo != null) {
+                final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+                final boolean schedule = partialLayoutViews.isEmpty();
+                partialLayoutViews.add(child);
+                if (schedule) {
+                    mAttachInfo.mRootCallbacks.schedulePartialLayout();
+                }
+                child.invalidate();
+            } else {
+                requestLayout();
+            }
+        }
+    }
+
+    /**
      * Indicates whether the view group has the ability to animate its children
      * after the first layout.
      *
@@ -5862,7 +6042,7 @@
      *         of its descendants
      */
     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return p;
+        return new LayoutParams(p);
     }
 
     /**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 07f1e2c..6ae448a 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -26,6 +26,11 @@
  * 
  */
 public interface ViewParent {
+    public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1;
+    public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2;
+    public static final int FLAG_LAYOUT_AXIS_ANY
+            = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL;
+
     /**
      * Called when something has changed which has invalidated the layout of a
      * child of this view parent. This will schedule a layout pass of the view
@@ -601,4 +606,48 @@
      * @return true if the action was consumed by this ViewParent
      */
     public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+    /**
+     * Called when a child of this ViewParent requires a relayout before
+     * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()}
+     * will implicitly result in a call to
+     * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this
+     * method directly. Call <code>child.requestLayout()</code> instead.
+     *
+     * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()}
+     * would cause a matching call to <code>requestLayout</code> on each parent view up to
+     * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may
+     * explicitly decide how to handle a layout request. This allows for optimizations when
+     * a view parent knows that a layout-altering change in a child will not affect its own
+     * measurement.</p>
+     *
+     * @param child Child requesting a layout
+     */
+    public void requestLayoutForChild(View child);
+
+    /**
+     * Determine which axes of this ViewParent's layout are dependent on the given
+     * direct child view. The returned value is a flag set that may contain
+     * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}.
+     * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for
+     * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>.
+     *
+     * <p>The given child must be a direct child view. Implementations should throw
+     * {@link IllegalArgumentException} otherwise.</p>
+     *
+     * <p>The caller may specify which axes it cares about. This should be treated as a filter.
+     * Implementations should never return a result that would be different from
+     * <code>result & axisFilter</code>.</p>
+     *
+     * @param child Direct child of this ViewParent to check
+     * @param axisFilter Which axes to check for dependencies. Can be
+     *                   {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+     *                   or {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * @return Axes of this ViewParent that depend on the given child's layout changes
+     *
+     * @see #FLAG_LAYOUT_AXIS_HORIZONTAL
+     * @see #FLAG_LAYOUT_AXIS_VERTICAL
+     * @see #FLAG_LAYOUT_AXIS_ANY
+     */
+    public int findDependentLayoutAxes(View child, int axisFilter);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d80c6a3..2c0cd7a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
 import android.Manifest;
 import android.animation.LayoutTransition;
 import android.app.ActivityManagerNative;
@@ -37,7 +39,6 @@
 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;
@@ -74,7 +75,6 @@
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.view.WindowCallbacks;
 import android.widget.Scroller;
 
 import com.android.internal.R;
@@ -91,6 +91,7 @@
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -176,6 +177,11 @@
 
     int mViewVisibility;
     boolean mAppVisible = true;
+    // For recents to freeform transition we need to keep drawing after the app receives information
+    // that it became invisible. This will ignore that information and depend on the decor view
+    // visibility to control drawing. The decor view visibility will get adjusted when the app get
+    // stopped and that's when the app will stop drawing further frames.
+    private boolean mForceDecorViewVisibility = false;
     int mOrigWindowType = -1;
 
     /** Whether the window had focus during the most recent traversal. */
@@ -561,6 +567,8 @@
                         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                     mInputChannel = new InputChannel();
                 }
+                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                 try {
                     mOrigWindowType = mWindowAttributes.type;
                     mAttachInfo.mRecomputeGlobalAttributes = true;
@@ -945,6 +953,25 @@
     }
 
     @Override
+    public void requestLayoutForChild(View child) {
+        requestLayout();
+    }
+
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        if (child != mView) {
+            return 0;
+        }
+
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams();
+        final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0;
+        final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0;
+        return (horizontal | vertical) & axisFilter;
+    }
+
+    @Override
     public boolean isLayoutRequested() {
         return mLayoutRequested;
     }
@@ -1063,7 +1090,7 @@
     }
 
     int getHostVisibility() {
-        return mAppVisible ? mView.getVisibility() : View.GONE;
+        return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
     }
 
     /**
@@ -1088,6 +1115,10 @@
         }
     }
 
+    public void schedulePartialLayout() {
+        scheduleTraversals();
+    }
+
     /**
      * Notifies the HardwareRenderer that a new frame will be coming soon.
      * Currently only {@link ThreadedRenderer} cares about this, and uses
@@ -1568,7 +1599,7 @@
         boolean insetsPending = false;
         int relayoutResult = 0;
 
-        boolean isViewVisible = viewVisibility == View.VISIBLE;
+        final boolean isViewVisible = viewVisibility == View.VISIBLE;
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null) {
 
@@ -1625,7 +1656,7 @@
                 if (mPendingConfiguration.seq != 0) {
                     if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
                             + mPendingConfiguration);
-                    updateConfiguration(mPendingConfiguration, !mFirst);
+                    updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
                     mPendingConfiguration.seq = 0;
                 }
 
@@ -1638,6 +1669,8 @@
                 final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                         mAttachInfo.mStableInsets);
                 final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
+                final boolean surfaceSizeChanged = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                 if (contentInsetsChanged) {
                     mAttachInfo.mContentInsets.set(mPendingContentInsets);
                     if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -1723,10 +1756,20 @@
                             mAttachInfo.mHardwareRenderer.isEnabled()) {
                         mAttachInfo.mHardwareRenderer.destroy();
                     }
-                } else if (surfaceGenerationId != mSurface.getGenerationId() &&
-                        mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
+                } else if ((surfaceGenerationId != mSurface.getGenerationId()
+                        || surfaceSizeChanged)
+                        && mSurfaceHolder == null
+                        && mAttachInfo.mHardwareRenderer != null) {
                     mFullRedrawNeeded = true;
                     try {
+                        // Need to do updateSurface (which leads to CanvasContext::setSurface and
+                        // re-create the EGLSurface) if either the Surface changed (as indicated by
+                        // generation id), or WindowManager changed the surface size. The latter is
+                        // because on some chips, changing the consumer side's BufferQueue size may
+                        // not take effect immediately unless we create a new EGLSurface.
+                        // Note that frame size change doesn't always imply surface size change (eg.
+                        // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
+                        // flag from WindowManager.
                         mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
                     } catch (OutOfResourcesException e) {
                         handleOutOfResourcesException(e);
@@ -1915,7 +1958,48 @@
                 || mAttachInfo.mRecomputeGlobalAttributes;
         if (didLayout) {
             performLayout(lp, desiredWindowWidth, desiredWindowHeight);
+        }
 
+        /*
+         * Handle partial layouts.
+         *
+         * Views that have requested partial layouts will not change size or position
+         * within their parent view, therefore we will re-measure and re-layout each one
+         * after any regularly scheduled layout pass. Any view that already had its
+         * isLayoutRequested bit cleared will be skipped, since this means the view has already
+         * been measured and laid out on this traversal pass naturally. Views won't be added
+         * to this list if layout was already requested when a partial layout is requested
+         * for a view, so there should not be duplicates in the list.
+         */
+        final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+        final boolean didPartialLayout;
+        if (!partialLayoutViews.isEmpty()) {
+            final int count = partialLayoutViews.size();
+            mInLayout = true;
+            for (int i = 0; i < count; i++) {
+                final View view = partialLayoutViews.get(i);
+
+                // Make sure the view is still attached and that it still has layout requested.
+                // We might have already serviced the layout request through the standard full-tree
+                // layout pass above or even through a previous partial layout view in this list.
+                if (view.isAttachedToWindow() && view.isLayoutRequested()) {
+                    final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
+                            MeasureSpec.EXACTLY);
+                    final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
+                            MeasureSpec.EXACTLY);
+                    view.measure(widthSpec, heightSpec);
+                    view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+                }
+            }
+            mInLayout = false;
+            partialLayoutViews.clear();
+            didPartialLayout = true;
+            triggerGlobalLayoutListener = true;
+        } else {
+            didPartialLayout = false;
+        }
+
+        if (didLayout || didPartialLayout) {
             // By this point all views have been sized and positioned
             // We can compute the transparent area
 
@@ -1945,7 +2029,7 @@
 
             if (DBG) {
                 System.out.println("======================================");
-                System.out.println("performTraversals -- after setFrame");
+                System.out.println("performTraversals -- after performLayout/partial layout");
                 host.debug();
             }
         }
@@ -2459,8 +2543,7 @@
                 if (callbacks != null) {
                     for (SurfaceHolder.Callback c : callbacks) {
                         if (c instanceof SurfaceHolder.Callback2) {
-                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
-                                    mSurfaceHolder);
+                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);
                         }
                     }
                 }
@@ -5289,10 +5372,10 @@
                     }
                 }
 
-                // When the drag operation ends, release any local state object
-                // that may have been in use
+                // When the drag operation ends, reset drag-related state
                 if (what == DragEvent.ACTION_DRAG_ENDED) {
                     setLocalDragState(null);
+                    mAttachInfo.mDragToken = null;
                 }
             }
         }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8259372..3c4d45a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -276,6 +276,8 @@
 
     private boolean mDestroyed;
 
+    private boolean mOverlayWithDecorCaption = false;
+
     // The current window attributes.
     private final WindowManager.LayoutParams mWindowAttributes =
         new WindowManager.LayoutParams();
@@ -2044,4 +2046,18 @@
     /** @hide */
     public void setTheme(int resId) {
     }
+
+    /**
+     * Whether the caption should be displayed directly on the content rather than push the content
+     * down. This affects only freeform windows since they display the caption.
+     * @hide
+     */
+    public void setOverlayDecorCaption(boolean overlayCaption) {
+        mOverlayWithDecorCaption = overlayCaption;
+    }
+
+    /** @hide */
+    public boolean getOverlayDecorCaption() {
+        return mOverlayWithDecorCaption;
+    }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2d0435f..1521f2e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,8 +118,7 @@
      */
     public void removeViewImmediate(View view);
 
-    public static class LayoutParams extends ViewGroup.LayoutParams
-            implements Parcelable {
+    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         /**
          * X position for this window.  With the default gravity it is ignored.
          * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
@@ -1148,6 +1147,24 @@
         public static final int PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT = 0x00001000;
 
         /**
+         * Flag indicating that the x, y, width, and height members should be
+         * ignored (and thus their previous value preserved). For example
+         * because they are being managed externally through repositionChild.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000;
+
+        /**
+         * Flag that will make window ignore app visibility and instead depend purely on the decor
+         * view visibility for determining window visibility. This is used by recents to keep
+         * drawing after it launches an app.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 43d643e..c08e1b5 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -77,6 +77,11 @@
     public static final int RELAYOUT_RES_DRAG_RESIZING = 0x8;
 
     /**
+     * The window manager has changed the size of the surface from the last call.
+     */
+    public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x10;
+
+    /**
      * Flag for relayout: the client will be later giving
      * internal insets; as a result, the window will not impact other window
      * layouts until the insets are given.
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 3882bca..89b1eb9 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
@@ -55,9 +56,10 @@
          * Called when the bounds of the screen content that is magnified changed.
          * Note that not the entire screen is magnified.
          *
-         * @param bounds The bounds.
+         * @param magnifiedBounds the currently magnified region
+         * @param availableBounds the region available for magnification
          */
-        public void onMagnifedBoundsChanged(Region bounds);
+        public void onMagnifiedBoundsChanged(Region magnifiedBounds, Region availableBounds);
 
         /**
          * Called when an application requests a rectangle on the screen to allow
@@ -142,7 +144,7 @@
      *
      * @param callbacks The callbacks to invoke.
      */
-    public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks);
+    public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
 
     /**
      * Set by the accessibility layer to specify the magnification and panning to
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ee06806..19a98f3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -23,6 +23,7 @@
 import com.android.internal.view.IInputMethodManager;
 import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
 
 import android.annotation.RequiresPermission;
 import android.content.Context;
@@ -440,33 +441,24 @@
                 }
                 case MSG_UNBIND: {
                     final int sequence = msg.arg1;
+                    @InputMethodClient.UnbindReason
+                    final int reason = msg.arg2;
                     if (DEBUG) {
-                        Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence);
+                        Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence +
+                                " reason=" + InputMethodClient.getUnbindReason(reason));
                     }
-                    boolean startInput = false;
+                    final boolean startInput;
                     synchronized (mH) {
-                        if (mBindSequence == sequence) {
-                            if (false) {
-                                // XXX the server has already unbound!
-                                if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
-                                    try {
-                                        mCurMethod.finishInput();
-                                    } catch (RemoteException e) {
-                                        Log.w(TAG, "IME died: " + mCurId, e);
-                                    }
-                                }
-                            }
-                            clearBindingLocked();
-                            
-                            // If we were actively using the last input method, then
-                            // we would like to re-connect to the next input method.
-                            if (mServedView != null && mServedView.isFocused()) {
-                                mServedConnecting = true;
-                            }
-                            if (mActive) {
-                                startInput = true;
-                            }
+                        if (mBindSequence != sequence) {
+                            return;
                         }
+                        clearBindingLocked();
+                        // If we were actively using the last input method, then
+                        // we would like to re-connect to the next input method.
+                        if (mServedView != null && mServedView.isFocused()) {
+                            mServedConnecting = true;
+                        }
+                        startInput = mActive;
                     }
                     if (startInput) {
                         startInputInner(null, 0, 0, 0);
@@ -548,6 +540,13 @@
         void deactivate() {
             mActive = false;
         }
+
+        @Override
+        public String toString() {
+            return "ControlledInputConnectionWrapper{mActive=" + mActive
+                    + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
+                    + "}";
+        }
     }
     
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
@@ -581,8 +580,8 @@
         }
 
         @Override
-        public void onUnbindMethod(int sequence) {
-            mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
+        public void onUnbindMethod(int sequence, @InputMethodClient.UnbindReason int unbindReason) {
+            mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, unbindReason));
         }
 
         @Override
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b8faf0c..90de053 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2109,6 +2109,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mSelector == null) {
             useDefaultSelector();
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 5eea252..41f1ce7 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -177,8 +177,7 @@
 
     public void onConfigurationChanged(Configuration newConfig) {
         if (!mMaxItemsSet) {
-            mMaxItems = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.max_action_buttons);
+            mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
         }
         if (mMenu != null) {
             mMenu.onItemsChanged(true);
@@ -938,10 +937,11 @@
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             mMenu.close();
             mOverflowPopup = null;
+
+            super.onDismiss();
         }
     }
 
@@ -960,10 +960,11 @@
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             mActionButtonPopup = null;
             mOpenSubMenuId = 0;
+
+            super.onDismiss();
         }
     }
 
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index a018d26..9f94005 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -28,6 +28,8 @@
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
@@ -447,6 +449,68 @@
         return CheckedTextView.class.getName();
     }
 
+    static class SavedState extends BaseSavedState {
+        boolean checked;
+
+        /**
+         * Constructor called from {@link CheckedTextView#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            checked = (Boolean)in.readValue(null);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeValue(checked);
+        }
+
+        @Override
+        public String toString() {
+            return "CheckedTextView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " checked=" + checked + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+
+        ss.checked = isChecked();
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+        setChecked(ss.checked);
+        requestLayout();
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 7e542c9..cbb6109 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -171,7 +171,7 @@
         // Set up year picker view.
         mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker);
         mYearPickerView.setRange(mMinDate, mMaxDate);
-        mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
+        mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
         mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
 
         // Set up content descriptions.
@@ -267,6 +267,9 @@
 
             // Automatically switch to day picker.
             setCurrentView(VIEW_MONTH_DAY);
+
+            // Switch focus back to the year text.
+            mHeaderYear.requestFocus();
         }
     };
 
@@ -344,7 +347,18 @@
                 mAnimator.announceForAccessibility(mSelectDay);
                 break;
             case VIEW_YEAR:
-                mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
+                final int year = mCurrentDate.get(Calendar.YEAR);
+                mYearPickerView.setYear(year);
+                mYearPickerView.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mYearPickerView.requestFocus();
+                        final View selected = mYearPickerView.getSelectedView();
+                        if (selected != null) {
+                            selected.requestFocus();
+                        }
+                    }
+                });
 
                 if (mCurrentView != viewIndex) {
                     mHeaderMonthDay.setActivated(false);
diff --git a/core/java/android/widget/DayPickerPagerAdapter.java b/core/java/android/widget/DayPickerPagerAdapter.java
index 8fe8252..f5840dc 100644
--- a/core/java/android/widget/DayPickerPagerAdapter.java
+++ b/core/java/android/widget/DayPickerPagerAdapter.java
@@ -287,6 +287,14 @@
         return null;
     }
 
+    SimpleMonthView getView(Object object) {
+        if (object == null) {
+            return null;
+        }
+        final ViewHolder holder = (ViewHolder) object;
+        return holder.calendar;
+    }
+
     private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() {
         @Override
         public void onDayClick(SimpleMonthView view, Calendar day) {
diff --git a/core/java/android/widget/DayPickerViewPager.java b/core/java/android/widget/DayPickerViewPager.java
index bb6e3a4..5f0ae29 100644
--- a/core/java/android/widget/DayPickerViewPager.java
+++ b/core/java/android/widget/DayPickerViewPager.java
@@ -16,12 +16,18 @@
 
 package android.widget;
 
-import com.android.internal.widget.ViewPager;
-
+import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import com.android.internal.util.Predicate;
+import com.android.internal.widget.PagerAdapter;
+import com.android.internal.widget.ViewPager;
 
 import java.util.ArrayList;
 
@@ -134,4 +140,37 @@
 
         mMatchParentChildren.clear();
     }
+
+    @Override
+    protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
+        if (predicate.apply(this)) {
+            return this;
+        }
+
+        // Always try the selected view first.
+        final DayPickerPagerAdapter adapter = (DayPickerPagerAdapter) getAdapter();
+        final SimpleMonthView current = adapter.getView(getCurrent());
+        if (current != childToSkip && current != null) {
+            final View v = current.findViewByPredicate(predicate);
+            if (v != null) {
+                return v;
+            }
+        }
+
+        final int len = getChildCount();
+        for (int i = 0; i < len; i++) {
+            final View child = getChildAt(i);
+
+            if (child != childToSkip && child != current) {
+                final View v = child.findViewByPredicate(predicate);
+
+                if (v != null) {
+                    return v;
+                }
+            }
+        }
+
+        return null;
+    }
+
 }
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index d21a5f7..e31bbe9 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -128,6 +128,15 @@
 
     /** @hide */
     @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        if (isEnabled()) {
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
+        }
+    }
+
+    /** @hide */
+    @Override
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
         switch (action) {
             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fabe33..2983053 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2705,11 +2705,22 @@
             mIsShowingUp = false;
         }
 
-        private class SuggestionInfo {
+        private final class SuggestionInfo {
             int suggestionStart, suggestionEnd; // range of actual suggestion within text
-            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
+
+            // the SuggestionSpan that this TextView represents
+            @Nullable
+            SuggestionSpan suggestionSpan;
+
             int suggestionIndex; // the index of this suggestion inside suggestionSpan
-            SpannableStringBuilder text = new SpannableStringBuilder();
+
+            @Nullable
+            final SpannableStringBuilder text = new SpannableStringBuilder();
+
+            void clear() {
+                suggestionSpan = null;
+                text.clear();
+            }
         }
 
         private class SuggestionAdapter extends BaseAdapter {
@@ -2863,9 +2874,11 @@
             return Math.min(positionY, displayMetrics.heightPixels - height);
         }
 
-        @Override
-        public void hide() {
-            super.hide();
+        private void hideWithCleanUp() {
+            for (final SuggestionInfo info : mSuggestionInfos) {
+                info.clear();
+            }
+            hide();
         }
 
         private boolean updateSuggestions() {
@@ -3017,7 +3030,7 @@
                     }
                     mTextView.deleteText_internal(spanUnionStart, spanUnionEnd);
                 }
-                hide();
+                hideWithCleanUp();
                 return;
             }
 
@@ -3025,7 +3038,7 @@
             final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
             if (spanStart < 0 || spanEnd <= spanStart) {
                 // Span has been removed
-                hide();
+                hideWithCleanUp();
                 return;
             }
 
@@ -3100,7 +3113,7 @@
                 mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition);
             }
 
-            hide();
+            hideWithCleanUp();
         }
     }
 
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 280ff15..4d9f55c 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -21,12 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
@@ -36,9 +32,6 @@
 import android.view.ViewHierarchyEncoder;
 import android.widget.RemoteViews.RemoteView;
 
-import com.android.internal.R;
-
-
 /**
  * FrameLayout is designed to block out an area on the screen to display
  * a single item. Generally, FrameLayout should be used to hold a single child view, because it can
@@ -171,6 +164,10 @@
             mPaddingBottom + mForegroundPaddingBottom;
     }
 
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
 
     /**
      * {@inheritDoc}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index ad939be..ba868a1 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.view.ViewParent;
 import com.android.internal.R;
 
 import android.annotation.IntDef;
@@ -37,6 +38,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 /**
  * A Layout that arranges its children in a single column or a single row. The direction of 
@@ -644,6 +647,60 @@
         }
     }
 
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        // This implementation is almost exactly equivalent to the default implementation
+        // offered to the rest of the framework in ViewGroup, but we treat weight to be
+        // functionally equivalent to MATCH_PARENT along the orientation axis.
+
+        if (!checkPartialLayoutParams(child, LayoutParams.class)) return axisFilter;
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (child.didLayoutParamsChange()) {
+            // Anything could have changed about our previous assumptions.
+            return axisFilter;
+        }
+
+        // Our layout can always end up depending on a WRAP_CONTENT child.
+        final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (wrapAxisFilter == axisFilter) {
+            // We know all queried axes are affected, just return early.
+            return wrapAxisFilter;
+        }
+
+        // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+        // that our layout will remain stable within our parent. We need to ask.
+        int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        // For LinearLayout, a nonzero weight is equivalent to MATCH_PARENT for this purpose.
+        if (lp.weight > 0) {
+            if (mOrientation == HORIZONTAL) {
+                matchAxisFilter |= FLAG_LAYOUT_AXIS_HORIZONTAL & axisFilter;
+            } else {
+                matchAxisFilter |= FLAG_LAYOUT_AXIS_VERTICAL & axisFilter;
+            }
+        }
+
+        if (matchAxisFilter != 0) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                // If our parent depends on us for an axis, then our layout can also be affected
+                // by a MATCH_PARENT child along that axis.
+                return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+                        | wrapAxisFilter;
+            }
+
+            // If we don't have a parent, assume we're affected
+            // in any determined affected direction.
+            return matchAxisFilter | wrapAxisFilter;
+        }
+
+        // Two exact sizes and LayoutParams didn't change. We're safe.
+        return 0;
+    }
+
     /**
      * Determines where to position dividers between children.
      *
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 53ca6d1..b43ea76 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1158,7 +1158,7 @@
             final View child = obtainView(0, mIsScrap);
 
             // Lay out child directly against the parent measure spec so that
-            // we can obtain exected minimum width and height.
+            // we can obtain expected minimum width and height.
             measureScrapChild(child, 0, widthMeasureSpec, heightSize);
 
             childWidth = child.getMeasuredWidth();
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 34a8439..027f874 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -19,9 +19,7 @@
 import com.android.internal.R;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPopupHelper;
-import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.ShowableListMenu;
-import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.annotation.MenuRes;
 import android.content.Context;
@@ -33,35 +31,23 @@
 import android.view.View.OnTouchListener;
 
 /**
- * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
- * The popup will appear below the anchor view if there is room, or above it if there is not.
- * If the IME is visible the popup will not overlap it until it is touched. Touching outside
- * of the popup will dismiss it.
+ * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
+ * {@link View}. The popup will appear below the anchor view if there is room,
+ * or above it if there is not. If the IME is visible the popup will not
+ * overlap it until it is touched. Touching outside of the popup will dismiss
+ * it.
  */
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
+public class PopupMenu {
     private final Context mContext;
     private final MenuBuilder mMenu;
     private final View mAnchor;
     private final MenuPopupHelper mPopup;
-    private final boolean mShowCascadingMenus;
 
     private OnMenuItemClickListener mMenuItemClickListener;
-    private OnDismissListener mDismissListener;
+    private OnDismissListener mOnDismissListener;
     private OnTouchListener mDragListener;
 
     /**
-     * Callback interface used to notify the application that the menu has closed.
-     */
-    public interface OnDismissListener {
-        /**
-         * Called when the associated menu has been dismissed.
-         *
-         * @param menu The PopupMenu that was dismissed.
-         */
-        public void onDismiss(PopupMenu menu);
-    }
-
-    /**
      * Constructor to create a new popup menu with an anchor view.
      *
      * @param context Context the popup menu is running in, through which it
@@ -108,14 +94,33 @@
     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
             int popupStyleRes) {
         mContext = context;
-        mShowCascadingMenus = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus);
-        mMenu = new MenuBuilder(context);
-        mMenu.setCallback(this);
         mAnchor = anchor;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                if (mMenuItemClickListener != null) {
+                    return mMenuItemClickListener.onMenuItemClick(item);
+                }
+                return false;
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {
+            }
+        });
+
         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
-        mPopup.setCallback(this);
+        mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+            @Override
+            public void onDismiss() {
+                if (mOnDismissListener != null) {
+                    mOnDismissListener.onDismiss(PopupMenu.this);
+                }
+            }
+        });
     }
 
     /**
@@ -125,7 +130,6 @@
      * the next time the popup is shown.
      *
      * @param gravity the gravity used to align the popup window
-     *
      * @see #getGravity()
      */
     public void setGravity(int gravity) {
@@ -134,7 +138,6 @@
 
     /**
      * @return the gravity used to align the popup window to its anchor view
-     *
      * @see #setGravity(int)
      */
     public int getGravity() {
@@ -146,8 +149,8 @@
      * to implement drag-to-open behavior.
      * <p>
      * When the listener is set on a view, touching that view and dragging
-     * outside of its bounds will open the popup window. Lifting will select the
-     * currently touched list item.
+     * outside of its bounds will open the popup window. Lifting will select
+     * the currently touched list item.
      * <p>
      * Example usage:
      * <pre>
@@ -184,9 +187,10 @@
     }
 
     /**
-     * @return the {@link Menu} associated with this popup. Populate the returned Menu with
-     * items before calling {@link #show()}.
+     * Returns the {@link Menu} associated with this popup. Populate the
+     * returned Menu with items before calling {@link #show()}.
      *
+     * @return the {@link Menu} associated with this popup
      * @see #show()
      * @see #getMenuInflater()
      */
@@ -195,9 +199,8 @@
     }
 
     /**
-     * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
-     * menu returned by {@link #getMenu()}.
-     *
+     * @return a {@link MenuInflater} that can be used to inflate menu items
+     *         from XML into the menu returned by {@link #getMenu()}
      * @see #getMenu()
      */
     public MenuInflater getMenuInflater() {
@@ -205,8 +208,9 @@
     }
 
     /**
-     * Inflate a menu resource into this PopupMenu. This is equivalent to calling
-     * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+     * Inflate a menu resource into this PopupMenu. This is equivalent to
+     * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+     *
      * @param menuRes Menu resource to inflate
      */
     public void inflate(@MenuRes int menuRes) {
@@ -215,6 +219,7 @@
 
     /**
      * Show the menu popup anchored to the view specified during construction.
+     *
      * @see #dismiss()
      */
     public void show() {
@@ -223,6 +228,7 @@
 
     /**
      * Dismiss the menu popup.
+     *
      * @see #show()
      */
     public void dismiss() {
@@ -230,68 +236,49 @@
     }
 
     /**
-     * Set a listener that will be notified when the user selects an item from the menu.
+     * Sets a listener that will be notified when the user selects an item from
+     * the menu.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
         mMenuItemClickListener = listener;
     }
 
     /**
-     * Set a listener that will be notified when this menu is dismissed.
+     * Sets a listener that will be notified when this menu is dismissed.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnDismissListener(OnDismissListener listener) {
-        mDismissListener = listener;
+        mOnDismissListener = listener;
     }
 
     /**
-     * @hide
-     */
-    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        if (mMenuItemClickListener != null) {
-            return mMenuItemClickListener.onMenuItemClick(item);
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        if (mDismissListener != null) {
-            mDismissListener.onDismiss(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean onOpenSubMenu(MenuBuilder subMenu) {
-        // The menu presenter will handle opening the submenu itself. Nothing to do here.
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onMenuModeChange(MenuBuilder menu) {
-    }
-
-    /**
-     * Interface responsible for receiving menu item click events if the items themselves
-     * do not have individual item click listeners.
+     * Interface responsible for receiving menu item click events if the items
+     * themselves do not have individual item click listeners.
      */
     public interface OnMenuItemClickListener {
         /**
-         * This method will be invoked when a menu item is clicked if the item itself did
-         * not already handle the event.
+         * This method will be invoked when a menu item is clicked if the item
+         * itself did not already handle the event.
          *
-         * @param item {@link MenuItem} that was clicked
-         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         * @param item the menu item that was clicked
+         * @return {@code true} if the event was handled, {@code false}
+         *         otherwise
          */
-        public boolean onMenuItemClick(MenuItem item);
+        boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Callback interface used to notify the application that the menu has closed.
+     */
+    public interface OnDismissListener {
+        /**
+         * Called when the associated menu has been dismissed.
+         *
+         * @param menu the popup menu that was dismissed
+         */
+        void onDismiss(PopupMenu menu);
     }
 }
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index e9325ef..6edce91 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -34,8 +35,10 @@
 import android.util.IntArray;
 import android.util.MathUtils;
 import android.util.StateSet;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -61,11 +64,14 @@
     private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
     private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
 
+    private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0;
+
     private final TextPaint mMonthPaint = new TextPaint();
     private final TextPaint mDayOfWeekPaint = new TextPaint();
     private final TextPaint mDayPaint = new TextPaint();
     private final Paint mDaySelectorPaint = new Paint();
     private final Paint mDayHighlightPaint = new Paint();
+    private final Paint mDayHighlightSelectorPaint = new Paint();
 
     private final Calendar mCalendar = Calendar.getInstance();
     private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();
@@ -130,7 +136,9 @@
 
     private ColorStateList mDayTextColor;
 
-    private int mTouchedItem = -1;
+    private int mHighlightedDay = -1;
+    private int mPreviouslyHighlightedDay = -1;
+    private boolean mIsTouchHighlighted = false;
 
     public SimpleMonthView(Context context) {
         this(context, null);
@@ -268,6 +276,9 @@
         mDayHighlightPaint.setAntiAlias(true);
         mDayHighlightPaint.setStyle(Style.FILL);
 
+        mDayHighlightSelectorPaint.setAntiAlias(true);
+        mDayHighlightSelectorPaint.setStyle(Style.FILL);
+
         mDayPaint.setAntiAlias(true);
         mDayPaint.setTextSize(dayTextSize);
         mDayPaint.setTypeface(Typeface.create(dayTypeface, 0));
@@ -296,6 +307,8 @@
         final int activatedColor = dayBackgroundColor.getColorForState(
                 StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
         mDaySelectorPaint.setColor(activatedColor);
+        mDayHighlightSelectorPaint.setColor(activatedColor);
+        mDayHighlightSelectorPaint.setAlpha(SELECTED_HIGHLIGHT_ALPHA);
         invalidate();
     }
 
@@ -326,8 +339,10 @@
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
                 final int touchedItem = getDayAtLocation(x, y);
-                if (mTouchedItem != touchedItem) {
-                    mTouchedItem = touchedItem;
+                mIsTouchHighlighted = true;
+                if (mHighlightedDay != touchedItem) {
+                    mHighlightedDay = touchedItem;
+                    mPreviouslyHighlightedDay = touchedItem;
                     invalidate();
                 }
                 if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) {
@@ -342,7 +357,8 @@
                 // Fall through.
             case MotionEvent.ACTION_CANCEL:
                 // Reset touched day on stream end.
-                mTouchedItem = -1;
+                mHighlightedDay = -1;
+                mIsTouchHighlighted = false;
                 invalidate();
                 break;
         }
@@ -350,6 +366,228 @@
     }
 
     @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // We need to handle focus change within the SimpleMonthView because we are simulating
+        // multiple Views. The arrow keys will move between days until there is no space (no
+        // day to the left, top, right, or bottom). Focus forward and back jumps out of the
+        // SimpleMonthView, skipping over other SimpleMonthViews in the parent ViewPager
+        // to the next focusable View in the hierarchy.
+        boolean focusChanged = false;
+        switch (event.getKeyCode()) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (event.hasNoModifiers()) {
+                    focusChanged = moveOneDay(isLayoutRtl());
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (event.hasNoModifiers()) {
+                    focusChanged = moveOneDay(!isLayoutRtl());
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (event.hasNoModifiers()) {
+                    ensureFocusedDay();
+                    if (mHighlightedDay > 7) {
+                        mHighlightedDay -= 7;
+                        focusChanged = true;
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (event.hasNoModifiers()) {
+                    ensureFocusedDay();
+                    if (mHighlightedDay <= mDaysInMonth - 7) {
+                        mHighlightedDay += 7;
+                        focusChanged = true;
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+                if (mHighlightedDay != -1) {
+                    onDayClicked(mHighlightedDay);
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_TAB: {
+                int focusChangeDirection = 0;
+                if (event.hasNoModifiers()) {
+                    focusChangeDirection = View.FOCUS_FORWARD;
+                } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+                    focusChangeDirection = View.FOCUS_BACKWARD;
+                }
+                if (focusChangeDirection != 0) {
+                    final ViewParent parent = getParent();
+                    // move out of the ViewPager next/previous
+                    View nextFocus = this;
+                    do {
+                        nextFocus = nextFocus.focusSearch(focusChangeDirection);
+                    } while (nextFocus != null && nextFocus != this &&
+                            nextFocus.getParent() == parent);
+                    if (nextFocus != null) {
+                        nextFocus.requestFocus();
+                        return true;
+                    }
+                }
+                break;
+            }
+        }
+        if (focusChanged) {
+            invalidate();
+            return true;
+        } else {
+            return super.onKeyDown(keyCode, event);
+        }
+    }
+
+    private boolean moveOneDay(boolean positive) {
+        ensureFocusedDay();
+        boolean focusChanged = false;
+        if (positive) {
+            if (!isLastDayOfWeek(mHighlightedDay) && mHighlightedDay < mDaysInMonth) {
+                mHighlightedDay++;
+                focusChanged = true;
+            }
+        } else {
+            if (!isFirstDayOfWeek(mHighlightedDay) && mHighlightedDay > 1) {
+                mHighlightedDay--;
+                focusChanged = true;
+            }
+        }
+        return focusChanged;
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
+            @Nullable Rect previouslyFocusedRect) {
+        if (gainFocus) {
+            // If we've gained focus through arrow keys, we should find the day closest
+            // to the focus rect. If we've gained focus through forward/back, we should
+            // focus on the selected day if there is one.
+            final int offset = findDayOffset();
+            switch(direction) {
+                case View.FOCUS_RIGHT: {
+                    int row = findClosestRow(previouslyFocusedRect);
+                    mHighlightedDay = row == 0 ? 1 : (row * DAYS_IN_WEEK) - offset + 1;
+                    break;
+                }
+                case View.FOCUS_LEFT: {
+                    int row = findClosestRow(previouslyFocusedRect) + 1;
+                    mHighlightedDay = Math.min(mDaysInMonth, (row * DAYS_IN_WEEK) - offset);
+                    break;
+                }
+                case View.FOCUS_DOWN: {
+                    final int col = findClosestColumn(previouslyFocusedRect);
+                    final int day = col - offset + 1;
+                    mHighlightedDay = day < 1 ? day + DAYS_IN_WEEK : day;
+                    break;
+                }
+                case View.FOCUS_UP: {
+                    final int col = findClosestColumn(previouslyFocusedRect);
+                    final int maxWeeks = (offset + mDaysInMonth) / DAYS_IN_WEEK;
+                    final int day = col - offset + (DAYS_IN_WEEK * maxWeeks) + 1;
+                    mHighlightedDay = day > mDaysInMonth ? day - DAYS_IN_WEEK : day;
+                    break;
+                }
+            }
+            ensureFocusedDay();
+            invalidate();
+        }
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+    }
+
+    /**
+     * Returns the row (0 indexed) closest to previouslyFocusedRect or center if null.
+     */
+    private int findClosestRow(@Nullable Rect previouslyFocusedRect) {
+        if (previouslyFocusedRect == null) {
+            return 3;
+        } else {
+            int centerY = previouslyFocusedRect.centerY();
+
+            final TextPaint p = mDayPaint;
+            final int headerHeight = mMonthHeight + mDayOfWeekHeight;
+            final int rowHeight = mDayHeight;
+
+            // Text is vertically centered within the row height.
+            final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
+            final int rowCenter = headerHeight + rowHeight / 2;
+
+            centerY -= rowCenter - halfLineHeight;
+            int row = Math.round(centerY / (float) rowHeight);
+            final int maxDay = findDayOffset() + mDaysInMonth;
+            final int maxRows = (maxDay / DAYS_IN_WEEK) - ((maxDay % DAYS_IN_WEEK == 0) ? 1 : 0);
+
+            row = MathUtils.constrain(row, 0, maxRows);
+            return row;
+        }
+    }
+
+    /**
+     * Returns the column (0 indexed) closest to the previouslyFocusedRect or center if null.
+     * The 0 index is related to the first day of the week.
+     */
+    private int findClosestColumn(@Nullable Rect previouslyFocusedRect) {
+        if (previouslyFocusedRect == null) {
+            return DAYS_IN_WEEK / 2;
+        } else {
+            int centerX = previouslyFocusedRect.centerX() - mPaddingLeft;
+            final int columnFromLeft =
+                    MathUtils.constrain(centerX / mCellWidth, 0, DAYS_IN_WEEK - 1);
+            return isLayoutRtl() ? DAYS_IN_WEEK - columnFromLeft - 1: columnFromLeft;
+        }
+    }
+
+    @Override
+    public void getFocusedRect(Rect r) {
+        if (mHighlightedDay > 0) {
+            getBoundsForDay(mHighlightedDay, r);
+        } else {
+            super.getFocusedRect(r);
+        }
+    }
+
+    @Override
+    protected void onFocusLost() {
+        if (!mIsTouchHighlighted) {
+            // Unhighlight a day.
+            mPreviouslyHighlightedDay = mHighlightedDay;
+            mHighlightedDay = -1;
+            invalidate();
+        }
+        super.onFocusLost();
+    }
+
+    /**
+     * Ensure some day is highlighted. If a day isn't highlighted, it chooses the selected day,
+     * if possible, or the first day of the month if not.
+     */
+    private void ensureFocusedDay() {
+        if (mHighlightedDay != -1) {
+            return;
+        }
+        if (mPreviouslyHighlightedDay != -1) {
+            mHighlightedDay = mPreviouslyHighlightedDay;
+            return;
+        }
+        if (mActivatedDay != -1) {
+            mHighlightedDay = mActivatedDay;
+            return;
+        }
+        mHighlightedDay = 1;
+    }
+
+    private boolean isFirstDayOfWeek(int day) {
+        final int offset = findDayOffset();
+        return (offset + day - 1) % DAYS_IN_WEEK == 0;
+    }
+
+    private boolean isLastDayOfWeek(int day) {
+        final int offset = findDayOffset();
+        return (offset + day) % DAYS_IN_WEEK == 0;
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         final int paddingLeft = getPaddingLeft();
         final int paddingTop = getPaddingTop();
@@ -432,12 +670,15 @@
             }
 
             final boolean isDayActivated = mActivatedDay == day;
+            final boolean isDayHighlighted = mHighlightedDay == day;
             if (isDayActivated) {
                 stateMask |= StateSet.VIEW_STATE_ACTIVATED;
 
                 // Adjust the circle to be centered on the row.
-                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
-            } else if (mTouchedItem == day) {
+                final Paint paint = isDayHighlighted ? mDayHighlightSelectorPaint :
+                        mDaySelectorPaint;
+                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, paint);
+            } else if (isDayHighlighted) {
                 stateMask |= StateSet.VIEW_STATE_PRESSED;
 
                 if (isDayEnabled) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 13b1c4b..eaf4fe2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -310,6 +310,14 @@
      */
     static final int PROCESS_TEXT_REQUEST_CODE = 100;
 
+    /**
+     *  Return code of {@link #doKeyDown}.
+     */
+    private static final int KEY_EVENT_NOT_HANDLED = 0;
+    private static final int KEY_EVENT_HANDLED = -1;
+    private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
+    private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
+
     // System wide time for last cut, copy or text changed action.
     static long sLastCutCopyOrTextChangedTime;
 
@@ -3358,10 +3366,17 @@
     }
 
     /**
-     * Sets whether the movement method will automatically be set to
-     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
-     * set to nonzero and links are detected in {@link #setText}.
-     * The default is true.
+     * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
+     * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
+     * following is true:
+     * <ul>
+     * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
+     * {@link #setText} or {@link #append}.
+     * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
+     * </ul>
+     *
+     * <p>This function does not have an immediate effect, movement method will be set only after a
+     * call to {@link #setText} or {@link #append}. The default is true.</p>
      *
      * @attr ref android.R.styleable#TextView_linksClickable
      */
@@ -3371,10 +3386,14 @@
     }
 
     /**
-     * Returns whether the movement method will automatically be set to
-     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
-     * set to nonzero and links are detected in {@link #setText}.
-     * The default is true.
+     * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
+     * after {@link #setText} or {@link #append} is called.
+     *
+     * See {@link #setLinksClickable} for details.
+     *
+     * <p>The default is true.</p>
+     *
+     * @see #setLinksClickable
      *
      * @attr ref android.R.styleable#TextView_linksClickable
      */
@@ -3967,6 +3986,21 @@
         }
 
         ((Editable) mText).append(text, start, end);
+
+        boolean hasClickableSpans = false;
+        if (mAutoLinkMask != 0) {
+            hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+        } else if (mLinksClickable && text instanceof Spanned) {
+            ClickableSpan[] clickableSpans =
+                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+            hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
+        }
+
+        // Do not change the movement method for text that supports text selection as it
+        // would prevent an arbitrary cursor displacement.
+        if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
+            setMovementMethod(LinkMovementMethod.getInstance());
+        }
     }
 
     private void updateTextColors() {
@@ -4310,6 +4344,7 @@
             text = TextUtils.stringOrSpannedString(text);
         }
 
+        boolean hasClickableSpans = false;
         if (mAutoLinkMask != 0) {
             Spannable s2;
 
@@ -4319,22 +4354,32 @@
                 s2 = mSpannableFactory.newSpannable(text);
             }
 
-            if (Linkify.addLinks(s2, mAutoLinkMask)) {
+            hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
+            if (hasClickableSpans) {
                 text = s2;
-                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
+            }
+        } else if (mLinksClickable && text instanceof Spanned) {
+            ClickableSpan[] clickableSpans =
+                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+            hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
+            if (hasClickableSpans && !(text instanceof Spannable)) {
+                text = mSpannableFactory.newSpannable(text);
+            }
+        }
 
-                /*
-                 * We must go ahead and set the text before changing the
-                 * movement method, because setMovementMethod() may call
-                 * setText() again to try to upgrade the buffer type.
-                 */
-                mText = text;
+        if (hasClickableSpans) {
+            type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
+            /*
+             * We must go ahead and set the text before changing the
+             * movement method, because setMovementMethod() may call
+             * setText() again to try to upgrade the buffer type.
+             */
+            mText = text;
 
-                // Do not change the movement method for text that support text selection as it
-                // would prevent an arbitrary cursor displacement.
-                if (mLinksClickable && !textCanBeSelected()) {
-                    setMovementMethod(LinkMovementMethod.getInstance());
-                }
+            // Do not change the movement method for text that supports text selection as it
+            // would prevent an arbitrary cursor displacement.
+            if (mLinksClickable && !textCanBeSelected()) {
+                setMovementMethod(LinkMovementMethod.getInstance());
             }
         }
 
@@ -5955,8 +6000,8 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        int which = doKeyDown(keyCode, event, null);
-        if (which == 0) {
+        final int which = doKeyDown(keyCode, event, null);
+        if (which == KEY_EVENT_NOT_HANDLED) {
             return super.onKeyDown(keyCode, event);
         }
 
@@ -5966,13 +6011,12 @@
     @Override
     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
-
-        int which = doKeyDown(keyCode, down, event);
-        if (which == 0) {
+        final int which = doKeyDown(keyCode, down, event);
+        if (which == KEY_EVENT_NOT_HANDLED) {
             // Go through default dispatching.
             return super.onKeyMultiple(keyCode, repeatCount, event);
         }
-        if (which == -1) {
+        if (which == KEY_EVENT_HANDLED) {
             // Consumed the whole thing.
             return true;
         }
@@ -5985,7 +6029,7 @@
         // It would be nice if those interfaces had an onKeyMultiple() method,
         // but adding that is a more complicated change.
         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
-        if (which == 1) {
+        if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
             // mEditor and mEditor.mInput are not null from doKeyDown
             mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
             while (--repeatCount > 0) {
@@ -5994,7 +6038,7 @@
             }
             hideErrorIfUnchanged();
 
-        } else if (which == 2) {
+        } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
             // mMovement is not null from doKeyDown
             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
             while (--repeatCount > 0) {
@@ -6051,7 +6095,7 @@
 
     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
         if (!isEnabled()) {
-            return 0;
+            return KEY_EVENT_NOT_HANDLED;
         }
 
         // If this is the initial keydown, we don't want to prevent a movement away from this view.
@@ -6078,7 +6122,7 @@
                                 this, EditorInfo.IME_NULL, event)) {
                             mEditor.mInputContentType.enterDown = true;
                             // We are consuming the enter key for them.
-                            return -1;
+                            return KEY_EVENT_HANDLED;
                         }
                     }
 
@@ -6088,9 +6132,9 @@
                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
                             || shouldAdvanceFocusOnEnter()) {
                         if (hasOnClickListeners()) {
-                            return 0;
+                            return KEY_EVENT_NOT_HANDLED;
                         }
-                        return -1;
+                        return KEY_EVENT_HANDLED;
                     }
                 }
                 break;
@@ -6098,7 +6142,7 @@
             case KeyEvent.KEYCODE_DPAD_CENTER:
                 if (event.hasNoModifiers()) {
                     if (shouldAdvanceFocusOnEnter()) {
-                        return 0;
+                        return KEY_EVENT_NOT_HANDLED;
                     }
                 }
                 break;
@@ -6106,7 +6150,7 @@
             case KeyEvent.KEYCODE_TAB:
                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                     if (shouldAdvanceFocusOnTab()) {
-                        return 0;
+                        return KEY_EVENT_NOT_HANDLED;
                     }
                 }
                 break;
@@ -6115,7 +6159,31 @@
             case KeyEvent.KEYCODE_BACK:
                 if (mEditor != null && mEditor.mTextActionMode != null) {
                     stopTextActionMode();
-                    return -1;
+                    return KEY_EVENT_HANDLED;
+                }
+                break;
+
+            case KeyEvent.KEYCODE_CUT:
+                if (event.hasNoModifiers() && canCut()) {
+                    if (onTextContextMenuItem(ID_CUT)) {
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
+
+            case KeyEvent.KEYCODE_COPY:
+                if (event.hasNoModifiers() && canCopy()) {
+                    if (onTextContextMenuItem(ID_COPY)) {
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
+
+            case KeyEvent.KEYCODE_PASTE:
+                if (event.hasNoModifiers() && canPaste()) {
+                    if (onTextContextMenuItem(ID_PASTE)) {
+                        return KEY_EVENT_HANDLED;
+                    }
                 }
                 break;
         }
@@ -6130,7 +6198,7 @@
                     hideErrorIfUnchanged();
                     doDown = false;
                     if (handled) {
-                        return -1;
+                        return KEY_EVENT_HANDLED;
                     }
                 } catch (AbstractMethodError e) {
                     // onKeyOther was added after 1.0, so if it isn't
@@ -6146,7 +6214,7 @@
                         keyCode, event);
                 endBatchEdit();
                 hideErrorIfUnchanged();
-                if (handled) return 1;
+                if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
             }
         }
 
@@ -6161,7 +6229,7 @@
                             otherEvent);
                     doDown = false;
                     if (handled) {
-                        return -1;
+                        return KEY_EVENT_HANDLED;
                     }
                 } catch (AbstractMethodError e) {
                     // onKeyOther was added after 1.0, so if it isn't
@@ -6173,12 +6241,13 @@
                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
                         mPreventDefaultMovement = true;
                     }
-                    return 2;
+                    return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
                 }
             }
         }
 
-        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
+        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
+                KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
     }
 
     /**
@@ -6732,10 +6801,11 @@
 
         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
             if (!compressText(ellipsisWidth)) {
-                final int height = mLayoutParams.height;
                 // If the size of the view does not depend on the size of the text, try to
                 // start the marquee immediately
-                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
+                final ViewParent parent = getParent();
+                if (parent != null && parent.findDependentLayoutAxes(this,
+                        ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) {
                     startMarquee();
                 } else {
                     // Defer the start of the marquee until we know our width (see setFrame())
@@ -7131,37 +7201,9 @@
      * new view layout.
      */
     private void checkForResize() {
-        boolean sizeChanged = false;
-
-        if (mLayout != null) {
-            // Check if our width changed
-            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
-                sizeChanged = true;
-                invalidate();
-            }
-
-            // Check if our height changed
-            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
-                int desiredHeight = getDesiredHeight();
-
-                if (desiredHeight != this.getHeight()) {
-                    sizeChanged = true;
-                }
-            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
-                if (mDesiredHeightAtMeasure >= 0) {
-                    int desiredHeight = getDesiredHeight();
-
-                    if (desiredHeight != mDesiredHeightAtMeasure) {
-                        sizeChanged = true;
-                    }
-                }
-            }
-        }
-
-        if (sizeChanged) {
-            requestLayout();
-            // caller will have already invalidated
-        }
+        // Always request a layout. The parent will perform the correct version
+        // of the intended optimizations as part of requestLayoutForChild.
+        requestLayout();
     }
 
     /**
@@ -7169,56 +7211,10 @@
      * or merely a new text layout.
      */
     private void checkForRelayout() {
-        // If we have a fixed width, we can just swap in a new text layout
-        // if the text height stays the same or if the view height is fixed.
-
-        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
-                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
-                (mHint == null || mHintLayout != null) &&
-                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
-            // Static width, so try making a new text layout.
-
-            int oldht = mLayout.getHeight();
-            int want = mLayout.getWidth();
-            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
-
-            /*
-             * No need to bring the text into view, since the size is not
-             * changing (unless we do the requestLayout(), in which case it
-             * will happen at measure).
-             */
-            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
-                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
-                          false);
-
-            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
-                // In a fixed-height view, so use our new text layout.
-                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
-                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
-                    invalidate();
-                    return;
-                }
-
-                // Dynamic height, but height has stayed the same,
-                // so use our new text layout.
-                if (mLayout.getHeight() == oldht &&
-                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
-                    invalidate();
-                    return;
-                }
-            }
-
-            // We lose: the height has changed and we have a dynamic height.
-            // Request a new view layout using our new text layout.
-            requestLayout();
-            invalidate();
-        } else {
-            // Dynamic width, so we have no choice but to request a new
-            // view layout with a new text layout.
-            nullLayouts();
-            requestLayout();
-            invalidate();
-        }
+        // Always request a layout. The parent will perform the correct version
+        // of the intended optimizations as part of requestLayoutForChild.
+        nullLayouts();
+        requestLayout();
     }
 
     @Override
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e5af79..a24d37f 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -100,7 +100,7 @@
      * @see #getHour()
      */
     public void setHour(int hour) {
-        mDelegate.setCurrentHour(hour);
+        mDelegate.setHour(hour);
     }
 
     /**
@@ -110,7 +110,7 @@
      * @see #setHour(int)
      */
     public int getHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -120,7 +120,7 @@
      * @see #getMinute()
      */
     public void setMinute(int minute) {
-        mDelegate.setCurrentMinute(minute);
+        mDelegate.setMinute(minute);
     }
 
     /**
@@ -130,7 +130,7 @@
      * @see #setMinute(int)
      */
     public int getMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -150,7 +150,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -160,7 +160,7 @@
      */
     @Deprecated
     public void setCurrentMinute(@NonNull Integer currentMinute) {
-        mDelegate.setCurrentMinute(currentMinute);
+        mDelegate.setMinute(currentMinute);
     }
 
     /**
@@ -170,7 +170,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -186,7 +186,7 @@
             return;
         }
 
-        mDelegate.setIs24HourView(is24HourView);
+        mDelegate.setIs24Hour(is24HourView);
     }
 
     /**
@@ -195,7 +195,7 @@
      * @see #setIs24HourView(Boolean)
      */
     public boolean is24HourView() {
-        return mDelegate.is24HourView();
+        return mDelegate.is24Hour();
     }
 
     /**
@@ -207,16 +207,6 @@
         mDelegate.setOnTimeChangedListener(onTimeChangedListener);
     }
 
-    /**
-     * Sets the callback that indicates the current time is valid.
-     *
-     * @param callback the callback, may be null
-     * @hide
-     */
-    public void setValidationCallback(@Nullable ValidationCallback callback) {
-        mDelegate.setValidationCallback(callback);
-    }
-
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
@@ -234,12 +224,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mDelegate.onConfigurationChanged(newConfig);
-    }
-
-    @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
         return mDelegate.onSaveInstanceState(superState);
@@ -269,25 +253,22 @@
      * for the real behavior.
      */
     interface TimePickerDelegate {
-        void setCurrentHour(int currentHour);
-        int getCurrentHour();
+        void setHour(int hour);
+        int getHour();
 
-        void setCurrentMinute(int currentMinute);
-        int getCurrentMinute();
+        void setMinute(int minute);
+        int getMinute();
 
-        void setIs24HourView(boolean is24HourView);
-        boolean is24HourView();
+        void setIs24Hour(boolean is24Hour);
+        boolean is24Hour();
 
         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
-        void setValidationCallback(ValidationCallback callback);
 
         void setEnabled(boolean enabled);
         boolean isEnabled();
 
         int getBaseline();
 
-        void onConfigurationChanged(Configuration newConfig);
-
         Parcelable onSaveInstanceState(Parcelable superState);
         void onRestoreInstanceState(Parcelable state);
 
@@ -295,16 +276,6 @@
         void onPopulateAccessibilityEvent(AccessibilityEvent event);
     }
 
-    /**
-     * A callback interface for updating input validity when the TimePicker
-     * when included into a Dialog.
-     *
-     * @hide
-     */
-    public static interface ValidationCallback {
-        void onValidationChanged(boolean valid);
-    }
-
     static String[] getAmPmStrings(Context context) {
         final Locale locale = context.getResources().getConfiguration().locale;
         final LocaleData d = LocaleData.get(locale);
@@ -319,43 +290,16 @@
      * An abstract class which can be used as a start for TimePicker implementations
      */
     abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
-        // The delegator
-        protected TimePicker mDelegator;
+        protected final TimePicker mDelegator;
+        protected final Context mContext;
+        protected final Locale mLocale;
 
-        // The context
-        protected Context mContext;
-
-        // The current locale
-        protected Locale mCurrentLocale;
-
-        // Callbacks
         protected OnTimeChangedListener mOnTimeChangedListener;
-        protected ValidationCallback mValidationCallback;
 
-        public AbstractTimePickerDelegate(TimePicker delegator, Context context) {
+        public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
             mDelegator = delegator;
             mContext = context;
-
-            // initialization based on locale
-            setCurrentLocale(Locale.getDefault());
-        }
-
-        public void setCurrentLocale(Locale locale) {
-            if (locale.equals(mCurrentLocale)) {
-                return;
-            }
-            mCurrentLocale = locale;
-        }
-
-        @Override
-        public void setValidationCallback(ValidationCallback callback) {
-            mValidationCallback = callback;
-        }
-
-        protected void onValidationChanged(boolean valid) {
-            if (mValidationCallback != null) {
-                mValidationCallback.onValidationChanged(valid);
-            }
+            mLocale = context.getResources().getConfiguration().locale;
         }
     }
 }
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 4dc5fd3e..a24fb40 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Parcel;
@@ -85,11 +84,13 @@
     private final RadialTimePickerView mRadialTimePickerView;
     private final TextView mSeparatorView;
 
+    private final Calendar mTempCalendar;
+
     private boolean mIsEnabled = true;
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
     private int mInitialMinute;
-    private boolean mIs24HourView;
+    private boolean mIs24Hour;
     private boolean mIsAmPmAtStart;
 
     // Accessibility strings.
@@ -104,8 +105,6 @@
     private CharSequence mLastAnnouncedText;
     private boolean mLastAnnouncedIsHour;
 
-    private Calendar mTempCalendar;
-
     public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(delegator, context);
@@ -199,20 +198,13 @@
 
         mAllowAutoAdvance = true;
 
-        // Updates mHourFormat variables used below.
-        updateHourFormat(mCurrentLocale, mIs24HourView);
-
-        // Update hour text field.
-        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
-        final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
-        mHourView.setRange(minHour, maxHour);
-        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+        updateHourFormat();
 
         // Initialize with current time.
-        mTempCalendar = Calendar.getInstance(mCurrentLocale);
+        mTempCalendar = Calendar.getInstance(mLocale);
         final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
         final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
-        initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+        initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX);
     }
 
     /**
@@ -233,15 +225,14 @@
     }
 
     /**
-     * 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
+     * Updates hour formatting based on the current locale and 24-hour mode.
+     * <p>
+     * Determines how the hour should be formatted, sets member variables for
+     * leading zero and starting hour, and sets the hour view's presentation.
      */
-    private void updateHourFormat(Locale locale, boolean is24Hour) {
+    private void updateHourFormat() {
         final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
-                locale, is24Hour ? "Hm" : "hm");
+                mLocale, mIs24Hour ? "Hm" : "hm");
         final int lengthPattern = bestDateTimePattern.length();
         boolean showLeadingZero = false;
         char hourFormat = '\0';
@@ -259,6 +250,12 @@
 
         mHourFormatShowLeadingZero = showLeadingZero;
         mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
+
+        // Update hour text field.
+        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+        final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
+        mHourView.setRange(minHour, maxHour);
+        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
     }
 
     private static final CharSequence obtainVerbatim(String text) {
@@ -333,7 +330,7 @@
     private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
         mInitialHourOfDay = hourOfDay;
         mInitialMinute = minute;
-        mIs24HourView = is24HourView;
+        mIs24Hour = is24HourView;
         updateUI(index);
     }
 
@@ -352,17 +349,16 @@
     }
 
     private void updateRadialPicker(int index) {
-        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24Hour);
         setCurrentItemShowing(index, false, true);
     }
 
     private void updateHeaderAmPm() {
-
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             mAmPmLayout.setVisibility(View.GONE);
         } else {
             // Ensure that AM/PM layout is in the correct position.
-            final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
+            final String dateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm");
             final boolean isAmPmAtStart = dateTimePattern.startsWith("a");
             setAmPmAtStart(isAmPmAtStart);
 
@@ -395,35 +391,32 @@
      * Set the current hour.
      */
     @Override
-    public void setCurrentHour(int currentHour) {
-        if (mInitialHourOfDay == currentHour) {
-            return;
+    public void setHour(int hour) {
+        if (mInitialHourOfDay != hour) {
+            mInitialHourOfDay = hour;
+            updateHeaderHour(hour, true);
+            updateHeaderAmPm();
+            mRadialTimePickerView.setCurrentHour(hour);
+            mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialHourOfDay = currentHour;
-        updateHeaderHour(currentHour, true);
-        updateHeaderAmPm();
-        mRadialTimePickerView.setCurrentHour(currentHour);
-        mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
-     * @return The current hour in the range (0-23).
+     * @return the current hour in the range (0-23)
      */
     @Override
-    public int getCurrentHour() {
-        int currentHour = mRadialTimePickerView.getCurrentHour();
-        if (mIs24HourView) {
+    public int getHour() {
+        final int currentHour = mRadialTimePickerView.getCurrentHour();
+        if (mIs24Hour) {
             return currentHour;
+        }
+
+        if (mRadialTimePickerView.getAmOrPm() == PM) {
+            return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
         } else {
-            switch(mRadialTimePickerView.getAmOrPm()) {
-                case PM:
-                    return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
-                case AM:
-                default:
-                    return currentHour % HOURS_IN_HALF_DAY;
-            }
+            return currentHour % HOURS_IN_HALF_DAY;
         }
     }
 
@@ -431,48 +424,49 @@
      * Set the current minute (0-59).
      */
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (mInitialMinute == currentMinute) {
-            return;
+    public void setMinute(int minute) {
+        if (mInitialMinute != minute) {
+            mInitialMinute = minute;
+            updateHeaderMinute(minute, true);
+            mRadialTimePickerView.setCurrentMinute(minute);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialMinute = currentMinute;
-        updateHeaderMinute(currentMinute, true);
-        mRadialTimePickerView.setCurrentMinute(currentMinute);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
      * @return The current minute.
      */
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mRadialTimePickerView.getCurrentMinute();
     }
 
     /**
-     * Set whether in 24 hour or AM/PM mode.
+     * Sets whether time is displayed in 24-hour mode or 12-hour mode with
+     * AM/PM indicators.
      *
-     * @param is24HourView True = 24 hour mode. False = AM/PM.
+     * @param is24Hour {@code true} to display time in 24-hour mode or
+     *        {@code false} for 12-hour mode with AM/PM
      */
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (is24HourView == mIs24HourView) {
-            return;
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24Hour != is24Hour) {
+            mIs24Hour = is24Hour;
+            mInitialHourOfDay = getHour();
+
+            updateHourFormat();
+            updateUI(mRadialTimePickerView.getCurrentItemShowing());
         }
-
-        mIs24HourView = is24HourView;
-        mInitialHourOfDay = getCurrentHour();
-
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
     }
 
     /**
-     * @return true if this is in 24 hour view else false.
+     * @return {@code true} if time is displayed in 24-hour mode, or
+     *         {@code false} if time is displayed in 12-hour mode with AM/PM
+     *         indicators
      */
     @Override
-    public boolean is24HourView() {
-        return mIs24HourView;
+    public boolean is24Hour() {
+        return mIs24Hour;
     }
 
     @Override
@@ -502,14 +496,9 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
-                is24HourView(), getCurrentItemShowing());
+        return new SavedState(superState, getHour(), getMinute(),
+                is24Hour(), getCurrentItemShowing());
     }
 
     @Override
@@ -520,12 +509,6 @@
     }
 
     @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
-    @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         onPopulateAccessibilityEvent(event);
         return true;
@@ -534,13 +517,13 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         int flags = DateUtils.FORMAT_SHOW_TIME;
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             flags |= DateUtils.FORMAT_24HOUR;
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDate = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDate);
@@ -559,8 +542,7 @@
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator,
-                    getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
@@ -666,25 +648,29 @@
         }
 
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
     /**
      * Converts hour-of-day (0-23) time into a localized hour number.
+     * <p>
+     * The localized value may be in the range (0-23), (1-24), (0-11), or
+     * (1-12) depending on the locale. This method does not handle leading
+     * zeroes.
      *
      * @param hourOfDay the hour-of-day (0-23)
      * @return a localized hour number
      */
     private int getLocalizedHour(int hourOfDay) {
-        if (!mIs24HourView) {
+        if (!mIs24Hour) {
             // Convert to hour-of-am-pm.
             hourOfDay %= 12;
         }
 
         if (!mHourFormatStartsAtZero && hourOfDay == 0) {
             // Convert to clock-hour (either of-day or of-am-pm).
-            hourOfDay = mIs24HourView ? 24 : 12;
+            hourOfDay = mIs24Hour ? 24 : 12;
         }
 
         return hourOfDay;
@@ -716,8 +702,8 @@
      * separator as the character which is just after the hour marker in the returned pattern.
      */
     private void updateHeaderSeparator() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
-                (mIs24HourView) ? "Hm" : "hm");
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
+                (mIs24Hour) ? "Hm" : "hm");
         final String separatorText;
         // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
         final char[] hourFormats = {'H', 'h', 'K', 'k'};
@@ -819,14 +805,14 @@
     private final Runnable mCommitHour = new Runnable() {
         @Override
         public void run() {
-            setCurrentHour(mHourView.getValue());
+            setHour(mHourView.getValue());
         }
     };
 
     private final Runnable mCommitMinute = new Runnable() {
         @Override
         public void run() {
-            setCurrentMinute(mMinuteView.getValue());
+            setMinute(mMinuteView.getValue());
         }
     };
 
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 8741cc3..863d409 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,11 +46,6 @@
     private static final boolean DEFAULT_ENABLED_STATE = true;
     private static final int HOURS_IN_HALF_DAY = 12;
 
-    // state
-    private boolean mIs24HourView;
-    private boolean mIsAm;
-
-    // ui components
     private final NumberPicker mHourSpinner;
     private final NumberPicker mMinuteSpinner;
     private final NumberPicker mAmPmSpinner;
@@ -68,11 +62,15 @@
 
     private final String[] mAmPmStrings;
 
+    private final Calendar mTempCalendar;
+
     private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-    private Calendar mTempCalendar;
     private boolean mHourWithTwoDigit;
     private char mHourFormat;
 
+    private boolean mIs24HourView;
+    private boolean mIsAm;
+
     public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(delegator, context);
@@ -92,7 +90,7 @@
         mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
             public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
                 updateInputState();
-                if (!is24HourView()) {
+                if (!is24Hour()) {
                     if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
                             (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
                         mIsAm = !mIsAm;
@@ -124,14 +122,14 @@
                 int maxValue = mMinuteSpinner.getMaxValue();
                 if (oldVal == maxValue && newVal == minValue) {
                     int newHour = mHourSpinner.getValue() + 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
                     mHourSpinner.setValue(newHour);
                 } else if (oldVal == minValue && newVal == maxValue) {
                     int newHour = mHourSpinner.getValue() - 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
@@ -204,8 +202,9 @@
         updateAmPmControl();
 
         // set to current time
-        setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
-        setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+        mTempCalendar = Calendar.getInstance(mLocale);
+        setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+        setMinute(mTempCalendar.get(Calendar.MINUTE));
 
         if (!isEnabled()) {
             setEnabled(false);
@@ -221,7 +220,7 @@
     }
 
     private void getHourFormatData() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 (mIs24HourView) ? "Hm" : "hm");
         final int lengthPattern = bestDateTimePattern.length();
         mHourWithTwoDigit = false;
@@ -241,7 +240,7 @@
     }
 
     private boolean isAmPmAtStart() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 "hm" /* skeleton */);
 
         return bestDateTimePattern.startsWith("a");
@@ -257,7 +256,7 @@
      */
     private void setDividerText() {
         final String skeleton = (mIs24HourView) ? "Hm" : "hm";
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 skeleton);
         final String separatorText;
         int hourIndex = bestDateTimePattern.lastIndexOf('H');
@@ -279,16 +278,16 @@
     }
 
     @Override
-    public void setCurrentHour(int currentHour) {
-        setCurrentHour(currentHour, true);
+    public void setHour(int hour) {
+        setCurrentHour(hour, true);
     }
 
     private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
         // why was Integer used in the first place?
-        if (currentHour == getCurrentHour()) {
+        if (currentHour == getHour()) {
             return;
         }
-        if (!is24HourView()) {
+        if (!is24Hour()) {
             // convert [0,23] ordinal to wall clock display
             if (currentHour >= HOURS_IN_HALF_DAY) {
                 mIsAm = false;
@@ -310,9 +309,9 @@
     }
 
     @Override
-    public int getCurrentHour() {
+    public int getHour() {
         int currentHour = mHourSpinner.getValue();
-        if (is24HourView()) {
+        if (is24Hour()) {
             return currentHour;
         } else if (mIsAm) {
             return currentHour % HOURS_IN_HALF_DAY;
@@ -322,28 +321,27 @@
     }
 
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (currentMinute == getCurrentMinute()) {
+    public void setMinute(int minute) {
+        if (minute == getMinute()) {
             return;
         }
-        mMinuteSpinner.setValue(currentMinute);
+        mMinuteSpinner.setValue(minute);
         onTimeChanged();
     }
 
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mMinuteSpinner.getValue();
     }
 
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (mIs24HourView == is24HourView) {
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24HourView == is24Hour) {
             return;
         }
         // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
-        int currentHour = getCurrentHour();
+        int currentHour = getHour();
         // Order is important here.
-        mIs24HourView = is24HourView;
+        mIs24HourView = is24Hour;
         getHourFormatData();
         updateHourControl();
         // set value after spinner range is updated
@@ -353,7 +351,7 @@
     }
 
     @Override
-    public boolean is24HourView() {
+    public boolean is24Hour() {
         return mIs24HourView;
     }
 
@@ -388,20 +386,15 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        setCurrentLocale(newConfig.locale);
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+        return new SavedState(superState, getHour(), getMinute());
     }
 
     @Override
     public void onRestoreInstanceState(Parcelable state) {
         SavedState ss = (SavedState) state;
-        setCurrentHour(ss.getHour());
-        setCurrentMinute(ss.getMinute());
+        setHour(ss.getHour());
+        setMinute(ss.getMinute());
     }
 
     @Override
@@ -418,8 +411,8 @@
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDateUtterance);
@@ -447,7 +440,7 @@
     }
 
     private void updateAmPmControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             if (mAmPmSpinner != null) {
                 mAmPmSpinner.setVisibility(View.GONE);
             } else {
@@ -466,27 +459,16 @@
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
     }
 
-    /**
-     * Sets the current locale.
-     *
-     * @param locale The current locale.
-     */
-    @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
-                    getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
+                    getMinute());
         }
     }
 
     private void updateHourControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             // 'k' means 1-24 hour
             if (mHourFormat == 'k') {
                 mHourSpinner.setMinValue(1);
@@ -509,7 +491,7 @@
     }
 
     private void updateMinuteControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
         } else {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index acbf5eb..6e56513 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -1368,6 +1368,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = 0;
         int height = 0;
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 89e59f9..96624d2 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -38,8 +38,6 @@
 
     private OnYearSelectedListener mOnYearSelectedListener;
 
-    private long mCurrentTimeMillis;
-
     public YearPickerView(Context context, AttributeSet attrs) {
         this(context, attrs, R.attr.listViewStyle);
     }
@@ -79,10 +77,6 @@
         mOnYearSelectedListener = listener;
     }
 
-    public void setDate(long currentTimeMillis) {
-        mCurrentTimeMillis = currentTimeMillis;
-    }
-
     /**
      * Sets the currently selected year. Jumps immediately to the new year.
      *
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.aidl b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
new file mode 100644
index 0000000..529527b
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
@@ -0,0 +1,19 @@
+/*
+** 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.internal.app;
+
+parcelable EphemeralResolveInfo;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.java b/core/java/com/android/internal/app/EphemeralResolveInfo.java
new file mode 100644
index 0000000..0e7ef05
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app;
+
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information that is returned when resolving ephemeral
+ * applications.
+ */
+public final class EphemeralResolveInfo implements Parcelable {
+    public static final String SHA_ALGORITHM = "SHA-256";
+    private byte[] mDigestBytes;
+    private int mDigestPrefix;
+    private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
+
+    public EphemeralResolveInfo(Uri uri, List<IntentFilter> filters) {
+        generateDigest(uri);
+        mFilters.addAll(filters);
+    }
+
+    private EphemeralResolveInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public byte[] getDigestBytes() {
+        return mDigestBytes;
+    }
+
+    public int getDigestPrefix() {
+        return mDigestPrefix;
+    }
+
+    public List<IntentFilter> getFilters() {
+        return mFilters;
+    }
+
+    private void generateDigest(Uri uri) {
+        try {
+            final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+            final byte[] hostBytes = uri.getHost().getBytes();
+            final byte[] digestBytes = digest.digest(hostBytes);
+            mDigestBytes = digestBytes;
+            mDigestPrefix =
+                    digestBytes[0] << 24
+                    | digestBytes[1] << 16
+                    | digestBytes[2] << 8
+                    | digestBytes[3] << 0;
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("could not find digest algorithm");
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (mDigestBytes == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(mDigestBytes.length);
+            out.writeByteArray(mDigestBytes);
+        }
+        out.writeInt(mDigestPrefix);
+        out.writeList(mFilters);
+    }
+
+    private void readFromParcel(Parcel in) {
+        int digestBytesSize = in.readInt();
+        if (digestBytesSize > 0) {
+            mDigestBytes = new byte[digestBytesSize];
+            in.readByteArray(mDigestBytes);
+        }
+        mDigestPrefix = in.readInt();
+        in.readList(mFilters, null /*loader*/);
+    }
+
+    public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
+            = new Parcelable.Creator<EphemeralResolveInfo>() {
+        public EphemeralResolveInfo createFromParcel(Parcel in) {
+            return new EphemeralResolveInfo(in);
+        }
+
+        public EphemeralResolveInfo[] newArray(int size) {
+            return new EphemeralResolveInfo[size];
+        }
+    };
+}
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
new file mode 100644
index 0000000..65530f2
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolverService.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.internal.app;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Base class for implementing the resolver service.
+ * @hide
+ */
+public abstract class EphemeralResolverService extends Service {
+    public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
+    public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+    private Handler mHandler;
+
+    /**
+     * Called to retrieve resolve info for ephemeral applications.
+     *
+     * @param digestPrefix The hash prefix of the ephemeral's domain.
+     */
+    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+
+    @Override
+    protected final void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        mHandler = new ServiceHandler(base.getMainLooper());
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return new IEphemeralResolver.Stub() {
+            @Override
+            public void getEphemeralResolveInfoList(
+                    IRemoteCallback callback, int digestPrefix, int sequence) {
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
+                        digestPrefix, sequence, callback)
+                    .sendToTarget();
+            }
+        };
+    }
+
+    private final class ServiceHandler extends Handler {
+        public static final int MSG_GET_EPHEMERAL_RESOLVE_INFO = 1;
+
+        public ServiceHandler(Looper looper) {
+            super(looper, null /*callback*/, true /*async*/);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            final int action = message.what;
+            switch (action) {
+                case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
+                    final IRemoteCallback callback = (IRemoteCallback) message.obj;
+                    final List<EphemeralResolveInfo> resolveInfo =
+                            getEphemeralResolveInfoList(message.arg1);
+                    final Bundle data = new Bundle();
+                    data.putInt(EXTRA_SEQUENCE, message.arg2);
+                    data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+                    try {
+                        callback.sendResult(data);
+                    } catch (RemoteException e) {
+                    }
+                } break;
+
+                default: {
+                    throw new IllegalArgumentException("Unknown message: " + action);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index afbc609..5fdc920 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
 // This interface is also used by native code, so must
 // be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
 oneway interface IAppOpsCallback {
-    void opChanged(int op, String packageName);
+    void opChanged(int op, int uid, String packageName);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/core/java/com/android/internal/app/IEphemeralResolver.aidl
similarity index 68%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to core/java/com/android/internal/app/IEphemeralResolver.aidl
index f187178..40429ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.events.activity;
+package com.android.internal.app;
 
-import com.android.systemui.recents.events.EventBus;
+import android.content.Intent;
+import android.os.IRemoteCallback;
 
-/**
- * This is sent when the window animation into Recents starts.
- */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
-    // Simple event
+oneway interface IEphemeralResolver {
+    void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/core/java/com/android/internal/app/SystemUserHomeActivity.java
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to core/java/com/android/internal/app/SystemUserHomeActivity.java
index f187178..26fbf6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/core/java/com/android/internal/app/SystemUserHomeActivity.java
@@ -11,16 +11,16 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.recents.events.activity;
+package com.android.internal.app;
 
-import com.android.systemui.recents.events.EventBus;
+import android.app.Activity;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * Placeholder home activity, which is always installed on the system user. At least one home
+ * activity must be present and enabled in order for the system to boot.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
-    // Simple event
+public class SystemUserHomeActivity extends Activity {
 }
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 2c5e50c..c992c70 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -30,6 +30,10 @@
     public static final int QS_LOCK_TILE = 257;
     public static final int QS_USER_TILE = 258;
     public static final int QS_BATTERY_TILE = 259;
+    public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
+    public static final int ACTION_ZEN_ALLOW_PEEK = 261;
+    public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
+    public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index e26b27d..c067da7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -17,13 +17,19 @@
 
 package com.android.internal.os;
 
+import android.os.ShellCommand;
+
 import java.io.PrintStream;
 
 public abstract class BaseCommand {
 
-    protected String[] mArgs;
-    private int mNextArg;
-    private String mCurArgData;
+    final protected ShellCommand mArgs = new ShellCommand() {
+        @Override public int onCommand(String cmd) {
+            return 0;
+        }
+        @Override public void onHelp() {
+        }
+    };
 
     // These are magic strings understood by the Eclipse plugin.
     public static final String FATAL_ERROR_CODE = "Error type 1";
@@ -39,9 +45,7 @@
             return;
         }
 
-        mArgs = args;
-        mNextArg = 0;
-        mCurArgData = null;
+        mArgs.init(null, null, null, null, args, 0);
 
         try {
             onRun();
@@ -87,32 +91,7 @@
      * starts with '-'.  If the next argument is not an option, null is returned.
      */
     public String nextOption() {
-        if (mCurArgData != null) {
-            String prev = mArgs[mNextArg - 1];
-            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
-        }
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mNextArg++;
-        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 mArgs.getNextOption();
     }
 
     /**
@@ -120,15 +99,7 @@
      * no arguments left, return null.
      */
     public String nextArg() {
-        if (mCurArgData != null) {
-            String arg = mCurArgData;
-            mCurArgData = null;
-            return arg;
-        } else if (mNextArg < mArgs.length) {
-            return mArgs[mNextArg++];
-        } else {
-            return null;
-        }
+        return mArgs.getNextArg();
     }
 
     /**
@@ -136,11 +107,6 @@
      * no arguments left, throws an IllegalArgumentException to report this to the user.
      */
     public String nextArgRequired() {
-        String arg = nextArg();
-        if (arg == null) {
-            String prev = mArgs[mNextArg - 1];
-            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
-        }
-        return arg;
+        return mArgs.getNextArgRequired();
     }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f73df00..9391c60 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 = 135 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 136 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -1975,8 +1975,14 @@
 
     private int buildBatteryLevelInt(HistoryItem h) {
         return ((((int)h.batteryLevel)<<25)&0xfe000000)
-                | ((((int)h.batteryTemperature)<<14)&0x01ff8000)
-                | ((((int)h.batteryVoltage)<<1)&0x00007fff);
+                | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
+                | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
+    }
+
+    private void readBatteryLevelInt(int batteryLevelInt, HistoryItem out) {
+        out.batteryLevel = (byte)((batteryLevelInt & 0xfe000000) >>> 25);
+        out.batteryTemperature = (short)((batteryLevelInt & 0x01ff8000) >>> 15);
+        out.batteryVoltage = (char)((batteryLevelInt & 0x00007ffe) >>> 1);
     }
 
     private int buildStateInt(HistoryItem h) {
@@ -2117,9 +2123,7 @@
         final int batteryLevelInt;
         if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
             batteryLevelInt = src.readInt();
-            cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
-            cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
-            cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+            readBatteryLevelInt(batteryLevelInt, cur);
             cur.numReadInts += 1;
             if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
                     + Integer.toHexString(batteryLevelInt)
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 197004c..8186378 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -39,10 +39,8 @@
     public static final int DEBUG_ENABLE_SAFEMODE   = 1 << 3;
     /** Enable logging of third-party JNI activity. */
     public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
-    /** enable the JIT compiler */
-    public static final int DEBUG_ENABLE_JIT         = 1 << 5;
     /** Force generation of native debugging information. */
-    public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 6;
+    public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3e86fac..a40f9a8 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -322,7 +322,7 @@
 
         /**
          * From --enable-debugger, --enable-checkjni, --enable-assert,
-         * --enable-safemode, --enable-jit, --generate-debug-info and --enable-jni-logging.
+         * --enable-safemode, --generate-debug-info and --enable-jni-logging.
          */
         int debugFlags;
 
@@ -432,8 +432,6 @@
                     debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
                 } else if (arg.equals("--enable-checkjni")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
-                } else if (arg.equals("--enable-jit")) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_JIT;
                 } else if (arg.equals("--generate-debug-info")) {
                     debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
                 } else if (arg.equals("--enable-jni-logging")) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9a5cde6..f78d8d8 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -523,7 +523,7 @@
         String args[] = {
             "--setuid=1000",
             "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007",
+            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007,3009",
             "--capabilities=" + capabilities + "," + capabilities,
             "--nice-name=system_server",
             "--runtime-args",
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
new file mode 100644
index 0000000..b101733
--- /dev/null
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.policy;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+/**
+ * The thread which draws a fill in background while the app is resizing in areas where the app
+ * content draw is lagging behind the resize operation.
+ * 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.
+ * @hide
+ */
+public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
+
+    private DecorView mDecorView;
+
+    // This is containing the last requested size by a resize command. Note that this size might
+    // or might not have been applied to the output already.
+    private final Rect mTargetRect = new Rect();
+
+    // The render nodes for the multi threaded renderer.
+    private ThreadedRenderer mRenderer;
+    private RenderNode mFrameAndBackdropNode;
+
+    private final Rect mOldTargetRect = new Rect();
+    private final Rect mNewTargetRect = new Rect();
+    private Choreographer mChoreographer;
+
+    // Cached size values from the last render for the case that the view hierarchy is gone
+    // during a configuration change.
+    private int mLastContentWidth;
+    private int mLastContentHeight;
+    private int mLastCaptionHeight;
+    private int mLastXOffset;
+    private int mLastYOffset;
+
+    // Whether to report when next frame is drawn or not.
+    private boolean mReportNextDraw;
+
+    private Drawable mCaptionBackgroundDrawable;
+    private Drawable mResizingBackgroundDrawable;
+
+    public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds,
+            Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable) {
+        setName("ResizeFrame");
+
+        mRenderer = renderer;
+        onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable);
+
+        // Create a render node for the content and frame backdrop
+        // which can be resized independently from the content.
+        mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
+
+        mRenderer.addRenderNode(mFrameAndBackdropNode, true);
+
+        // Set the initial bounds and draw once so that we do not get a broken frame.
+        mTargetRect.set(initialBounds);
+        synchronized (this) {
+            changeWindowSizeLocked(initialBounds);
+        }
+
+        // Kick off our draw thread.
+        start();
+    }
+
+    void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable,
+            Drawable captionBackgroundDrawableDrawable) {
+        mDecorView = decorView;
+        mResizingBackgroundDrawable = resizingBackgroundDrawable;
+        mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable;
+    }
+
+    /**
+     * Call this function asynchronously when the window size has been changed. The change will
+     * be picked up once per frame and the frame will be re-rendered accordingly.
+     * @param newTargetBounds The new target bounds.
+     */
+    public void setTargetRect(Rect newTargetBounds) {
+        synchronized (this) {
+            mTargetRect.set(newTargetBounds);
+            // Notify of a bounds change.
+            pingRenderLocked();
+        }
+    }
+
+    /**
+     * The window got replaced due to a configuration change.
+     */
+    public void onConfigurationChange() {
+        synchronized (this) {
+            if (mRenderer != null) {
+                // Enforce a window redraw.
+                mOldTargetRect.set(0, 0, 0, 0);
+                pingRenderLocked();
+            }
+        }
+    }
+
+    /**
+     * All resources of the renderer will be released. This function can be called from the
+     * the UI thread as well as the renderer thread.
+     */
+    public void releaseRenderer() {
+        synchronized (this) {
+            if (mRenderer != null) {
+                // Invalidate the current content bounds.
+                mRenderer.setContentDrawBounds(0, 0, 0, 0);
+
+                // Remove the render node again
+                // (see comment above - better to do that only once).
+                mRenderer.removeRenderNode(mFrameAndBackdropNode);
+
+                mRenderer = null;
+
+                // Exit the renderer loop.
+                pingRenderLocked();
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        try {
+            Looper.prepare();
+            synchronized (this) {
+                mChoreographer = Choreographer.getInstance();
+
+                // Draw at least once.
+                mChoreographer.postFrameCallback(this);
+            }
+            Looper.loop();
+        } finally {
+            releaseRenderer();
+        }
+        synchronized (this) {
+            // Make sure no more messages are being sent.
+            mChoreographer = null;
+        }
+    }
+
+    /**
+     * The implementation of the FrameCallback.
+     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+     * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
+     */
+    @Override
+    public void doFrame(long frameTimeNanos) {
+        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);
+            }
+        }
+    }
+
+    /**
+     * 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,
+                    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 || !mDecorView.isShowingCaption());
+        }
+    }
+
+    public void onRequestDraw(boolean reportNextDraw) {
+        synchronized (this) {
+            mReportNextDraw = reportNextDraw;
+            mOldTargetRect.set(0, 0, 0, 0);
+            pingRenderLocked();
+        }
+    }
+
+    /**
+     * Resizing the frame to fit the new window size.
+     * @param newBounds The window bounds which needs to be drawn.
+     */
+    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.
+        final int captionHeight = mDecorView.getCaptionHeight();
+        // 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;
+        }
+
+        // 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 && mDecorView.isShowingCaption()) ||
+                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;
+        final int top = mLastYOffset + newBounds.top;
+        final int width = newBounds.width();
+        final int height = newBounds.height();
+
+        mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
+
+        // Draw the caption and content backdrops in to our render node.
+        final DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
+        mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
+        mCaptionBackgroundDrawable.draw(canvas);
+
+        // The backdrop: clear everything with the background. Clipping is done elsewhere.
+        mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
+        mResizingBackgroundDrawable.draw(canvas);
+        mFrameAndBackdropNode.end(canvas);
+
+        // We need to render the node explicitly
+        mRenderer.drawRenderNode(mFrameAndBackdropNode);
+
+        reportDrawIfNeeded();
+    }
+
+    /** Notify view root that a frame has been drawn by us, if it has requested so. */
+    private void reportDrawIfNeeded() {
+        if (mReportNextDraw) {
+            if (mDecorView.isAttachedToWindow()) {
+                mDecorView.getViewRootImpl().reportDrawFinish();
+            }
+            mReportNextDraw = false;
+        }
+    }
+
+    /**
+     * Sends a message to the renderer to wake up and perform the next action which can be
+     * either the next rendering or the self destruction if mRenderer is null.
+     * Note: This call must be synchronized.
+     */
+    private void pingRenderLocked() {
+        if (mChoreographer != null) {
+            mChoreographer.postFrameCallback(this);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
new file mode 100644
index 0000000..27fe03c
--- /dev/null
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -0,0 +1,1936 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.policy;
+
+import com.android.internal.R;
+import com.android.internal.view.FloatingActionMode;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorCaptionView;
+import com.android.internal.widget.FloatingToolbar;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowCallbacks;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+/** @hide */
+public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
+    private static final String TAG = "DecorView";
+
+    private static final boolean SWEEP_OPEN_MENU = false;
+
+    // The height of a window which has focus in DIP.
+    private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
+    // The height of a window which has not in DIP.
+    private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
+
+    // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
+    // size calculation takes the shadow size into account. We set the elevation currently
+    // to max until the first layout command has been executed.
+    private boolean mAllowUpdateElevation = false;
+
+    private boolean mElevationAdjustedForStack = false;
+
+    int mDefaultOpacity = PixelFormat.OPAQUE;
+
+    /** The feature ID of the panel, or -1 if this is the application's DecorView */
+    private final int mFeatureId;
+
+    private final Rect mDrawingBounds = new Rect();
+
+    private final Rect mBackgroundPadding = new Rect();
+
+    private final Rect mFramePadding = new Rect();
+
+    private final Rect mFrameOffsets = new Rect();
+
+    private boolean mHasCaption = false;
+
+    private boolean mChanging;
+
+    private Drawable mMenuBackground;
+    private boolean mWatchingForMenu;
+    private int mDownY;
+
+    ActionMode mPrimaryActionMode;
+    private ActionMode mFloatingActionMode;
+    private ActionBarContextView mPrimaryActionModeView;
+    private PopupWindow mPrimaryActionModePopup;
+    private Runnable mShowPrimaryActionModePopup;
+    private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+    private View mFloatingActionModeOriginatingView;
+    private FloatingToolbar mFloatingToolbar;
+    private ObjectAnimator mFadeAnim;
+
+    // View added at runtime to draw under the status bar area
+    private View mStatusGuard;
+    // View added at runtime to draw under the navigation bar area
+    private View mNavigationGuard;
+
+    private final ColorViewState mStatusColorViewState = new ColorViewState(
+            SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+            Gravity.TOP, Gravity.LEFT,
+            Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+            com.android.internal.R.id.statusBarBackground,
+            FLAG_FULLSCREEN);
+    private final ColorViewState mNavigationColorViewState = new ColorViewState(
+            SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+            Gravity.BOTTOM, Gravity.RIGHT,
+            Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+            com.android.internal.R.id.navigationBarBackground,
+            0 /* hideWindowFlag */);
+
+    private final Interpolator mShowInterpolator;
+    private final Interpolator mHideInterpolator;
+    private final int mBarEnterExitDuration;
+
+    private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
+
+    private int mLastTopInset = 0;
+    private int mLastBottomInset = 0;
+    private int mLastRightInset = 0;
+    private boolean mLastHasTopStableInset = false;
+    private boolean mLastHasBottomStableInset = false;
+    private boolean mLastHasRightStableInset = false;
+    private int mLastWindowFlags = 0;
+
+    private int mRootScrollY = 0;
+
+    private PhoneWindow mWindow;
+
+    ViewGroup mContentRoot;
+
+    private Rect mTempRect;
+    private Rect mOutsets = new Rect();
+
+    // This is the caption view for the window, containing the caption and window control
+    // buttons. The visibility of this decor depends on the workspace and the window type.
+    // If the window type does not require such a view, this member might be null.
+    DecorCaptionView mDecorCaptionView;
+
+    // Stack window is currently in. Since querying and changing the stack is expensive,
+    // this is the stack value the window is currently set up for.
+    int mStackId;
+
+    private boolean mWindowResizeCallbacksAdded = false;
+
+    BackdropFrameRenderer mBackdropFrameRenderer = null;
+    private Drawable mResizingBackgroundDrawable;
+    private Drawable mCaptionBackgroundDrawable;
+
+    DecorView(Context context, int featureId, PhoneWindow window) {
+        super(context);
+        mFeatureId = featureId;
+
+        mShowInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.linear_out_slow_in);
+        mHideInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.fast_out_linear_in);
+
+        mBarEnterExitDuration = context.getResources().getInteger(
+                R.integer.dock_enter_exit_duration);
+
+        setWindow(window);
+    }
+
+    void setBackgroundFallback(int resId) {
+        mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
+        setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+        mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        final int action = event.getAction();
+        final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+        if (isDown && (event.getRepeatCount() == 0)) {
+            // First handle chording of panel key: if a panel key is held
+            // but not released, try to execute a shortcut in it.
+            if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
+                boolean handled = dispatchKeyShortcutEvent(event);
+                if (handled) {
+                    return true;
+                }
+            }
+
+            // If a panel is open, perform a shortcut on it without the
+            // chorded panel key
+            if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
+                if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
+                    return true;
+                }
+            }
+        }
+
+        if (!mWindow.isDestroyed()) {
+            final Window.Callback cb = mWindow.getCallback();
+            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+                    : super.dispatchKeyEvent(event);
+            if (handled) {
+                return true;
+            }
+        }
+
+        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
+                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
+        // If the panel is already prepared, then perform the shortcut using it.
+        boolean handled;
+        if (mWindow.mPreparedPanel != null) {
+            handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
+                    Menu.FLAG_PERFORM_NO_CLOSE);
+            if (handled) {
+                if (mWindow.mPreparedPanel != null) {
+                    mWindow.mPreparedPanel.isHandled = true;
+                }
+                return true;
+            }
+        }
+
+        // Shortcut not handled by the panel.  Dispatch to the view hierarchy.
+        final Window.Callback cb = mWindow.getCallback();
+        handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
+        if (handled) {
+            return true;
+        }
+
+        // If the panel is not prepared, then we may be trying to handle a shortcut key
+        // combination such as Control+C.  Temporarily prepare the panel then mark it
+        // unprepared again when finished to ensure that the panel will again be prepared
+        // the next time it is shown for real.
+        PhoneWindow.PanelFeatureState st =
+                mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+        if (st != null && mWindow.mPreparedPanel == null) {
+            mWindow.preparePanel(st, ev);
+            handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
+                    Menu.FLAG_PERFORM_NO_CLOSE);
+            st.isPrepared = false;
+            if (handled) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
+    }
+
+    public boolean superDispatchKeyEvent(KeyEvent event) {
+        // Give priority to closing action modes if applicable.
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            final int action = event.getAction();
+            // Back cancels action modes first.
+            if (mPrimaryActionMode != null) {
+                if (action == KeyEvent.ACTION_UP) {
+                    mPrimaryActionMode.finish();
+                }
+                return true;
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+        return super.dispatchKeyShortcutEvent(event);
+    }
+
+    public boolean superDispatchTouchEvent(MotionEvent event) {
+        return super.dispatchTouchEvent(event);
+    }
+
+    public boolean superDispatchTrackballEvent(MotionEvent event) {
+        return super.dispatchTrackballEvent(event);
+    }
+
+    public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+        return super.dispatchGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return onInterceptTouchEvent(event);
+    }
+
+    private boolean isOutOfInnerBounds(int x, int y) {
+        return x < 0 || y < 0 || x > getWidth() || y > getHeight();
+    }
+
+    private boolean isOutOfBounds(int x, int y) {
+        return x < -5 || y < -5 || x > (getWidth() + 5)
+                || y > (getHeight() + 5);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        int action = event.getAction();
+        if (mHasCaption && isShowingCaption()) {
+            // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
+            // was (starting) outside the window. Window resizing events should be handled by
+            // WindowManager.
+            // TODO: Investigate how to handle the outside touch in window manager
+            //       without generating these events.
+            //       Currently we receive these because we need to enlarge the window's
+            //       touch region so that the monitor channel receives the events
+            //       in the outside touch area.
+            if (action == MotionEvent.ACTION_DOWN) {
+                final int x = (int) event.getX();
+                final int y = (int) event.getY();
+                if (isOutOfInnerBounds(x, y)) {
+                    return true;
+                }
+            }
+        }
+
+        if (mFeatureId >= 0) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                int x = (int)event.getX();
+                int y = (int)event.getY();
+                if (isOutOfBounds(x, y)) {
+                    mWindow.closePanel(mFeatureId);
+                    return true;
+                }
+            }
+        }
+
+        if (!SWEEP_OPEN_MENU) {
+            return false;
+        }
+
+        if (mFeatureId >= 0) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                Log.i(TAG, "Watchiing!");
+                mWatchingForMenu = true;
+                mDownY = (int) event.getY();
+                return false;
+            }
+
+            if (!mWatchingForMenu) {
+                return false;
+            }
+
+            int y = (int)event.getY();
+            if (action == MotionEvent.ACTION_MOVE) {
+                if (y > (mDownY+30)) {
+                    Log.i(TAG, "Closing!");
+                    mWindow.closePanel(mFeatureId);
+                    mWatchingForMenu = false;
+                    return true;
+                }
+            } else if (action == MotionEvent.ACTION_UP) {
+                mWatchingForMenu = false;
+            }
+
+            return false;
+        }
+
+        //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+        //        + " (in " + getHeight() + ")");
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            int y = (int)event.getY();
+            if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
+                Log.i(TAG, "Watching!");
+                mWatchingForMenu = true;
+            }
+            return false;
+        }
+
+        if (!mWatchingForMenu) {
+            return false;
+        }
+
+        int y = (int)event.getY();
+        if (action == MotionEvent.ACTION_MOVE) {
+            if (y < (getHeight()-30)) {
+                Log.i(TAG, "Opening!");
+                mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
+                        KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+                mWatchingForMenu = false;
+                return true;
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mWatchingForMenu = false;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void sendAccessibilityEvent(int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+            return;
+        }
+
+        // if we are showing a feature that should be announced and one child
+        // make this child the event source since this is the feature itself
+        // otherwise the callback will take over and announce its client
+        if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL ||
+                mFeatureId == Window.FEATURE_CONTEXT_MENU ||
+                mFeatureId == Window.FEATURE_PROGRESS ||
+                mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS)
+                && getChildCount() == 1) {
+            getChildAt(0).sendAccessibilityEvent(eventType);
+        } else {
+            super.sendAccessibilityEvent(eventType);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed()) {
+            if (cb.dispatchPopulateAccessibilityEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchPopulateAccessibilityEventInternal(event);
+    }
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean changed = super.setFrame(l, t, r, b);
+        if (changed) {
+            final Rect drawingBounds = mDrawingBounds;
+            getDrawingRect(drawingBounds);
+
+            Drawable fg = getForeground();
+            if (fg != null) {
+                final Rect frameOffsets = mFrameOffsets;
+                drawingBounds.left += frameOffsets.left;
+                drawingBounds.top += frameOffsets.top;
+                drawingBounds.right -= frameOffsets.right;
+                drawingBounds.bottom -= frameOffsets.bottom;
+                fg.setBounds(drawingBounds);
+                final Rect framePadding = mFramePadding;
+                drawingBounds.left += framePadding.left - frameOffsets.left;
+                drawingBounds.top += framePadding.top - frameOffsets.top;
+                drawingBounds.right -= framePadding.right - frameOffsets.right;
+                drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+            }
+
+            Drawable bg = getBackground();
+            if (bg != null) {
+                bg.setBounds(drawingBounds);
+            }
+
+            if (SWEEP_OPEN_MENU) {
+                if (mMenuBackground == null && mFeatureId < 0
+                        && mWindow.getAttributes().height
+                        == WindowManager.LayoutParams.MATCH_PARENT) {
+                    mMenuBackground = getContext().getDrawable(
+                            R.drawable.menu_background);
+                }
+                if (mMenuBackground != null) {
+                    mMenuBackground.setBounds(drawingBounds.left,
+                            drawingBounds.bottom-6, drawingBounds.right,
+                            drawingBounds.bottom+20);
+                }
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+        final int widthMode = getMode(widthMeasureSpec);
+        final int heightMode = getMode(heightMeasureSpec);
+
+        boolean fixedWidth = false;
+        if (widthMode == AT_MOST) {
+            final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
+                    : mWindow.mFixedWidthMajor;
+            if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+                final int w;
+                if (tvw.type == TypedValue.TYPE_DIMENSION) {
+                    w = (int) tvw.getDimension(metrics);
+                } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+                    w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+                } else {
+                    w = 0;
+                }
+
+                if (w > 0) {
+                    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            Math.min(w, widthSize), EXACTLY);
+                    fixedWidth = true;
+                }
+            }
+        }
+
+        if (heightMode == AT_MOST) {
+            final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
+                    : mWindow.mFixedHeightMinor;
+            if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+                final int h;
+                if (tvh.type == TypedValue.TYPE_DIMENSION) {
+                    h = (int) tvh.getDimension(metrics);
+                } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+                    h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+                } else {
+                    h = 0;
+                }
+                if (h > 0) {
+                    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            Math.min(h, heightSize), EXACTLY);
+                }
+            }
+        }
+
+        getOutsets(mOutsets);
+        if (mOutsets.top > 0 || mOutsets.bottom > 0) {
+            int mode = MeasureSpec.getMode(heightMeasureSpec);
+            if (mode != MeasureSpec.UNSPECIFIED) {
+                int height = MeasureSpec.getSize(heightMeasureSpec);
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        height + mOutsets.top + mOutsets.bottom, mode);
+            }
+        }
+        if (mOutsets.left > 0 || mOutsets.right > 0) {
+            int mode = MeasureSpec.getMode(widthMeasureSpec);
+            if (mode != MeasureSpec.UNSPECIFIED) {
+                int width = MeasureSpec.getSize(widthMeasureSpec);
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        width + mOutsets.left + mOutsets.right, mode);
+            }
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int width = getMeasuredWidth();
+        boolean measure = false;
+
+        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+
+        if (!fixedWidth && widthMode == AT_MOST) {
+            final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+            if (tv.type != TypedValue.TYPE_NULL) {
+                final int min;
+                if (tv.type == TypedValue.TYPE_DIMENSION) {
+                    min = (int)tv.getDimension(metrics);
+                } else if (tv.type == TypedValue.TYPE_FRACTION) {
+                    min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+                } else {
+                    min = 0;
+                }
+
+                if (width < min) {
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+                    measure = true;
+                }
+            }
+        }
+
+        // TODO: Support height?
+
+        if (measure) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        getOutsets(mOutsets);
+        if (mOutsets.left > 0) {
+            offsetLeftAndRight(-mOutsets.left);
+        }
+        if (mOutsets.top > 0) {
+            offsetTopAndBottom(-mOutsets.top);
+        }
+
+        // If the application changed its SystemUI metrics, we might also have to adapt
+        // our shadow elevation.
+        updateElevation();
+        mAllowUpdateElevation = true;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (mMenuBackground != null) {
+            mMenuBackground.draw(canvas);
+        }
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        // Reuse the context menu builder
+        if (mWindow.mContextMenu == null) {
+            mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+            mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+        } else {
+            mWindow.mContextMenu.clearAll();
+        }
+
+        final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
+                originalView.getWindowToken());
+        if (helper != null) {
+            helper.setPresenterCallback(mWindow.mContextMenuCallback);
+        } else if (mWindow.mContextMenuHelper != null) {
+            // No menu to show, but if we have a menu currently showing it just became blank.
+            // Close it.
+            mWindow.mContextMenuHelper.dismiss();
+        }
+        mWindow.mContextMenuHelper = helper;
+        return helper != null;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        // Reuse the context menu builder
+        if (mWindow.mContextMenu == null) {
+            mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+            mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+        } else {
+            mWindow.mContextMenu.clearAll();
+        }
+
+        final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
+                getContext(), originalView, x, y);
+        if (helper != null) {
+            helper.setCallback(mWindow.mContextMenuCallback);
+        } else if (mWindow.mContextMenuPopupHelper != null) {
+            // No menu to show, but if we have a menu currently showing it just became blank.
+            // Close it.
+            mWindow.mContextMenuPopupHelper.dismiss();
+        }
+        mWindow.mContextMenuPopupHelper = helper;
+        return helper != null;
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View originalView,
+            ActionMode.Callback callback) {
+        return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(
+            View child, ActionMode.Callback callback, int type) {
+        return startActionMode(child, callback, type);
+    }
+
+    @Override
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+    }
+
+    @Override
+    public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+        return startActionMode(this, callback, type);
+    }
+
+    private ActionMode startActionMode(
+            View originatingView, ActionMode.Callback callback, int type) {
+        ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
+        ActionMode mode = null;
+        if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            try {
+                mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
+            } catch (AbstractMethodError ame) {
+                // Older apps might not implement the typed version of this method.
+                if (type == ActionMode.TYPE_PRIMARY) {
+                    try {
+                        mode = mWindow.getCallback().onWindowStartingActionMode(
+                                wrappedCallback);
+                    } catch (AbstractMethodError ame2) {
+                        // Older apps might not implement this callback method at all.
+                    }
+                }
+            }
+        }
+        if (mode != null) {
+            if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+                cleanupPrimaryActionMode();
+                mPrimaryActionMode = mode;
+            } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+                if (mFloatingActionMode != null) {
+                    mFloatingActionMode.finish();
+                }
+                mFloatingActionMode = mode;
+            }
+        } else {
+            mode = createActionMode(type, wrappedCallback, originatingView);
+            if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+                setHandledActionMode(mode);
+            } else {
+                mode = null;
+            }
+        }
+        if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            try {
+                mWindow.getCallback().onActionModeStarted(mode);
+            } catch (AbstractMethodError ame) {
+                // Older apps might not implement this callback method.
+            }
+        }
+        return mode;
+    }
+
+    private void cleanupPrimaryActionMode() {
+        if (mPrimaryActionMode != null) {
+            mPrimaryActionMode.finish();
+            mPrimaryActionMode = null;
+        }
+        if (mPrimaryActionModeView != null) {
+            mPrimaryActionModeView.killMode();
+        }
+    }
+
+    private void cleanupFloatingActionModeViews() {
+        if (mFloatingToolbar != null) {
+            mFloatingToolbar.dismiss();
+            mFloatingToolbar = null;
+        }
+        if (mFloatingActionModeOriginatingView != null) {
+            if (mFloatingToolbarPreDrawListener != null) {
+                mFloatingActionModeOriginatingView.getViewTreeObserver()
+                    .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
+                mFloatingToolbarPreDrawListener = null;
+            }
+            mFloatingActionModeOriginatingView = null;
+        }
+    }
+
+    void startChanging() {
+        mChanging = true;
+    }
+
+    void finishChanging() {
+        mChanging = false;
+        drawableChanged();
+    }
+
+    public void setWindowBackground(Drawable drawable) {
+        if (getBackground() != drawable) {
+            setBackgroundDrawable(drawable);
+            if (drawable != null) {
+                drawable.getPadding(mBackgroundPadding);
+            } else {
+                mBackgroundPadding.setEmpty();
+            }
+            drawableChanged();
+        }
+    }
+
+    public void setWindowFrame(Drawable drawable) {
+        if (getForeground() != drawable) {
+            setForeground(drawable);
+            if (drawable != null) {
+                drawable.getPadding(mFramePadding);
+            } else {
+                mFramePadding.setEmpty();
+            }
+            drawableChanged();
+        }
+    }
+
+    @Override
+    public void onWindowSystemUiVisibilityChanged(int visible) {
+        updateColorViews(null /* insets */, true /* animate */);
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mFrameOffsets.set(insets.getSystemWindowInsets());
+        insets = updateColorViews(insets, true /* animate */);
+        insets = updateStatusGuard(insets);
+        updateNavigationGuard(insets);
+        if (getForeground() != null) {
+            drawableChanged();
+        }
+        return insets;
+    }
+
+    @Override
+    public boolean isTransitionGroup() {
+        return false;
+    }
+
+    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
+        WindowManager.LayoutParams attrs = mWindow.getAttributes();
+        int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+
+        if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
+            boolean disallowAnimate = !isLaidOut();
+            disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
+                    & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+            mLastWindowFlags = attrs.flags;
+
+            if (insets != null) {
+                mLastTopInset = Math.min(insets.getStableInsetTop(),
+                        insets.getSystemWindowInsetTop());
+                mLastBottomInset = Math.min(insets.getStableInsetBottom(),
+                        insets.getSystemWindowInsetBottom());
+                mLastRightInset = Math.min(insets.getStableInsetRight(),
+                        insets.getSystemWindowInsetRight());
+
+                // Don't animate if the presence of stable insets has changed, because that
+                // indicates that the window was either just added and received them for the
+                // first time, or the window size or position has changed.
+                boolean hasTopStableInset = insets.getStableInsetTop() != 0;
+                disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
+                mLastHasTopStableInset = hasTopStableInset;
+
+                boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
+                disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
+                mLastHasBottomStableInset = hasBottomStableInset;
+
+                boolean hasRightStableInset = insets.getStableInsetRight() != 0;
+                disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
+                mLastHasRightStableInset = hasRightStableInset;
+            }
+
+            boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
+            int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
+            updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
+                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
+                    0 /* rightInset */, animate && !disallowAnimate);
+
+            boolean statusBarNeedsRightInset = navBarToRightEdge
+                    && mNavigationColorViewState.present;
+            int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
+            updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
+                    mLastTopInset, false /* matchVertical */, statusBarRightInset,
+                    animate && !disallowAnimate);
+        }
+
+        // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
+        // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
+        // explicitly asked for it.
+
+        boolean consumingNavBar =
+                (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+                        && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+
+        int consumedRight = consumingNavBar ? mLastRightInset : 0;
+        int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+
+        if (mContentRoot != null
+                && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+            MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
+            if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
+                lp.rightMargin = consumedRight;
+                lp.bottomMargin = consumedBottom;
+                mContentRoot.setLayoutParams(lp);
+
+                if (insets == null) {
+                    // The insets have changed, but we're not currently in the process
+                    // of dispatching them.
+                    requestApplyInsets();
+                }
+            }
+            if (insets != null) {
+                insets = insets.replaceSystemWindowInsets(
+                        insets.getSystemWindowInsetLeft(),
+                        insets.getSystemWindowInsetTop(),
+                        insets.getSystemWindowInsetRight() - consumedRight,
+                        insets.getSystemWindowInsetBottom() - consumedBottom);
+            }
+        }
+
+        if (insets != null) {
+            insets = insets.consumeStableInsets();
+        }
+        return insets;
+    }
+
+    /**
+     * Update a color view
+     *
+     * @param state the color view to update.
+     * @param sysUiVis the current systemUiVisibility to apply.
+     * @param color the current color to apply.
+     * @param size the current size in the non-parent-matching dimension.
+     * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
+     *                    horizontal edge,
+     * @param rightMargin rightMargin for the color view.
+     * @param animate if true, the change will be animated.
+     */
+    private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
+            int size, boolean verticalBar, int rightMargin, boolean animate) {
+        state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
+                && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
+                && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+        boolean show = state.present
+                && (color & Color.BLACK) != 0
+                && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
+
+        boolean visibilityChanged = false;
+        View view = state.view;
+
+        int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
+        int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
+        int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
+
+        if (view == null) {
+            if (show) {
+                state.view = view = new View(mContext);
+                view.setBackgroundColor(color);
+                view.setTransitionName(state.transitionName);
+                view.setId(state.id);
+                visibilityChanged = true;
+                view.setVisibility(INVISIBLE);
+                state.targetVisibility = VISIBLE;
+
+                LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
+                        resolvedGravity);
+                lp.rightMargin = rightMargin;
+                addView(view, lp);
+                updateColorViewTranslations();
+            }
+        } else {
+            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) {
+                view.setBackgroundColor(color);
+            }
+        }
+        if (visibilityChanged) {
+            view.animate().cancel();
+            if (animate) {
+                if (show) {
+                    if (view.getVisibility() != VISIBLE) {
+                        view.setVisibility(VISIBLE);
+                        view.setAlpha(0.0f);
+                    }
+                    view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
+                            setDuration(mBarEnterExitDuration);
+                } else {
+                    view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
+                            .setDuration(mBarEnterExitDuration)
+                            .withEndAction(new Runnable() {
+                                @Override
+                                public void run() {
+                                    state.view.setAlpha(1.0f);
+                                    state.view.setVisibility(INVISIBLE);
+                                }
+                            });
+                }
+            } else {
+                view.setAlpha(1.0f);
+                view.setVisibility(show ? VISIBLE : INVISIBLE);
+            }
+        }
+    }
+
+    private void updateColorViewTranslations() {
+        // Put the color views back in place when they get moved off the screen
+        // due to the the ViewRootImpl panning.
+        int rootScrollY = mRootScrollY;
+        if (mStatusColorViewState.view != null) {
+            mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
+        }
+        if (mNavigationColorViewState.view != null) {
+            mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
+        }
+    }
+
+    private WindowInsets updateStatusGuard(WindowInsets insets) {
+        boolean showStatusGuard = false;
+        // Show the status guard when the non-overlay contextual action bar is showing
+        if (mPrimaryActionModeView != null) {
+            if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
+                // Insets are magic!
+                final MarginLayoutParams mlp = (MarginLayoutParams)
+                        mPrimaryActionModeView.getLayoutParams();
+                boolean mlpChanged = false;
+                if (mPrimaryActionModeView.isShown()) {
+                    if (mTempRect == null) {
+                        mTempRect = new Rect();
+                    }
+                    final Rect rect = mTempRect;
+
+                    // If the parent doesn't consume the insets, manually
+                    // apply the default system window insets.
+                    mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
+                    final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
+                    if (mlp.topMargin != newMargin) {
+                        mlpChanged = true;
+                        mlp.topMargin = insets.getSystemWindowInsetTop();
+
+                        if (mStatusGuard == null) {
+                            mStatusGuard = new View(mContext);
+                            mStatusGuard.setBackgroundColor(mContext.getColor(
+                                    R.color.input_method_navigation_guard));
+                            addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
+                                    new LayoutParams(LayoutParams.MATCH_PARENT,
+                                            mlp.topMargin, Gravity.START | Gravity.TOP));
+                        } else {
+                            final LayoutParams lp = (LayoutParams)
+                                    mStatusGuard.getLayoutParams();
+                            if (lp.height != mlp.topMargin) {
+                                lp.height = mlp.topMargin;
+                                mStatusGuard.setLayoutParams(lp);
+                            }
+                        }
+                    }
+
+                    // The action mode's theme may differ from the app, so
+                    // always show the status guard above it if we have one.
+                    showStatusGuard = mStatusGuard != null;
+
+                    // We only need to consume the insets if the action
+                    // mode is overlaid on the app content (e.g. it's
+                    // sitting in a FrameLayout, see
+                    // screen_simple_overlay_action_mode.xml).
+                    final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
+                            & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
+                    insets = insets.consumeSystemWindowInsets(
+                            false, nonOverlay && showStatusGuard /* top */, false, false);
+                } else {
+                    // reset top margin
+                    if (mlp.topMargin != 0) {
+                        mlpChanged = true;
+                        mlp.topMargin = 0;
+                    }
+                }
+                if (mlpChanged) {
+                    mPrimaryActionModeView.setLayoutParams(mlp);
+                }
+            }
+        }
+        if (mStatusGuard != null) {
+            mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+        }
+        return insets;
+    }
+
+    private void updateNavigationGuard(WindowInsets insets) {
+        // IMEs lay out below the nav bar, but the content view must not (for back compat)
+        if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+            // prevent the content view from including the nav bar height
+            if (mWindow.mContentParent != null) {
+                if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+                    MarginLayoutParams mlp =
+                            (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
+                    mlp.bottomMargin = insets.getSystemWindowInsetBottom();
+                    mWindow.mContentParent.setLayoutParams(mlp);
+                }
+            }
+            // position the navigation guard view, creating it if necessary
+            if (mNavigationGuard == null) {
+                mNavigationGuard = new View(mContext);
+                mNavigationGuard.setBackgroundColor(mContext.getColor(
+                        R.color.input_method_navigation_guard));
+                addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
+                        new LayoutParams(LayoutParams.MATCH_PARENT,
+                                insets.getSystemWindowInsetBottom(),
+                                Gravity.START | Gravity.BOTTOM));
+            } else {
+                LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+                lp.height = insets.getSystemWindowInsetBottom();
+                mNavigationGuard.setLayoutParams(lp);
+            }
+        }
+    }
+
+    private void drawableChanged() {
+        if (mChanging) {
+            return;
+        }
+
+        setPadding(mFramePadding.left + mBackgroundPadding.left,
+                mFramePadding.top + mBackgroundPadding.top,
+                mFramePadding.right + mBackgroundPadding.right,
+                mFramePadding.bottom + mBackgroundPadding.bottom);
+        requestLayout();
+        invalidate();
+
+        int opacity = PixelFormat.OPAQUE;
+        if (ActivityManager.StackId.hasWindowShadow(mStackId)) {
+            // If the window has a shadow, it must be translucent.
+            opacity = PixelFormat.TRANSLUCENT;
+        } else{
+            // Note: If there is no background, we will assume opaque. The
+            // common case seems to be that an application sets there to be
+            // no background so it can draw everything itself. For that,
+            // we would like to assume OPAQUE and let the app force it to
+            // the slower TRANSLUCENT mode if that is really what it wants.
+            Drawable bg = getBackground();
+            Drawable fg = getForeground();
+            if (bg != null) {
+                if (fg == null) {
+                    opacity = bg.getOpacity();
+                } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+                        && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+                    // If the frame padding is zero, then we can be opaque
+                    // if either the frame -or- the background is opaque.
+                    int fop = fg.getOpacity();
+                    int bop = bg.getOpacity();
+                    if (false)
+                        Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+                    if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+                        opacity = PixelFormat.OPAQUE;
+                    } else if (fop == PixelFormat.UNKNOWN) {
+                        opacity = bop;
+                    } else if (bop == PixelFormat.UNKNOWN) {
+                        opacity = fop;
+                    } else {
+                        opacity = Drawable.resolveOpacity(fop, bop);
+                    }
+                } else {
+                    // For now we have to assume translucent if there is a
+                    // frame with padding... there is no way to tell if the
+                    // frame and background together will draw all pixels.
+                    if (false)
+                        Log.v(TAG, "Padding: " + mFramePadding);
+                    opacity = PixelFormat.TRANSLUCENT;
+                }
+            }
+            if (false)
+                Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+        }
+
+        if (false)
+            Log.v(TAG, "Selected default opacity: " + opacity);
+
+        mDefaultOpacity = opacity;
+        if (mFeatureId < 0) {
+            mWindow.setDefaultWindowFormat(opacity);
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        // If the user is chording a menu shortcut, release the chord since
+        // this window lost focus
+        if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus
+                && mWindow.mPanelChordingKey != 0) {
+            mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+        }
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            cb.onWindowFocusChanged(hasWindowFocus);
+        }
+
+        if (mPrimaryActionMode != null) {
+            mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
+        }
+        if (mFloatingActionMode != null) {
+            mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
+        }
+
+        updateElevation();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            cb.onAttachedToWindow();
+        }
+
+        if (mFeatureId == -1) {
+            /*
+             * The main window has been attached, try to restore any panels
+             * that may have been open before. This is called in cases where
+             * an activity is being killed for configuration change and the
+             * menu was open. When the activity is recreated, the menu
+             * should be shown again.
+             */
+            mWindow.openPanelsAfterRestore();
+        }
+
+        if (!mWindowResizeCallbacksAdded) {
+            // If there is no window callback installed there was no window set before. Set it now.
+            // Note that our ViewRootImpl object will not change.
+            getViewRootImpl().addWindowCallbacks(this);
+            mWindowResizeCallbacksAdded = true;
+        } else if (mBackdropFrameRenderer != null) {
+            // We are resizing and this call happened due to a configuration change. Tell the
+            // renderer about it.
+            mBackdropFrameRenderer.onConfigurationChange();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && mFeatureId < 0) {
+            cb.onDetachedFromWindow();
+        }
+
+        if (mWindow.mDecorContentParent != null) {
+            mWindow.mDecorContentParent.dismissPopups();
+        }
+
+        if (mPrimaryActionModePopup != null) {
+            removeCallbacks(mShowPrimaryActionModePopup);
+            if (mPrimaryActionModePopup.isShowing()) {
+                mPrimaryActionModePopup.dismiss();
+            }
+            mPrimaryActionModePopup = null;
+        }
+        if (mFloatingToolbar != null) {
+            mFloatingToolbar.dismiss();
+            mFloatingToolbar = null;
+        }
+
+        PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+        if (st != null && st.menu != null && mFeatureId < 0) {
+            st.menu.close();
+        }
+
+        if (mWindowResizeCallbacksAdded) {
+            getViewRootImpl().removeWindowCallbacks(this);
+            mWindowResizeCallbacksAdded = false;
+        }
+    }
+
+    @Override
+    public void onCloseSystemDialogs(String reason) {
+        if (mFeatureId >= 0) {
+            mWindow.closeAllPanels();
+        }
+    }
+
+    public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
+        return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
+    }
+
+    public InputQueue.Callback willYouTakeTheInputQueue() {
+        return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
+    }
+
+    public void setSurfaceType(int type) {
+        mWindow.setType(type);
+    }
+
+    public void setSurfaceFormat(int format) {
+        mWindow.setFormat(format);
+    }
+
+    public void setSurfaceKeepScreenOn(boolean keepOn) {
+        if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    public void onRootViewScrollYChanged(int rootScrollY) {
+        mRootScrollY = rootScrollY;
+        updateColorViewTranslations();
+    }
+
+    private ActionMode createActionMode(
+            int type, ActionMode.Callback2 callback, View originatingView) {
+        switch (type) {
+            case ActionMode.TYPE_PRIMARY:
+            default:
+                return createStandaloneActionMode(callback);
+            case ActionMode.TYPE_FLOATING:
+                return createFloatingActionMode(originatingView, callback);
+        }
+    }
+
+    private void setHandledActionMode(ActionMode mode) {
+        if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+            setHandledPrimaryActionMode(mode);
+        } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+            setHandledFloatingActionMode(mode);
+        }
+    }
+
+    private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
+        endOnGoingFadeAnimation();
+        cleanupPrimaryActionMode();
+        if (mPrimaryActionModeView == null) {
+            if (mWindow.isFloating()) {
+                // Use the action bar theme.
+                final TypedValue outValue = new TypedValue();
+                final Resources.Theme baseTheme = mContext.getTheme();
+                baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+                final Context actionBarContext;
+                if (outValue.resourceId != 0) {
+                    final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
+                    actionBarTheme.setTo(baseTheme);
+                    actionBarTheme.applyStyle(outValue.resourceId, true);
+
+                    actionBarContext = new ContextThemeWrapper(mContext, 0);
+                    actionBarContext.getTheme().setTo(actionBarTheme);
+                } else {
+                    actionBarContext = mContext;
+                }
+
+                mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
+                mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
+                        R.attr.actionModePopupWindowStyle);
+                mPrimaryActionModePopup.setWindowLayoutType(
+                        WindowManager.LayoutParams.TYPE_APPLICATION);
+                mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
+                mPrimaryActionModePopup.setWidth(MATCH_PARENT);
+
+                actionBarContext.getTheme().resolveAttribute(
+                        R.attr.actionBarSize, outValue, true);
+                final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+                        actionBarContext.getResources().getDisplayMetrics());
+                mPrimaryActionModeView.setContentHeight(height);
+                mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
+                mShowPrimaryActionModePopup = new Runnable() {
+                    public void run() {
+                        mPrimaryActionModePopup.showAtLocation(
+                                mPrimaryActionModeView.getApplicationWindowToken(),
+                                Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+                        endOnGoingFadeAnimation();
+                        mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+                                0f, 1f);
+                        mFadeAnim.addListener(new Animator.AnimatorListener() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                mPrimaryActionModeView.setVisibility(VISIBLE);
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                mPrimaryActionModeView.setAlpha(1f);
+                                mFadeAnim = null;
+                            }
+
+                            @Override
+                            public void onAnimationCancel(Animator animation) {
+
+                            }
+
+                            @Override
+                            public void onAnimationRepeat(Animator animation) {
+
+                            }
+                        });
+                        mFadeAnim.start();
+                    }
+                };
+            } else {
+                ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
+                if (stub != null) {
+                    mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
+                }
+            }
+        }
+        if (mPrimaryActionModeView != null) {
+            mPrimaryActionModeView.killMode();
+            ActionMode mode = new StandaloneActionMode(
+                    mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
+                    callback, mPrimaryActionModePopup == null);
+            return mode;
+        }
+        return null;
+    }
+
+    private void endOnGoingFadeAnimation() {
+        if (mFadeAnim != null) {
+            mFadeAnim.end();
+        }
+    }
+
+    private void setHandledPrimaryActionMode(ActionMode mode) {
+        endOnGoingFadeAnimation();
+        mPrimaryActionMode = mode;
+        mPrimaryActionMode.invalidate();
+        mPrimaryActionModeView.initForMode(mPrimaryActionMode);
+        if (mPrimaryActionModePopup != null) {
+            post(mShowPrimaryActionModePopup);
+        } else {
+            mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
+            mFadeAnim.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mPrimaryActionModeView.setVisibility(View.VISIBLE);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mPrimaryActionModeView.setAlpha(1f);
+                    mFadeAnim = null;
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+
+                }
+            });
+            mFadeAnim.start();
+        }
+        mPrimaryActionModeView.sendAccessibilityEvent(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    private ActionMode createFloatingActionMode(
+            View originatingView, ActionMode.Callback2 callback) {
+        if (mFloatingActionMode != null) {
+            mFloatingActionMode.finish();
+        }
+        cleanupFloatingActionModeViews();
+        final FloatingActionMode mode =
+                new FloatingActionMode(mContext, callback, originatingView);
+        mFloatingActionModeOriginatingView = originatingView;
+        mFloatingToolbarPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    mode.updateViewLocationInWindow();
+                    return true;
+                }
+            };
+        return mode;
+    }
+
+    private void setHandledFloatingActionMode(ActionMode mode) {
+        mFloatingActionMode = mode;
+        mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+        ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
+        mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
+        mFloatingActionModeOriginatingView.getViewTreeObserver()
+            .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
+    }
+
+    /**
+     * Informs the decor if the caption is attached and visible.
+     * @param attachedAndVisible true when the decor is visible.
+     * Note that this will even be called if there is no caption.
+     **/
+    void enableCaption(boolean attachedAndVisible) {
+        if (mHasCaption != attachedAndVisible) {
+            mHasCaption = attachedAndVisible;
+            if (getForeground() != null) {
+                drawableChanged();
+            }
+        }
+    }
+
+    void setWindow(PhoneWindow phoneWindow) {
+        mWindow = phoneWindow;
+        Context context = getContext();
+        if (context instanceof DecorContext) {
+            DecorContext decorContext = (DecorContext) context;
+            decorContext.setPhoneWindow(mWindow);
+        }
+    }
+
+    void onConfigurationChanged() {
+        int workspaceId = getStackId();
+        if (mDecorCaptionView != null) {
+            if (mStackId != workspaceId) {
+                mStackId = workspaceId;
+                // We might have to change the kind of surface before we do anything else.
+                mDecorCaptionView.onConfigurationChanged(
+                        ActivityManager.StackId.hasWindowDecor(mStackId));
+                enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId));
+            }
+        }
+        initializeElevation();
+    }
+
+    View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
+        mStackId = getStackId();
+
+        mResizingBackgroundDrawable = getResizingBackgroundDrawable(
+                mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
+        mCaptionBackgroundDrawable =
+                getContext().getDrawable(R.drawable.decor_caption_title_focused);
+
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.onResourcesLoaded(
+                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable);
+        }
+
+        mDecorCaptionView = createDecorCaptionView(inflater);
+        final View root = inflater.inflate(layoutResource, null);
+        if (mDecorCaptionView != null) {
+            if (mDecorCaptionView.getParent() == null) {
+                addView(mDecorCaptionView,
+                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            }
+            mDecorCaptionView.addView(root,
+                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
+        } else {
+            addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        }
+        mContentRoot = (ViewGroup) root;
+        initializeElevation();
+        return root;
+    }
+
+    // Free floating overlapping windows require a caption.
+    private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
+        DecorCaptionView DecorCaptionView = null;
+        for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) {
+            View view = getChildAt(i);
+            if (view instanceof DecorCaptionView) {
+                // The decor was most likely saved from a relaunch - so reuse it.
+                DecorCaptionView = (DecorCaptionView) view;
+                removeViewAt(i);
+            }
+        }
+        final WindowManager.LayoutParams attrs = mWindow.getAttributes();
+        final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+                attrs.type == TYPE_APPLICATION;
+        // Only a non floating application window on one of the allowed workspaces can get a caption
+        if (!mWindow.isFloating() && isApplication
+                && ActivityManager.StackId.hasWindowDecor(mStackId)) {
+            // Dependent on the brightness of the used title we either use the
+            // dark or the light button frame.
+            if (DecorCaptionView == null) {
+                Context context = getContext();
+                TypedValue value = new TypedValue();
+                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
+                inflater = inflater.from(context);
+                if (Color.luminance(value.data) < 0.5) {
+                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
+                            R.layout.decor_caption_dark, null);
+                } else {
+                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
+                            R.layout.decor_caption_light, null);
+                }
+            }
+            DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
+        } else {
+            DecorCaptionView = null;
+        }
+
+        // Tell the decor if it has a visible caption.
+        enableCaption(DecorCaptionView != null);
+        return DecorCaptionView;
+    }
+
+    /**
+     * 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(int backgroundRes, int backgroundFallbackRes) {
+        final Context context = getContext();
+
+        if (backgroundRes != 0) {
+            final Drawable drawable = context.getDrawable(backgroundRes);
+            if (drawable != null) {
+                return drawable;
+            }
+        }
+
+        if (backgroundFallbackRes != 0) {
+            final Drawable fallbackDrawable = context.getDrawable(backgroundFallbackRes);
+            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=" + mWindow);
+        return null;
+    }
+
+    /**
+     * Returns the Id of the stack which contains this window.
+     * Note that if no stack can be determined - which usually means that it was not
+     * created for an activity - the fullscreen stack ID will be returned.
+     * @return Returns the stack id which contains this window.
+     **/
+    private int getStackId() {
+        int workspaceId = INVALID_STACK_ID;
+        final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                workspaceId = callback.getWindowStackId();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
+            }
+        }
+        if (workspaceId == INVALID_STACK_ID) {
+            return FULLSCREEN_WORKSPACE_STACK_ID;
+        }
+        return workspaceId;
+    }
+
+    void clearContentView() {
+        if (mDecorCaptionView != null) {
+            mDecorCaptionView.removeContentView();
+        } else {
+            // This window doesn't have caption, so we need to just remove the
+            // children of the decor view.
+            removeAllViews();
+        }
+    }
+
+    @Override
+    public void onWindowSizeIsChanging(Rect newBounds) {
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.setTargetRect(newBounds);
+        }
+    }
+
+    @Override
+    public void onWindowDragResizeStart(Rect initialBounds) {
+        if (mWindow.isDestroyed()) {
+            // If the owner's window is gone, we should not be able to come here anymore.
+            releaseThreadedRenderer();
+            return;
+        }
+        if (mBackdropFrameRenderer != null) {
+            return;
+        }
+        final ThreadedRenderer renderer = (ThreadedRenderer) getHardwareRenderer();
+        if (renderer != null) {
+            mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
+                    initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable);
+
+            // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
+            // If we want to get the shadow shown while resizing, we would need to elevate a new
+            // element which owns the caption and has the elevation.
+            updateElevation();
+        }
+    }
+
+    @Override
+    public void onWindowDragResizeEnd() {
+        releaseThreadedRenderer();
+    }
+
+    @Override
+    public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
+        if (mBackdropFrameRenderer == null) {
+            return false;
+        }
+        return mBackdropFrameRenderer.onContentDrawn(offsetX, offsetY, sizeX, sizeY);
+    }
+
+    @Override
+    public void onRequestDraw(boolean reportNextDraw) {
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.onRequestDraw(reportNextDraw);
+        } else if (reportNextDraw) {
+            // If render thread is gone, just report immediately.
+            if (isAttachedToWindow()) {
+                getViewRootImpl().reportDrawFinish();
+            }
+        }
+    }
+
+    /** Release the renderer thread which is usually done when the user stops resizing. */
+    private void releaseThreadedRenderer() {
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.releaseRenderer();
+            mBackdropFrameRenderer = null;
+            // Bring the shadow back.
+            updateElevation();
+        }
+    }
+
+    /**
+     * The elevation gets set for the first time and the framework needs to be informed that
+     * the surface layer gets created with the shadow size in mind.
+     */
+    private void initializeElevation() {
+        // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
+        mAllowUpdateElevation = false;
+        updateElevation();
+    }
+
+    private void updateElevation() {
+        float elevation = 0;
+        final boolean wasAdjustedForStack = mElevationAdjustedForStack;
+        // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
+        // since the shadow is bound to the content size and not the target size.
+        if (ActivityManager.StackId.hasWindowShadow(mStackId)
+                && mBackdropFrameRenderer == null) {
+            elevation = hasWindowFocus() ?
+                    DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
+            // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
+            if (!mAllowUpdateElevation) {
+                elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
+            }
+            // Convert the DP elevation into physical pixels.
+            elevation = dipToPx(elevation);
+            mElevationAdjustedForStack = true;
+        } else {
+            mElevationAdjustedForStack = false;
+        }
+
+        // Don't change the elevation if we didn't previously adjust it for the stack it was in
+        // or it didn't change.
+        if ((wasAdjustedForStack || mElevationAdjustedForStack)
+                && getElevation() != elevation) {
+            mWindow.setElevation(elevation);
+        }
+    }
+
+    boolean isShowingCaption() {
+        return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing();
+    }
+
+    int getCaptionHeight() {
+        return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
+    }
+
+    /**
+     * Converts a DIP measure into physical pixels.
+     * @param dip The dip value.
+     * @return Returns the number of pixels.
+     */
+    private float dipToPx(float dip) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
+                getResources().getDisplayMetrics());
+    }
+
+    private static class ColorViewState {
+        View view = null;
+        int targetVisibility = View.INVISIBLE;
+        boolean present = false;
+
+        final int id;
+        final int systemUiHideFlag;
+        final int translucentFlag;
+        final int verticalGravity;
+        final int horizontalGravity;
+        final String transitionName;
+        final int hideWindowFlag;
+
+        ColorViewState(int systemUiHideFlag,
+                int translucentFlag, int verticalGravity, int horizontalGravity,
+                String transitionName, int id, int hideWindowFlag) {
+            this.id = id;
+            this.systemUiHideFlag = systemUiHideFlag;
+            this.translucentFlag = translucentFlag;
+            this.verticalGravity = verticalGravity;
+            this.horizontalGravity = horizontalGravity;
+            this.transitionName = transitionName;
+            this.hideWindowFlag = hideWindowFlag;
+        }
+    }
+
+    /**
+     * Clears out internal references when the action mode is destroyed.
+     */
+    private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
+        private final ActionMode.Callback mWrapped;
+
+        public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
+            mWrapped = wrapped;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onCreateActionMode(mode, menu);
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            requestFitSystemWindows();
+            return mWrapped.onPrepareActionMode(mode, menu);
+        }
+
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return mWrapped.onActionItemClicked(mode, item);
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+            mWrapped.onDestroyActionMode(mode);
+            final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
+                    >= Build.VERSION_CODES.M;
+            final boolean isPrimary;
+            final boolean isFloating;
+            if (isMncApp) {
+                isPrimary = mode == mPrimaryActionMode;
+                isFloating = mode == mFloatingActionMode;
+                if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
+                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
+                            + mode + " was not the current primary action mode! Expected "
+                            + mPrimaryActionMode);
+                }
+                if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
+                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
+                            + mode + " was not the current floating action mode! Expected "
+                            + mFloatingActionMode);
+                }
+            } else {
+                isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
+                isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
+            }
+            if (isPrimary) {
+                if (mPrimaryActionModePopup != null) {
+                    removeCallbacks(mShowPrimaryActionModePopup);
+                }
+                if (mPrimaryActionModeView != null) {
+                    endOnGoingFadeAnimation();
+                    mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+                            1f, 0f);
+                    mFadeAnim.addListener(new Animator.AnimatorListener() {
+                                @Override
+                                public void onAnimationStart(Animator animation) {
+
+                                }
+
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    mPrimaryActionModeView.setVisibility(GONE);
+                                    if (mPrimaryActionModePopup != null) {
+                                        mPrimaryActionModePopup.dismiss();
+                                    }
+                                    mPrimaryActionModeView.removeAllViews();
+                                    mFadeAnim = null;
+                                }
+
+                                @Override
+                                public void onAnimationCancel(Animator animation) {
+
+                                }
+
+                                @Override
+                                public void onAnimationRepeat(Animator animation) {
+
+                                }
+                            });
+                    mFadeAnim.start();
+                }
+
+                mPrimaryActionMode = null;
+            } else if (isFloating) {
+                cleanupFloatingActionModeViews();
+                mFloatingActionMode = null;
+            }
+            if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+                try {
+                    mWindow.getCallback().onActionModeFinished(mode);
+                } catch (AbstractMethodError ame) {
+                    // Older apps might not implement this callback method.
+                }
+            }
+            requestFitSystemWindows();
+        }
+
+        @Override
+        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+            if (mWrapped instanceof ActionMode.Callback2) {
+                ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
+            } else {
+                super.onGetContentRect(mode, view, outRect);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 83f810f..6e7e5cf 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,24 +16,14 @@
 
 package com.android.internal.policy;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.getMode;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.*;
 
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerNative;
 import android.app.SearchManager;
-import android.os.Build;
 import android.os.UserHandle;
 
-import android.view.ActionMode;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.IRotationWatcher.Stub;
@@ -55,15 +45,9 @@
 import android.view.ViewManager;
 import android.view.ViewParent;
 import android.view.ViewRootImpl;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.Window;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import com.android.internal.R;
-import com.android.internal.view.FloatingActionMode;
-import com.android.internal.view.RootViewSurfaceTaker;
-import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.IconMenuPresenter;
 import com.android.internal.view.menu.ListMenuPresenter;
@@ -72,11 +56,7 @@
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.MenuView;
-import com.android.internal.widget.ActionBarContextView;
-import com.android.internal.widget.BackgroundFallback;
 import com.android.internal.widget.DecorContentParent;
-import com.android.internal.widget.FloatingToolbar;
-import com.android.internal.widget.NonClientDecorView;
 import com.android.internal.widget.SwipeDismissLayout;
 
 import android.app.ActivityManager;
@@ -87,10 +67,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.session.MediaController;
@@ -108,22 +85,17 @@
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
 import android.util.AndroidRuntimeException;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.ImageView;
-import android.widget.PopupWindow;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -142,8 +114,6 @@
 
     private final static String TAG = "PhoneWindow";
 
-    private final static boolean SWEEP_OPEN_MENU = false;
-
     private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
 
     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
@@ -174,32 +144,21 @@
     // view is requested, so we need to force the recreating without introducing an infinite loop.
     private boolean mForceDecorInstall = false;
 
-    // This is the non client decor view for the window, containing the caption and window control
-    // buttons. The visibility of this decor depends on the workspace and the window type.
-    // If the window type does not require such a view, this member might be null.
-    NonClientDecorView mNonClientDecorView;
-
-    // The non client decor needs to adapt to the used workspace. Since querying and changing the
-    // workspace is expensive, this is the workspace value the window is currently set up for.
-    int mWorkspaceId;
-
     // This is the view in which the window contents are placed. It is either
     // mDecor itself, or a child of mDecor where the contents go.
-    private ViewGroup mContentParent;
-
-    private ViewGroup mContentRoot;
+    ViewGroup mContentParent;
 
     Callback2 mTakeSurfaceCallback;
 
     InputQueue.Callback mTakeInputQueueCallback;
 
-    private boolean mIsFloating;
+    boolean mIsFloating;
 
     private LayoutInflater mLayoutInflater;
 
     private TextView mTitleView;
 
-    private DecorContentParent mDecorContentParent;
+    DecorContentParent mDecorContentParent;
     private ActionMenuPresenterCallback mActionMenuPresenterCallback;
     private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
 
@@ -231,13 +190,13 @@
      * multiple panels). Shortcuts will go to this panel. It gets set in
      * {@link #preparePanel} and cleared in {@link #closePanel}.
      */
-    private PanelFeatureState mPreparedPanel;
+    PanelFeatureState mPreparedPanel;
 
     /**
      * The keycode that is currently held down (as a modifier) for chording. If
      * this is 0, there is no key held down.
      */
-    private int mPanelChordingKey;
+    int mPanelChordingKey;
 
     private ImageView mLeftIconView;
 
@@ -247,12 +206,12 @@
 
     private ProgressBar mHorizontalProgressBar;
 
-    private int mBackgroundResource = 0;
-    private int mBackgroundFallbackResource = 0;
+    int mBackgroundResource = 0;
+    int mBackgroundFallbackResource = 0;
 
     private Drawable mBackgroundDrawable;
 
-    private boolean mLoadEleveation = true;
+    private boolean mLoadElevation = true;
     private float mElevation;
 
     /** Whether window content should be clipped to the background outline. */
@@ -261,8 +220,8 @@
     private int mFrameResource = 0;
 
     private int mTextColor = 0;
-    private int mStatusBarColor = 0;
-    private int mNavigationBarColor = 0;
+    int mStatusBarColor = 0;
+    int mNavigationBarColor = 0;
     private boolean mForcedStatusBarColor = false;
     private boolean mForcedNavigationBarColor = false;
 
@@ -272,9 +231,9 @@
 
     private boolean mAlwaysReadCloseOnTouchAttr = false;
 
-    private ContextMenuBuilder mContextMenu;
-    private MenuDialogHelper mContextMenuHelper;
-    private MenuPopupHelper mContextMenuPopupHelper;
+    ContextMenuBuilder mContextMenu;
+    MenuDialogHelper mContextMenuHelper;
+    MenuPopupHelper mContextMenuPopupHelper;
     private boolean mClosingActionMenu;
 
     private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
@@ -312,9 +271,6 @@
     private long mBackgroundFadeDurationMillis = -1;
     private Boolean mSharedElementsUseOverlay;
 
-    private Rect mTempRect;
-    private Rect mOutsets = new Rect();
-
     private boolean mIsStartingWindow;
     private int mTheme = -1;
 
@@ -335,7 +291,7 @@
         if (preservedWindow != null) {
             mDecor = (DecorView) preservedWindow.getDecorView();
             mElevation = preservedWindow.getElevation();
-            mLoadEleveation = false;
+            mLoadElevation = false;
             mForceDecorInstall = true;
             // If we're preserving window, carry over the app token from the preserved
             // window, as we'll be skipping the addView in handleResumeActivity(), and
@@ -508,15 +464,10 @@
         }
     }
 
+    @Override
     public void clearContentView() {
-        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();
+        if (mDecor != null) {
+            mDecor.clearContentView();
         }
     }
 
@@ -730,15 +681,8 @@
                 }
             }
         }
-        if (mNonClientDecorView != null) {
-            int workspaceId = getWorkspaceId();
-            if (mWorkspaceId != workspaceId) {
-                mWorkspaceId = workspaceId;
-                // We might have to change the kind of surface before we do anything else.
-                mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
-                        StackId.hasWindowShadow(mWorkspaceId));
-                mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
-            }
+        if (mDecor != null) {
+            mDecor.onConfigurationChanged();
         }
     }
 
@@ -1170,7 +1114,7 @@
         return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
     }
 
-    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+    boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
             int flags) {
         if (event.isSystem() || (st == null)) {
             return false;
@@ -2261,7 +2205,7 @@
      * called sometime after {@link #restorePanelState} when it is safe to add
      * to the window manager.
      */
-    private void openPanelsAfterRestore() {
+    void openPanelsAfterRestore() {
         PanelFeatureState[] panels = mPanels;
 
         if (panels == null) {
@@ -2332,1530 +2276,6 @@
         }
     }
 
-    private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
-
-        /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
-
-        /** The feature ID of the panel, or -1 if this is the application's DecorView */
-        private final int mFeatureId;
-
-        private final Rect mDrawingBounds = new Rect();
-
-        private final Rect mBackgroundPadding = new Rect();
-
-        private final Rect mFramePadding = new Rect();
-
-        private final Rect mFrameOffsets = new Rect();
-
-        // True if a non client area decor exists.
-        private boolean mHasNonClientDecor = false;
-
-        private boolean mChanging;
-
-        private Drawable mMenuBackground;
-        private boolean mWatchingForMenu;
-        private int mDownY;
-
-        private ActionMode mPrimaryActionMode;
-        private ActionMode mFloatingActionMode;
-        private ActionBarContextView mPrimaryActionModeView;
-        private PopupWindow mPrimaryActionModePopup;
-        private Runnable mShowPrimaryActionModePopup;
-        private OnPreDrawListener mFloatingToolbarPreDrawListener;
-        private View mFloatingActionModeOriginatingView;
-        private FloatingToolbar mFloatingToolbar;
-        private ObjectAnimator mFadeAnim;
-
-        // View added at runtime to draw under the status bar area
-        private View mStatusGuard;
-        // View added at runtime to draw under the navigation bar area
-        private View mNavigationGuard;
-
-        private final ColorViewState mStatusColorViewState = new ColorViewState(
-                SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
-                Gravity.TOP,
-                Gravity.LEFT,
-                STATUS_BAR_BACKGROUND_TRANSITION_NAME,
-                com.android.internal.R.id.statusBarBackground,
-                FLAG_FULLSCREEN);
-        private final ColorViewState mNavigationColorViewState = new ColorViewState(
-                SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
-                Gravity.BOTTOM,
-                Gravity.RIGHT,
-                NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
-                com.android.internal.R.id.navigationBarBackground,
-                0 /* hideWindowFlag */);
-
-        private final Interpolator mShowInterpolator;
-        private final Interpolator mHideInterpolator;
-        private final int mBarEnterExitDuration;
-
-        private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
-
-        private int mLastTopInset = 0;
-        private int mLastBottomInset = 0;
-        private int mLastRightInset = 0;
-        private boolean mLastHasTopStableInset = false;
-        private boolean mLastHasBottomStableInset = false;
-        private boolean mLastHasRightStableInset = false;
-        private int mLastWindowFlags = 0;
-
-        private int mRootScrollY = 0;
-
-        private PhoneWindow mWindow;
-
-        private DecorView(Context context, int featureId, PhoneWindow window) {
-            super(context);
-            mFeatureId = featureId;
-
-            mShowInterpolator = AnimationUtils.loadInterpolator(context,
-                    android.R.interpolator.linear_out_slow_in);
-            mHideInterpolator = AnimationUtils.loadInterpolator(context,
-                    android.R.interpolator.fast_out_linear_in);
-
-            mBarEnterExitDuration = context.getResources().getInteger(
-                    R.integer.dock_enter_exit_duration);
-
-            setWindow(window);
-        }
-
-        public void setBackgroundFallback(int resId) {
-            mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
-            setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
-        }
-
-        @Override
-        public void onDraw(Canvas c) {
-            super.onDraw(c);
-            mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent);
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            final int keyCode = event.getKeyCode();
-            final int action = event.getAction();
-            final boolean isDown = action == KeyEvent.ACTION_DOWN;
-
-            if (isDown && (event.getRepeatCount() == 0)) {
-                // First handle chording of panel key: if a panel key is held
-                // but not released, try to execute a shortcut in it.
-                if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
-                    boolean handled = dispatchKeyShortcutEvent(event);
-                    if (handled) {
-                        return true;
-                    }
-                }
-
-                // If a panel is open, perform a shortcut on it without the
-                // chorded panel key
-                if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
-                    if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
-                        return true;
-                    }
-                }
-            }
-
-            if (!mWindow.isDestroyed()) {
-                final Callback cb = mWindow.getCallback();
-                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
-                        : super.dispatchKeyEvent(event);
-                if (handled) {
-                    return true;
-                }
-            }
-
-            return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
-                    : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
-        }
-
-        @Override
-        public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
-            // If the panel is already prepared, then perform the shortcut using it.
-            boolean handled;
-            if (mWindow.mPreparedPanel != null) {
-                handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
-                        Menu.FLAG_PERFORM_NO_CLOSE);
-                if (handled) {
-                    if (mWindow.mPreparedPanel != null) {
-                        mWindow.mPreparedPanel.isHandled = true;
-                    }
-                    return true;
-                }
-            }
-
-            // Shortcut not handled by the panel.  Dispatch to the view hierarchy.
-            final Callback cb = mWindow.getCallback();
-            handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
-            if (handled) {
-                return true;
-            }
-
-            // If the panel is not prepared, then we may be trying to handle a shortcut key
-            // combination such as Control+C.  Temporarily prepare the panel then mark it
-            // unprepared again when finished to ensure that the panel will again be prepared
-            // the next time it is shown for real.
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (st != null && mWindow.mPreparedPanel == null) {
-                mWindow.preparePanel(st, ev);
-                handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
-                        Menu.FLAG_PERFORM_NO_CLOSE);
-                st.isPrepared = false;
-                if (handled) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        public boolean dispatchTrackballEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
-        }
-
-        @Override
-        public boolean dispatchGenericMotionEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
-        }
-
-        public boolean superDispatchKeyEvent(KeyEvent event) {
-            // Give priority to closing action modes if applicable.
-            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-                final int action = event.getAction();
-                // Back cancels action modes first.
-                if (mPrimaryActionMode != null) {
-                    if (action == KeyEvent.ACTION_UP) {
-                        mPrimaryActionMode.finish();
-                    }
-                    return true;
-                }
-            }
-
-            return super.dispatchKeyEvent(event);
-        }
-
-        public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
-            return super.dispatchKeyShortcutEvent(event);
-        }
-
-        public boolean superDispatchTouchEvent(MotionEvent event) {
-            return super.dispatchTouchEvent(event);
-        }
-
-        public boolean superDispatchTrackballEvent(MotionEvent event) {
-            return super.dispatchTrackballEvent(event);
-        }
-
-        public boolean superDispatchGenericMotionEvent(MotionEvent event) {
-            return super.dispatchGenericMotionEvent(event);
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            return onInterceptTouchEvent(event);
-        }
-
-        private boolean isOutOfInnerBounds(int x, int y) {
-            return x < 0 || y < 0 || x > getWidth() || y > getHeight();
-        }
-
-        private boolean isOutOfBounds(int x, int y) {
-            return x < -5 || y < -5 || x > (getWidth() + 5)
-                    || y > (getHeight() + 5);
-        }
-
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            int action = event.getAction();
-            if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
-                // Don't dispatch ACTION_DOWN to the non client decor if the window is
-                // resizable and the event was (starting) outside the window.
-                // Window resizing events should be handled by WindowManager.
-                // TODO: Investigate how to handle the outside touch in window manager
-                //       without generating these events.
-                //       Currently we receive these because we need to enlarge the window's
-                //       touch region so that the monitor channel receives the events
-                //       in the outside touch area.
-                if (action == MotionEvent.ACTION_DOWN) {
-                    final int x = (int) event.getX();
-                    final int y = (int) event.getY();
-                    if (isOutOfInnerBounds(x, y)) {
-                        return true;
-                    }
-                }
-            }
-
-            if (mFeatureId >= 0) {
-                if (action == MotionEvent.ACTION_DOWN) {
-                    int x = (int)event.getX();
-                    int y = (int)event.getY();
-                    if (isOutOfBounds(x, y)) {
-                        mWindow.closePanel(mFeatureId);
-                        return true;
-                    }
-                }
-            }
-
-            if (!SWEEP_OPEN_MENU) {
-                return false;
-            }
-
-            if (mFeatureId >= 0) {
-                if (action == MotionEvent.ACTION_DOWN) {
-                    Log.i(TAG, "Watchiing!");
-                    mWatchingForMenu = true;
-                    mDownY = (int) event.getY();
-                    return false;
-                }
-
-                if (!mWatchingForMenu) {
-                    return false;
-                }
-
-                int y = (int)event.getY();
-                if (action == MotionEvent.ACTION_MOVE) {
-                    if (y > (mDownY+30)) {
-                        Log.i(TAG, "Closing!");
-                        mWindow.closePanel(mFeatureId);
-                        mWatchingForMenu = false;
-                        return true;
-                    }
-                } else if (action == MotionEvent.ACTION_UP) {
-                    mWatchingForMenu = false;
-                }
-
-                return false;
-            }
-
-            //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
-            //        + " (in " + getHeight() + ")");
-
-            if (action == MotionEvent.ACTION_DOWN) {
-                int y = (int)event.getY();
-                if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
-                    Log.i(TAG, "Watching!");
-                    mWatchingForMenu = true;
-                }
-                return false;
-            }
-
-            if (!mWatchingForMenu) {
-                return false;
-            }
-
-            int y = (int)event.getY();
-            if (action == MotionEvent.ACTION_MOVE) {
-                if (y < (getHeight()-30)) {
-                    Log.i(TAG, "Opening!");
-                    mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
-                            KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
-                    mWatchingForMenu = false;
-                    return true;
-                }
-            } else if (action == MotionEvent.ACTION_UP) {
-                mWatchingForMenu = false;
-            }
-
-            return false;
-        }
-
-        @Override
-        public void sendAccessibilityEvent(int eventType) {
-            if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
-                return;
-            }
-
-            // if we are showing a feature that should be announced and one child
-            // make this child the event source since this is the feature itself
-            // otherwise the callback will take over and announce its client
-            if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
-                    mFeatureId == FEATURE_CONTEXT_MENU ||
-                    mFeatureId == FEATURE_PROGRESS ||
-                    mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
-                    && getChildCount() == 1) {
-                getChildAt(0).sendAccessibilityEvent(eventType);
-            } else {
-                super.sendAccessibilityEvent(eventType);
-            }
-        }
-
-        @Override
-        public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed()) {
-                if (cb.dispatchPopulateAccessibilityEvent(event)) {
-                    return true;
-                }
-            }
-            return super.dispatchPopulateAccessibilityEventInternal(event);
-        }
-
-        @Override
-        protected boolean setFrame(int l, int t, int r, int b) {
-            boolean changed = super.setFrame(l, t, r, b);
-            if (changed) {
-                final Rect drawingBounds = mDrawingBounds;
-                getDrawingRect(drawingBounds);
-
-                Drawable fg = getForeground();
-                if (fg != null) {
-                    final Rect frameOffsets = mFrameOffsets;
-                    drawingBounds.left += frameOffsets.left;
-                    drawingBounds.top += frameOffsets.top;
-                    drawingBounds.right -= frameOffsets.right;
-                    drawingBounds.bottom -= frameOffsets.bottom;
-                    fg.setBounds(drawingBounds);
-                    final Rect framePadding = mFramePadding;
-                    drawingBounds.left += framePadding.left - frameOffsets.left;
-                    drawingBounds.top += framePadding.top - frameOffsets.top;
-                    drawingBounds.right -= framePadding.right - frameOffsets.right;
-                    drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
-                }
-
-                Drawable bg = getBackground();
-                if (bg != null) {
-                    bg.setBounds(drawingBounds);
-                }
-
-                if (SWEEP_OPEN_MENU) {
-                    if (mMenuBackground == null && mFeatureId < 0
-                            && mWindow.getAttributes().height
-                            == WindowManager.LayoutParams.MATCH_PARENT) {
-                        mMenuBackground = getContext().getDrawable(
-                                R.drawable.menu_background);
-                    }
-                    if (mMenuBackground != null) {
-                        mMenuBackground.setBounds(drawingBounds.left,
-                                drawingBounds.bottom-6, drawingBounds.right,
-                                drawingBounds.bottom+20);
-                    }
-                }
-            }
-            return changed;
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
-            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
-
-            final int widthMode = getMode(widthMeasureSpec);
-            final int heightMode = getMode(heightMeasureSpec);
-
-            boolean fixedWidth = false;
-            if (widthMode == AT_MOST) {
-                final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
-                        : mWindow.mFixedWidthMajor;
-                if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
-                    final int w;
-                    if (tvw.type == TypedValue.TYPE_DIMENSION) {
-                        w = (int) tvw.getDimension(metrics);
-                    } else if (tvw.type == TypedValue.TYPE_FRACTION) {
-                        w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
-                    } else {
-                        w = 0;
-                    }
-
-                    if (w > 0) {
-                        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                                Math.min(w, widthSize), EXACTLY);
-                        fixedWidth = true;
-                    }
-                }
-            }
-
-            if (heightMode == AT_MOST) {
-                final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
-                        : mWindow.mFixedHeightMinor;
-                if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
-                    final int h;
-                    if (tvh.type == TypedValue.TYPE_DIMENSION) {
-                        h = (int) tvh.getDimension(metrics);
-                    } else if (tvh.type == TypedValue.TYPE_FRACTION) {
-                        h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
-                    } else {
-                        h = 0;
-                    }
-                    if (h > 0) {
-                        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-                        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                                Math.min(h, heightSize), EXACTLY);
-                    }
-                }
-            }
-
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) {
-                int mode = MeasureSpec.getMode(heightMeasureSpec);
-                if (mode != MeasureSpec.UNSPECIFIED) {
-                    int height = MeasureSpec.getSize(heightMeasureSpec);
-                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode);
-                }
-            }
-            if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) {
-                int mode = MeasureSpec.getMode(widthMeasureSpec);
-                if (mode != MeasureSpec.UNSPECIFIED) {
-                    int width = MeasureSpec.getSize(widthMeasureSpec);
-                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode);
-                }
-            }
-
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-            int width = getMeasuredWidth();
-            boolean measure = false;
-
-            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
-
-            if (!fixedWidth && widthMode == AT_MOST) {
-                final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
-                if (tv.type != TypedValue.TYPE_NULL) {
-                    final int min;
-                    if (tv.type == TypedValue.TYPE_DIMENSION) {
-                        min = (int)tv.getDimension(metrics);
-                    } else if (tv.type == TypedValue.TYPE_FRACTION) {
-                        min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
-                    } else {
-                        min = 0;
-                    }
-
-                    if (width < min) {
-                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
-                        measure = true;
-                    }
-                }
-            }
-
-            // TODO: Support height?
-
-            if (measure) {
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
-        }
-
-        @Override
-        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-            super.onLayout(changed, left, top, right, bottom);
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.left > 0) {
-                offsetLeftAndRight(-mWindow.mOutsets.left);
-            }
-            if (mWindow.mOutsets.top > 0) {
-                offsetTopAndBottom(-mWindow.mOutsets.top);
-            }
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            super.draw(canvas);
-
-            if (mMenuBackground != null) {
-                mMenuBackground.draw(canvas);
-            }
-        }
-
-        @Override
-        public boolean showContextMenuForChild(View originalView) {
-            // Reuse the context menu builder
-            if (mWindow.mContextMenu == null) {
-                mWindow.mContextMenu = new ContextMenuBuilder(getContext());
-                mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
-            } else {
-                mWindow.mContextMenu.clearAll();
-            }
-
-            final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
-                    originalView.getWindowToken());
-            if (helper != null) {
-                helper.setPresenterCallback(mWindow.mContextMenuCallback);
-            } else if (mWindow.mContextMenuHelper != null) {
-                // No menu to show, but if we have a menu currently showing it just became blank.
-                // Close it.
-                mWindow.mContextMenuHelper.dismiss();
-            }
-            mWindow.mContextMenuHelper = helper;
-            return helper != null;
-        }
-
-        @Override
-        public boolean showContextMenuForChild(View originalView, float x, float y) {
-            // Reuse the context menu builder
-            if (mWindow.mContextMenu == null) {
-                mWindow.mContextMenu = new ContextMenuBuilder(getContext());
-                mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
-            } else {
-                mWindow.mContextMenu.clearAll();
-            }
-
-            final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
-                    getContext(), originalView, x, y);
-            if (helper != null) {
-                helper.setCallback(mWindow.mContextMenuCallback);
-            } else if (mWindow.mContextMenuPopupHelper != null) {
-                // No menu to show, but if we have a menu currently showing it just became blank.
-                // Close it.
-                mWindow.mContextMenuPopupHelper.dismiss();
-            }
-            mWindow.mContextMenuPopupHelper = helper;
-            return helper != null;
-        }
-
-        @Override
-        public ActionMode startActionModeForChild(View originalView,
-                ActionMode.Callback callback) {
-            return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
-        }
-
-        @Override
-        public ActionMode startActionModeForChild(
-                View child, ActionMode.Callback callback, int type) {
-            return startActionMode(child, callback, type);
-        }
-
-        @Override
-        public ActionMode startActionMode(ActionMode.Callback callback) {
-            return startActionMode(callback, ActionMode.TYPE_PRIMARY);
-        }
-
-        @Override
-        public ActionMode startActionMode(ActionMode.Callback callback, int type) {
-            return startActionMode(this, callback, type);
-        }
-
-        private ActionMode startActionMode(
-                View originatingView, ActionMode.Callback callback, int type) {
-            ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
-            ActionMode mode = null;
-            if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                try {
-                    mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
-                } catch (AbstractMethodError ame) {
-                    // Older apps might not implement the typed version of this method.
-                    if (type == ActionMode.TYPE_PRIMARY) {
-                        try {
-                            mode = mWindow.getCallback().onWindowStartingActionMode(
-                                    wrappedCallback);
-                        } catch (AbstractMethodError ame2) {
-                            // Older apps might not implement this callback method at all.
-                        }
-                    }
-                }
-            }
-            if (mode != null) {
-                if (mode.getType() == ActionMode.TYPE_PRIMARY) {
-                    cleanupPrimaryActionMode();
-                    mPrimaryActionMode = mode;
-                } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
-                    if (mFloatingActionMode != null) {
-                        mFloatingActionMode.finish();
-                    }
-                    mFloatingActionMode = mode;
-                }
-            } else {
-                mode = createActionMode(type, wrappedCallback, originatingView);
-                if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
-                    setHandledActionMode(mode);
-                } else {
-                    mode = null;
-                }
-            }
-            if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                try {
-                    mWindow.getCallback().onActionModeStarted(mode);
-                } catch (AbstractMethodError ame) {
-                    // Older apps might not implement this callback method.
-                }
-            }
-            return mode;
-        }
-
-        private void cleanupPrimaryActionMode() {
-            if (mPrimaryActionMode != null) {
-                mPrimaryActionMode.finish();
-                mPrimaryActionMode = null;
-            }
-            if (mPrimaryActionModeView != null) {
-                mPrimaryActionModeView.killMode();
-            }
-        }
-
-        private void cleanupFloatingActionModeViews() {
-            if (mFloatingToolbar != null) {
-                mFloatingToolbar.dismiss();
-                mFloatingToolbar = null;
-            }
-            if (mFloatingActionModeOriginatingView != null) {
-                if (mFloatingToolbarPreDrawListener != null) {
-                    mFloatingActionModeOriginatingView.getViewTreeObserver()
-                        .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
-                    mFloatingToolbarPreDrawListener = null;
-                }
-                mFloatingActionModeOriginatingView = null;
-            }
-        }
-
-        public void startChanging() {
-            mChanging = true;
-        }
-
-        public void finishChanging() {
-            mChanging = false;
-            drawableChanged();
-        }
-
-        public void setWindowBackground(Drawable drawable) {
-            if (getBackground() != drawable) {
-                setBackgroundDrawable(drawable);
-                if (drawable != null) {
-                    drawable.getPadding(mBackgroundPadding);
-                } else {
-                    mBackgroundPadding.setEmpty();
-                }
-                drawableChanged();
-            }
-        }
-
-        public void setWindowFrame(Drawable drawable) {
-            if (getForeground() != drawable) {
-                setForeground(drawable);
-                if (drawable != null) {
-                    drawable.getPadding(mFramePadding);
-                } else {
-                    mFramePadding.setEmpty();
-                }
-                drawableChanged();
-            }
-        }
-
-        @Override
-        public void onWindowSystemUiVisibilityChanged(int visible) {
-            updateColorViews(null /* insets */, true /* animate */);
-        }
-
-        @Override
-        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-            mFrameOffsets.set(insets.getSystemWindowInsets());
-            insets = updateColorViews(insets, true /* animate */);
-            insets = updateStatusGuard(insets);
-            updateNavigationGuard(insets);
-            if (getForeground() != null) {
-                drawableChanged();
-            }
-            return insets;
-        }
-
-        @Override
-        public boolean isTransitionGroup() {
-            return false;
-        }
-
-        private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
-            WindowManager.LayoutParams attrs = mWindow.getAttributes();
-            int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
-
-            if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
-                boolean disallowAnimate = !isLaidOut();
-                disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
-                        & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-                mLastWindowFlags = attrs.flags;
-
-                if (insets != null) {
-                    mLastTopInset = Math.min(insets.getStableInsetTop(),
-                            insets.getSystemWindowInsetTop());
-                    mLastBottomInset = Math.min(insets.getStableInsetBottom(),
-                            insets.getSystemWindowInsetBottom());
-                    mLastRightInset = Math.min(insets.getStableInsetRight(),
-                            insets.getSystemWindowInsetRight());
-
-                    // Don't animate if the presence of stable insets has changed, because that
-                    // indicates that the window was either just added and received them for the
-                    // first time, or the window size or position has changed.
-                    boolean hasTopStableInset = insets.getStableInsetTop() != 0;
-                    disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
-                    mLastHasTopStableInset = hasTopStableInset;
-
-                    boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
-                    disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
-                    mLastHasBottomStableInset = hasBottomStableInset;
-
-                    boolean hasRightStableInset = insets.getStableInsetRight() != 0;
-                    disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
-                    mLastHasRightStableInset = hasRightStableInset;
-                }
-
-                boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
-                int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
-                updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
-                        mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
-                        0 /* rightInset */, animate && !disallowAnimate);
-
-                boolean statusBarNeedsRightInset = navBarToRightEdge
-                        && mNavigationColorViewState.present;
-                int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
-                updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
-                        mLastTopInset, false /* matchVertical */, statusBarRightInset,
-                        animate && !disallowAnimate);
-            }
-
-            // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
-            // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
-            // explicitly asked for it.
-
-            boolean consumingNavBar =
-                    (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                            && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
-                            && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
-
-            int consumedRight = consumingNavBar ? mLastRightInset : 0;
-            int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
-
-            if (mWindow.mContentRoot != null
-                    && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
-                MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams();
-                if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
-                    lp.rightMargin = consumedRight;
-                    lp.bottomMargin = consumedBottom;
-                    mWindow.mContentRoot.setLayoutParams(lp);
-
-                    if (insets == null) {
-                        // The insets have changed, but we're not currently in the process
-                        // of dispatching them.
-                        requestApplyInsets();
-                    }
-                }
-                if (insets != null) {
-                    insets = insets.replaceSystemWindowInsets(
-                            insets.getSystemWindowInsetLeft(),
-                            insets.getSystemWindowInsetTop(),
-                            insets.getSystemWindowInsetRight() - consumedRight,
-                            insets.getSystemWindowInsetBottom() - consumedBottom);
-                }
-            }
-
-            if (insets != null) {
-                insets = insets.consumeStableInsets();
-            }
-            return insets;
-        }
-
-        /**
-         * Update a color view
-         *
-         * @param state the color view to update.
-         * @param sysUiVis the current systemUiVisibility to apply.
-         * @param color the current color to apply.
-         * @param size the current size in the non-parent-matching dimension.
-         * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
-         *                    horizontal edge,
-         * @param rightMargin rightMargin for the color view.
-         * @param animate if true, the change will be animated.
-         */
-        private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
-                int size, boolean verticalBar, int rightMargin, boolean animate) {
-            state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
-                    && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
-                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-            boolean show = state.present
-                    && (color & Color.BLACK) != 0
-                    && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
-
-            boolean visibilityChanged = false;
-            View view = state.view;
-
-            int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
-            int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
-            int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
-
-            if (view == null) {
-                if (show) {
-                    state.view = view = new View(mContext);
-                    view.setBackgroundColor(color);
-                    view.setTransitionName(state.transitionName);
-                    view.setId(state.id);
-                    visibilityChanged = true;
-                    view.setVisibility(INVISIBLE);
-                    state.targetVisibility = VISIBLE;
-
-                    LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
-                            resolvedGravity);
-                    lp.rightMargin = rightMargin;
-                    addView(view, lp);
-                    updateColorViewTranslations();
-                }
-            } else {
-                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) {
-                    view.setBackgroundColor(color);
-                }
-            }
-            if (visibilityChanged) {
-                view.animate().cancel();
-                if (animate) {
-                    if (show) {
-                        if (view.getVisibility() != VISIBLE) {
-                            view.setVisibility(VISIBLE);
-                            view.setAlpha(0.0f);
-                        }
-                        view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
-                                setDuration(mBarEnterExitDuration);
-                    } else {
-                        view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
-                                .setDuration(mBarEnterExitDuration)
-                                .withEndAction(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        state.view.setAlpha(1.0f);
-                                        state.view.setVisibility(INVISIBLE);
-                                    }
-                                });
-                    }
-                } else {
-                    view.setAlpha(1.0f);
-                    view.setVisibility(show ? VISIBLE : INVISIBLE);
-                }
-            }
-        }
-
-        private void updateColorViewTranslations() {
-            // Put the color views back in place when they get moved off the screen
-            // due to the the ViewRootImpl panning.
-            int rootScrollY = mRootScrollY;
-            if (mStatusColorViewState.view != null) {
-                mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
-            }
-            if (mNavigationColorViewState.view != null) {
-                mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
-            }
-        }
-
-        private WindowInsets updateStatusGuard(WindowInsets insets) {
-            boolean showStatusGuard = false;
-            // Show the status guard when the non-overlay contextual action bar is showing
-            if (mPrimaryActionModeView != null) {
-                if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
-                    // Insets are magic!
-                    final MarginLayoutParams mlp = (MarginLayoutParams)
-                            mPrimaryActionModeView.getLayoutParams();
-                    boolean mlpChanged = false;
-                    if (mPrimaryActionModeView.isShown()) {
-                        if (mWindow.mTempRect == null) {
-                            mWindow.mTempRect = new Rect();
-                        }
-                        final Rect rect = mWindow.mTempRect;
-
-                        // If the parent doesn't consume the insets, manually
-                        // apply the default system window insets.
-                        mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
-                        final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
-                        if (mlp.topMargin != newMargin) {
-                            mlpChanged = true;
-                            mlp.topMargin = insets.getSystemWindowInsetTop();
-
-                            if (mStatusGuard == null) {
-                                mStatusGuard = new View(mContext);
-                                mStatusGuard.setBackgroundColor(mContext.getColor(
-                                        R.color.input_method_navigation_guard));
-                                addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
-                                        new LayoutParams(LayoutParams.MATCH_PARENT,
-                                                mlp.topMargin, Gravity.START | Gravity.TOP));
-                            } else {
-                                final LayoutParams lp = (LayoutParams)
-                                        mStatusGuard.getLayoutParams();
-                                if (lp.height != mlp.topMargin) {
-                                    lp.height = mlp.topMargin;
-                                    mStatusGuard.setLayoutParams(lp);
-                                }
-                            }
-                        }
-
-                        // The action mode's theme may differ from the app, so
-                        // always show the status guard above it if we have one.
-                        showStatusGuard = mStatusGuard != null;
-
-                        // We only need to consume the insets if the action
-                        // mode is overlaid on the app content (e.g. it's
-                        // sitting in a FrameLayout, see
-                        // screen_simple_overlay_action_mode.xml).
-                        final boolean nonOverlay = (mWindow.getLocalFeatures()
-                                & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
-                        insets = insets.consumeSystemWindowInsets(
-                                false, nonOverlay && showStatusGuard /* top */, false, false);
-                    } else {
-                        // reset top margin
-                        if (mlp.topMargin != 0) {
-                            mlpChanged = true;
-                            mlp.topMargin = 0;
-                        }
-                    }
-                    if (mlpChanged) {
-                        mPrimaryActionModeView.setLayoutParams(mlp);
-                    }
-                }
-            }
-            if (mStatusGuard != null) {
-                mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
-            }
-            return insets;
-        }
-
-        private void updateNavigationGuard(WindowInsets insets) {
-            // IMEs lay out below the nav bar, but the content view must not (for back compat)
-            if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
-                // prevent the content view from including the nav bar height
-                if (mWindow.mContentParent != null) {
-                    if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
-                        MarginLayoutParams mlp =
-                                (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
-                        mlp.bottomMargin = insets.getSystemWindowInsetBottom();
-                        mWindow.mContentParent.setLayoutParams(mlp);
-                    }
-                }
-                // position the navigation guard view, creating it if necessary
-                if (mNavigationGuard == null) {
-                    mNavigationGuard = new View(mContext);
-                    mNavigationGuard.setBackgroundColor(mContext.getColor(
-                            R.color.input_method_navigation_guard));
-                    addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
-                            new LayoutParams(LayoutParams.MATCH_PARENT,
-                                    insets.getSystemWindowInsetBottom(),
-                                    Gravity.START | Gravity.BOTTOM));
-                } else {
-                    LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
-                    lp.height = insets.getSystemWindowInsetBottom();
-                    mNavigationGuard.setLayoutParams(lp);
-                }
-            }
-        }
-
-        private void drawableChanged() {
-            if (mChanging) {
-                return;
-            }
-
-            setPadding(mFramePadding.left + mBackgroundPadding.left,
-                    mFramePadding.top + mBackgroundPadding.top,
-                    mFramePadding.right + mBackgroundPadding.right,
-                    mFramePadding.bottom + mBackgroundPadding.bottom);
-            requestLayout();
-            invalidate();
-
-            int opacity = PixelFormat.OPAQUE;
-            if (windowHasShadow()) {
-                // If the window has a shadow, it must be translucent.
-                opacity = PixelFormat.TRANSLUCENT;
-            } else{
-                // Note: If there is no background, we will assume opaque. The
-                // common case seems to be that an application sets there to be
-                // no background so it can draw everything itself. For that,
-                // we would like to assume OPAQUE and let the app force it to
-                // the slower TRANSLUCENT mode if that is really what it wants.
-                Drawable bg = getBackground();
-                Drawable fg = getForeground();
-                if (bg != null) {
-                    if (fg == null) {
-                        opacity = bg.getOpacity();
-                    } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
-                            && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
-                        // If the frame padding is zero, then we can be opaque
-                        // if either the frame -or- the background is opaque.
-                        int fop = fg.getOpacity();
-                        int bop = bg.getOpacity();
-                        if (false)
-                            Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
-                        if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
-                            opacity = PixelFormat.OPAQUE;
-                        } else if (fop == PixelFormat.UNKNOWN) {
-                            opacity = bop;
-                        } else if (bop == PixelFormat.UNKNOWN) {
-                            opacity = fop;
-                        } else {
-                            opacity = Drawable.resolveOpacity(fop, bop);
-                        }
-                    } else {
-                        // For now we have to assume translucent if there is a
-                        // frame with padding... there is no way to tell if the
-                        // frame and background together will draw all pixels.
-                        if (false)
-                            Log.v(TAG, "Padding: " + mFramePadding);
-                        opacity = PixelFormat.TRANSLUCENT;
-                    }
-                }
-                if (false)
-                    Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
-            }
-
-            if (false)
-                Log.v(TAG, "Selected default opacity: " + opacity);
-
-            mDefaultOpacity = opacity;
-            if (mFeatureId < 0) {
-                mWindow.setDefaultWindowFormat(opacity);
-            }
-        }
-
-        @Override
-        public void onWindowFocusChanged(boolean hasWindowFocus) {
-            super.onWindowFocusChanged(hasWindowFocus);
-
-            // If the user is chording a menu shortcut, release the chord since
-            // this window lost focus
-            if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus
-                    && mWindow.mPanelChordingKey != 0) {
-                mWindow.closePanel(FEATURE_OPTIONS_PANEL);
-            }
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
-                cb.onWindowFocusChanged(hasWindowFocus);
-            }
-
-            if (mPrimaryActionMode != null) {
-                mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
-            }
-            if (mFloatingActionMode != null) {
-                mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
-            }
-        }
-
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
-                cb.onAttachedToWindow();
-            }
-
-            if (mFeatureId == -1) {
-                /*
-                 * The main window has been attached, try to restore any panels
-                 * that may have been open before. This is called in cases where
-                 * an activity is being killed for configuration change and the
-                 * menu was open. When the activity is recreated, the menu
-                 * should be shown again.
-                 */
-                mWindow.openPanelsAfterRestore();
-            }
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && mFeatureId < 0) {
-                cb.onDetachedFromWindow();
-            }
-
-            if (mWindow.mDecorContentParent != null) {
-                mWindow.mDecorContentParent.dismissPopups();
-            }
-
-            if (mPrimaryActionModePopup != null) {
-                removeCallbacks(mShowPrimaryActionModePopup);
-                if (mPrimaryActionModePopup.isShowing()) {
-                    mPrimaryActionModePopup.dismiss();
-                }
-                mPrimaryActionModePopup = null;
-            }
-            if (mFloatingToolbar != null) {
-                mFloatingToolbar.dismiss();
-                mFloatingToolbar = null;
-            }
-
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (st != null && st.menu != null && mFeatureId < 0) {
-                st.menu.close();
-            }
-        }
-
-        @Override
-        public void onCloseSystemDialogs(String reason) {
-            if (mFeatureId >= 0) {
-                mWindow.closeAllPanels();
-            }
-        }
-
-        public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
-            return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
-        }
-
-        public InputQueue.Callback willYouTakeTheInputQueue() {
-            return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
-        }
-
-        public void setSurfaceType(int type) {
-            mWindow.setType(type);
-        }
-
-        public void setSurfaceFormat(int format) {
-            mWindow.setFormat(format);
-        }
-
-        public void setSurfaceKeepScreenOn(boolean keepOn) {
-            if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        }
-
-        @Override
-        public void onRootViewScrollYChanged(int rootScrollY) {
-            mRootScrollY = rootScrollY;
-            updateColorViewTranslations();
-        }
-
-        private ActionMode createActionMode(
-                int type, ActionMode.Callback2 callback, View originatingView) {
-            switch (type) {
-                case ActionMode.TYPE_PRIMARY:
-                default:
-                    return createStandaloneActionMode(callback);
-                case ActionMode.TYPE_FLOATING:
-                    return createFloatingActionMode(originatingView, callback);
-            }
-        }
-
-        private void setHandledActionMode(ActionMode mode) {
-            if (mode.getType() == ActionMode.TYPE_PRIMARY) {
-                setHandledPrimaryActionMode(mode);
-            } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
-                setHandledFloatingActionMode(mode);
-            }
-        }
-
-        private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
-            endOnGoingFadeAnimation();
-            cleanupPrimaryActionMode();
-            if (mPrimaryActionModeView == null) {
-                if (mWindow.isFloating()) {
-                    // Use the action bar theme.
-                    final TypedValue outValue = new TypedValue();
-                    final Theme baseTheme = mContext.getTheme();
-                    baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
-                    final Context actionBarContext;
-                    if (outValue.resourceId != 0) {
-                        final Theme actionBarTheme = mContext.getResources().newTheme();
-                        actionBarTheme.setTo(baseTheme);
-                        actionBarTheme.applyStyle(outValue.resourceId, true);
-
-                        actionBarContext = new ContextThemeWrapper(mContext, 0);
-                        actionBarContext.getTheme().setTo(actionBarTheme);
-                    } else {
-                        actionBarContext = mContext;
-                    }
-
-                    mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
-                    mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
-                            R.attr.actionModePopupWindowStyle);
-                    mPrimaryActionModePopup.setWindowLayoutType(
-                            WindowManager.LayoutParams.TYPE_APPLICATION);
-                    mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
-                    mPrimaryActionModePopup.setWidth(MATCH_PARENT);
-
-                    actionBarContext.getTheme().resolveAttribute(
-                            R.attr.actionBarSize, outValue, true);
-                    final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
-                            actionBarContext.getResources().getDisplayMetrics());
-                    mPrimaryActionModeView.setContentHeight(height);
-                    mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
-                    mShowPrimaryActionModePopup = new Runnable() {
-                        public void run() {
-                            mPrimaryActionModePopup.showAtLocation(
-                                    mPrimaryActionModeView.getApplicationWindowToken(),
-                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
-                            endOnGoingFadeAnimation();
-                            mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
-                                    0f, 1f);
-                            mFadeAnim.addListener(new Animator.AnimatorListener() {
-                                @Override
-                                public void onAnimationStart(Animator animation) {
-                                    mPrimaryActionModeView.setVisibility(VISIBLE);
-                                }
-
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    mPrimaryActionModeView.setAlpha(1f);
-                                    mFadeAnim = null;
-                                }
-
-                                @Override
-                                public void onAnimationCancel(Animator animation) {
-
-                                }
-
-                                @Override
-                                public void onAnimationRepeat(Animator animation) {
-
-                                }
-                            });
-                            mFadeAnim.start();
-                        }
-                    };
-                } else {
-                    ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
-                    if (stub != null) {
-                        mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
-                    }
-                }
-            }
-            if (mPrimaryActionModeView != null) {
-                mPrimaryActionModeView.killMode();
-                ActionMode mode = new StandaloneActionMode(
-                        mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
-                        callback, mPrimaryActionModePopup == null);
-                return mode;
-            }
-            return null;
-        }
-
-        private void endOnGoingFadeAnimation() {
-            if (mFadeAnim != null) {
-                mFadeAnim.end();
-            }
-        }
-
-        private void setHandledPrimaryActionMode(ActionMode mode) {
-            endOnGoingFadeAnimation();
-            mPrimaryActionMode = mode;
-            mPrimaryActionMode.invalidate();
-            mPrimaryActionModeView.initForMode(mPrimaryActionMode);
-            if (mPrimaryActionModePopup != null) {
-                post(mShowPrimaryActionModePopup);
-            } else {
-                mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
-                mFadeAnim.addListener(new Animator.AnimatorListener() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mPrimaryActionModeView.setVisibility(View.VISIBLE);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mPrimaryActionModeView.setAlpha(1f);
-                        mFadeAnim = null;
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-
-                    }
-
-                    @Override
-                    public void onAnimationRepeat(Animator animation) {
-
-                    }
-                });
-                mFadeAnim.start();
-            }
-            mPrimaryActionModeView.sendAccessibilityEvent(
-                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-
-        private ActionMode createFloatingActionMode(
-                View originatingView, ActionMode.Callback2 callback) {
-            if (mFloatingActionMode != null) {
-                mFloatingActionMode.finish();
-            }
-            cleanupFloatingActionModeViews();
-            final FloatingActionMode mode =
-                    new FloatingActionMode(mContext, callback, originatingView);
-            mFloatingActionModeOriginatingView = originatingView;
-            mFloatingToolbarPreDrawListener =
-                new OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mode.updateViewLocationInWindow();
-                        return true;
-                    }
-                };
-            return mode;
-        }
-
-        private void setHandledFloatingActionMode(ActionMode mode) {
-            mFloatingActionMode = mode;
-            mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
-            ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
-            mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
-            mFloatingActionModeOriginatingView.getViewTreeObserver()
-                .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
-        }
-
-        /**
-         * Informs the decor if a non client decor is attached and visible.
-         * @param attachedAndVisible true when the decor is visible.
-         * Note that this will even be called if there is no non client decor.
-         **/
-        void enableNonClientDecor(boolean attachedAndVisible) {
-            if (mHasNonClientDecor != attachedAndVisible) {
-                mHasNonClientDecor = attachedAndVisible;
-                if (getForeground() != null) {
-                    drawableChanged();
-                }
-            }
-        }
-
-        /**
-         * Returns true if the window has a non client decor.
-         * @return If there is a non client decor - even if it is not visible.
-         **/
-        private boolean windowHasNonClientDecor() {
-            return mHasNonClientDecor;
-        }
-
-        /**
-         * 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() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
-        }
-
-        void setWindow(PhoneWindow phoneWindow) {
-            mWindow = phoneWindow;
-            Context context = getContext();
-            if (context instanceof DecorContext) {
-                DecorContext decorContex = (DecorContext) context;
-                decorContex.setPhoneWindow(mWindow);
-            }
-        }
-
-        /**
-         * Clears out internal references when the action mode is destroyed.
-         */
-        private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
-            private final ActionMode.Callback mWrapped;
-
-            public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
-                mWrapped = wrapped;
-            }
-
-            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                return mWrapped.onCreateActionMode(mode, menu);
-            }
-
-            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                requestFitSystemWindows();
-                return mWrapped.onPrepareActionMode(mode, menu);
-            }
-
-            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                return mWrapped.onActionItemClicked(mode, item);
-            }
-
-            public void onDestroyActionMode(ActionMode mode) {
-                mWrapped.onDestroyActionMode(mode);
-                final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
-                        >= Build.VERSION_CODES.M;
-                final boolean isPrimary;
-                final boolean isFloating;
-                if (isMncApp) {
-                    isPrimary = mode == mPrimaryActionMode;
-                    isFloating = mode == mFloatingActionMode;
-                    if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
-                        Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
-                                + mode + " was not the current primary action mode! Expected "
-                                + mPrimaryActionMode);
-                    }
-                    if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
-                        Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
-                                + mode + " was not the current floating action mode! Expected "
-                                + mFloatingActionMode);
-                    }
-                } else {
-                    isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
-                    isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
-                }
-                if (isPrimary) {
-                    if (mPrimaryActionModePopup != null) {
-                        removeCallbacks(mShowPrimaryActionModePopup);
-                    }
-                    if (mPrimaryActionModeView != null) {
-                        endOnGoingFadeAnimation();
-                        mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
-                                1f, 0f);
-                        mFadeAnim.addListener(new Animator.AnimatorListener() {
-                                    @Override
-                                    public void onAnimationStart(Animator animation) {
-
-                                    }
-
-                                    @Override
-                                    public void onAnimationEnd(Animator animation) {
-                                        mPrimaryActionModeView.setVisibility(GONE);
-                                        if (mPrimaryActionModePopup != null) {
-                                            mPrimaryActionModePopup.dismiss();
-                                        }
-                                        mPrimaryActionModeView.removeAllViews();
-                                        mFadeAnim = null;
-                                    }
-
-                                    @Override
-                                    public void onAnimationCancel(Animator animation) {
-
-                                    }
-
-                                    @Override
-                                    public void onAnimationRepeat(Animator animation) {
-
-                                    }
-                                });
-                        mFadeAnim.start();
-                    }
-
-                    mPrimaryActionMode = null;
-                } else if (isFloating) {
-                    cleanupFloatingActionModeViews();
-                    mFloatingActionMode = null;
-                }
-                if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                    try {
-                        mWindow.getCallback().onActionModeFinished(mode);
-                    } catch (AbstractMethodError ame) {
-                        // Older apps might not implement this callback method.
-                    }
-                }
-                requestFitSystemWindows();
-            }
-
-            @Override
-            public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
-                if (mWrapped instanceof ActionMode.Callback2) {
-                    ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
-                } else {
-                    super.onGetContentRect(mode, view, outRect);
-                }
-            }
-        }
-    }
-
     protected DecorView generateDecor(int featureId) {
         // System process doesn't have application context and in that case we need to directly use
         // the context we have. Otherwise we want the application context, so we don't cling to the
@@ -4065,7 +2485,7 @@
                             + Integer.toHexString(mFrameResource));
                 }
             }
-            if (mLoadEleveation) {
+            if (mLoadElevation) {
                 mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
             }
             mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
@@ -4135,19 +2555,7 @@
         }
 
         mDecor.startChanging();
-
-        mNonClientDecorView = createNonClientDecorView();
-        View in = mLayoutInflater.inflate(layoutResource, null);
-        if (mNonClientDecorView != null) {
-            if (mNonClientDecorView.getParent() == null) {
-                decor.addView(mNonClientDecorView,
-                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-            }
-            mNonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-        } else {
-            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-        }
-        mContentRoot = (ViewGroup) in;
+        final View in = mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
 
         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
         if (contentParent == null) {
@@ -4202,50 +2610,6 @@
         return contentParent;
     }
 
-    // Free floating overlapping windows require a non client decor with a caption and shadow..
-    private NonClientDecorView createNonClientDecorView() {
-        NonClientDecorView nonClientDecorView = null;
-        for (int i = mDecor.getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
-            View view = mDecor.getChildAt(i);
-            if (view instanceof NonClientDecorView) {
-                // The decor was most likely saved from a relaunch - so reuse it.
-                nonClientDecorView = (NonClientDecorView) view;
-                mDecor.removeViewAt(i);
-            }
-        }
-        final WindowManager.LayoutParams attrs = getAttributes();
-        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
-                attrs.type == TYPE_APPLICATION;
-        mWorkspaceId = getWorkspaceId();
-        // Only a non floating application window on one of the allowed workspaces can get a non
-        // client decor.
-        if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
-            // Dependent on the brightness of the used title we either use the
-            // dark or the light button frame.
-            if (nonClientDecorView == null) {
-                Context context = mDecor.getContext();
-                TypedValue value = new TypedValue();
-                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
-                LayoutInflater inflater = mLayoutInflater.from(context);
-                if (Color.luminance(value.data) < 0.5) {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_dark, null);
-                } else {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_light, null);
-                }
-            }
-            nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
-                    StackId.hasWindowShadow(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&& StackId.hasWindowDecor(mWorkspaceId));
-
-        return nonClientDecorView;
-    }
-
     /** @hide */
     public void alwaysReadCloseOnTouchAttr() {
         mAlwaysReadCloseOnTouchAttr = true;
@@ -4450,7 +2814,7 @@
      *            isn't in our features, this throws an exception).
      * @return The panel state.
      */
-    private PanelFeatureState getPanelState(int featureId, boolean required) {
+    PanelFeatureState getPanelState(int featureId, boolean required) {
         return getPanelState(featureId, required, null);
     }
 
@@ -4914,7 +3278,7 @@
         int curAlpha = 255;
     }
 
-    private static final class PanelFeatureState {
+    static final class PanelFeatureState {
 
         /** Feature ID for this panel. */
         int featureId;
@@ -5316,30 +3680,12 @@
         }
     }
 
-    private static class ColorViewState {
-        View view = null;
-        int targetVisibility = View.INVISIBLE;
-        boolean present = false;
+    int getLocalFeaturesPrivate() {
+        return super.getLocalFeatures();
+    }
 
-        final int id;
-        final int systemUiHideFlag;
-        final int translucentFlag;
-        final int verticalGravity;
-        final int horizontalGravity;
-        final String transitionName;
-        final int hideWindowFlag;
-
-        ColorViewState(int systemUiHideFlag,
-                int translucentFlag, int verticalGravity, int horizontalGravity,
-                String transitionName, int id, int hideWindowFlag) {
-            this.id = id;
-            this.systemUiHideFlag = systemUiHideFlag;
-            this.translucentFlag = translucentFlag;
-            this.verticalGravity = verticalGravity;
-            this.horizontalGravity = horizontalGravity;
-            this.transitionName = transitionName;
-            this.hideWindowFlag = hideWindowFlag;
-        }
+    protected void setDefaultWindowFormat(int format) {
+        super.setDefaultWindowFormat(format);
     }
 
     void sendCloseSystemWindows() {
@@ -5391,28 +3737,6 @@
         mIsStartingWindow = isStartingWindow;
     }
 
-    /**
-     * Returns the Id of the workspace which contains this window.
-     * Note that if no workspace can be determined - which usually means that it was not
-     * created for an activity - the fullscreen workspace ID will be returned.
-     * @return Returns the workspace stack id which contains this window.
-     **/
-    private int getWorkspaceId() {
-        int workspaceId = INVALID_STACK_ID;
-        WindowControllerCallback callback = getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                workspaceId = callback.getWindowStackId();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
-            }
-        }
-        if (workspaceId == INVALID_STACK_ID) {
-            return FULLSCREEN_WORKSPACE_STACK_ID;
-        }
-        return workspaceId;
-    }
-
     @Override
     public void setTheme(int resid) {
         mTheme = resid;
@@ -5423,31 +3747,4 @@
             }
         }
     }
-
-    /**
-     * 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/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f190d8c..e8970bc 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -144,6 +144,13 @@
     }
 
     /**
+     * Checks if given array is null or has zero elements.
+     */
+    public static boolean isEmpty(byte[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
      * Checks that value is present as at least one of the elements of the array.
      * @param array the array to check in
      * @param value the value to check for
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 3c7b7ac..7be95d8 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -19,28 +19,29 @@
 public class HexDump
 {
     private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
-    
+    private final static char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
     public static String dumpHexString(byte[] array)
     {
         return dumpHexString(array, 0, array.length);
     }
-    
+
     public static String dumpHexString(byte[] array, int offset, int length)
     {
         StringBuilder result = new StringBuilder();
-        
+
         byte[] line = new byte[16];
         int lineIndex = 0;
-        
+
         result.append("\n0x");
         result.append(toHexString(offset));
-        
+
         for (int i = offset ; i < offset + length ; i++)
         {
             if (lineIndex == 16)
             {
                 result.append(" ");
-                
+
                 for (int j = 0 ; j < 16 ; j++)
                 {
                     if (line[j] > ' ' && line[j] < '~')
@@ -52,20 +53,20 @@
                         result.append(".");
                     }
                 }
-                
+
                 result.append("\n0x");
                 result.append(toHexString(i));
                 lineIndex = 0;
             }
-            
+
             byte b = array[i];
             result.append(" ");
             result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
             result.append(HEX_DIGITS[b & 0x0F]);
-            
+
             line[lineIndex++] = b;
         }
-        
+
         if (lineIndex != 16)
         {
             int count = (16 - lineIndex) * 3;
@@ -74,7 +75,7 @@
             {
                 result.append(" ");
             }
-            
+
             for (int i = 0 ; i < lineIndex ; i++)
             {
                 if (line[i] > ' ' && line[i] < '~')
@@ -87,10 +88,10 @@
                 }
             }
         }
-        
+
         return result.toString();
     }
-    
+
     public static String toHexString(byte b)
     {
         return toHexString(toByteArray(b));
@@ -98,48 +99,59 @@
 
     public static String toHexString(byte[] array)
     {
-        return toHexString(array, 0, array.length);
+        return toHexString(array, 0, array.length, true);
     }
-    
+
+    public static String toHexString(byte[] array, boolean upperCase)
+    {
+        return toHexString(array, 0, array.length, upperCase);
+    }
+
     public static String toHexString(byte[] array, int offset, int length)
     {
+        return toHexString(array, offset, length, true);
+    }
+
+    public static String toHexString(byte[] array, int offset, int length, boolean upperCase)
+    {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
         char[] buf = new char[length * 2];
 
         int bufIndex = 0;
-        for (int i = offset ; i < offset + length; i++) 
+        for (int i = offset ; i < offset + length; i++)
         {
             byte b = array[i];
-            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
-            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+            buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = digits[b & 0x0F];
         }
 
-        return new String(buf);        
+        return new String(buf);
     }
-    
+
     public static String toHexString(int i)
     {
         return toHexString(toByteArray(i));
     }
-    
+
     public static byte[] toByteArray(byte b)
     {
         byte[] array = new byte[1];
         array[0] = b;
         return array;
     }
-    
+
     public static byte[] toByteArray(int i)
     {
         byte[] array = new byte[4];
-        
+
         array[3] = (byte)(i & 0xFF);
         array[2] = (byte)((i >> 8) & 0xFF);
         array[1] = (byte)((i >> 16) & 0xFF);
         array[0] = (byte)((i >> 24) & 0xFF);
-        
+
         return array;
     }
-    
+
     private static int toByte(char c)
     {
         if (c >= '0' && c <= '9') return (c - '0');
@@ -148,7 +160,7 @@
 
         throw new RuntimeException ("Invalid hex char '" + c + "'");
     }
-    
+
     public static byte[] hexStringToByteArray(String hexString)
     {
         int length = hexString.length();
@@ -158,7 +170,15 @@
         {
             buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
         }
-        
+
         return buffer;
-    }    
+    }
+
+    public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        sb.append(digits[(b >> 4) & 0xf]);
+        sb.append(digits[b & 0xf]);
+        return sb;
+    }
+
 }
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index bee59dc..007ab29 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,6 +19,7 @@
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
@@ -38,10 +39,29 @@
         mContext = context;
     }
 
+    /**
+     * Returns the maximum number of action buttons that should be permitted within an action
+     * bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit.
+     * "always" items can override this.
+     */
     public int getMaxActionButtons() {
-        return mContext.getResources().getInteger(R.integer.max_action_buttons);
+        final Configuration config = mContext.getResources().getConfiguration();
+        final int width = config.screenWidthDp;
+        final int height = config.screenHeightDp;
+        final int smallest = config.smallestScreenWidthDp;
+        if (smallest > 600 || (width > 960 && height > 720) || (width > 720 && height > 960)) {
+            // For values-w600dp, values-sw600dp and values-xlarge.
+            return 5;
+        } else if (width >= 500 || (width > 640 && height > 480) || (width > 480 && height > 640)) {
+            // For values-w500dp and values-large.
+            return 4;
+        } else if (width >= 360) {
+            // For values-w360dp.
+            return 3;
+        } else {
+            return 2;
+        }
     }
-
     public boolean showsOverflowMenuButton() {
         return true;
     }
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 89d36ff..81056f2 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -25,7 +25,8 @@
 oneway interface IInputMethodClient {
     void setUsingInputMethod(boolean state);
     void onBindMethod(in InputBindResult res);
-    void onUnbindMethod(int sequence);
+    // unbindReason corresponds to InputMethodClient.UnbindReason.
+    void onUnbindMethod(int sequence, int unbindReason);
     void setActive(boolean active);
     void setUserActionNotificationSequenceNumber(int sequenceNumber);
 }
diff --git a/core/java/com/android/internal/view/InputMethodClient.java b/core/java/com/android/internal/view/InputMethodClient.java
new file mode 100644
index 0000000..a035343
--- /dev/null
+++ b/core/java/com/android/internal/view/InputMethodClient.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.view;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+public final class InputMethodClient {
+    public static final int UNBIND_REASON_UNSPECIFIED = 0;
+    public static final int UNBIND_REASON_SWITCH_CLIENT = 1;
+    public static final int UNBIND_REASON_SWITCH_IME = 2;
+    public static final int UNBIND_REASON_DISCONNECT_IME = 3;
+    public static final int UNBIND_REASON_NO_IME = 4;
+    public static final int UNBIND_REASON_SWITCH_IME_FAILED = 5;
+    public static final int UNBIND_REASON_RESET_IME = 6;
+
+    @Retention(SOURCE)
+    @IntDef({UNBIND_REASON_UNSPECIFIED, UNBIND_REASON_SWITCH_CLIENT, UNBIND_REASON_SWITCH_IME,
+            UNBIND_REASON_DISCONNECT_IME, UNBIND_REASON_NO_IME, UNBIND_REASON_SWITCH_IME_FAILED,
+            UNBIND_REASON_RESET_IME})
+    public @interface UnbindReason {}
+
+    public static String getUnbindReason(@UnbindReason final int reason) {
+        switch (reason) {
+            case UNBIND_REASON_UNSPECIFIED:
+                return "UNSPECIFIED";
+            case UNBIND_REASON_SWITCH_CLIENT:
+                return "SWITCH_CLIENT";
+            case UNBIND_REASON_SWITCH_IME:
+                return "SWITCH_IME";
+            case UNBIND_REASON_DISCONNECT_IME:
+                return "DISCONNECT_IME";
+            case UNBIND_REASON_NO_IME:
+                return "NO_IME";
+            case UNBIND_REASON_SWITCH_IME_FAILED:
+                return "SWITCH_IME_FAILED";
+            case UNBIND_REASON_RESET_IME:
+                return "RESET_IME";
+            default:
+                return "Unknown=" + reason;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index e674ecc..59d5f94 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -18,82 +18,104 @@
 
 import com.android.internal.view.menu.MenuPresenter.Callback;
 
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
 import android.content.Context;
 import android.view.Gravity;
 import android.view.View;
-import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
 
 /**
  * Presents a menu as a small, simple popup anchored to another view.
- *
- * @hide
  */
-public class MenuPopupHelper implements PopupWindow.OnDismissListener {
+public class MenuPopupHelper {
     private final Context mContext;
+
+    // Immutable cached popup menu properties.
     private final MenuBuilder mMenu;
     private final boolean mOverflowOnly;
     private final int mPopupStyleAttr;
     private final int mPopupStyleRes;
 
+    // Mutable cached popup menu properties.
     private View mAnchorView;
-    private MenuPopup mPopup;
-
-    private int mDropDownGravity = Gravity.NO_GRAVITY;
+    private int mDropDownGravity = Gravity.START;
     private boolean mForceShowIcon;
-    private boolean mShowTitle;
     private Callback mPresenterCallback;
-    private int mInitXOffset;
-    private int mInitYOffset;
 
-    public MenuPopupHelper(Context context, MenuBuilder menu) {
+    private MenuPopup mPopup;
+    private OnDismissListener mOnDismissListener;
+
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
         this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView) {
         this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView,
+            boolean overflowOnly, @AttrRes int popupStyleAttr) {
         this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+            @StyleRes int popupStyleRes) {
         mContext = context;
         mMenu = menu;
+        mAnchorView = anchorView;
         mOverflowOnly = overflowOnly;
         mPopupStyleAttr = popupStyleAttr;
         mPopupStyleRes = popupStyleRes;
-        mAnchorView = anchorView;
-        mPopup = createMenuPopup();
     }
 
-    private MenuPopup createMenuPopup() {
-        if (mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus)) {
-            return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
-                    mOverflowOnly);
-        }
-        return new StandardMenuPopup(
-                mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
+    public void setOnDismissListener(@Nullable OnDismissListener listener) {
+        mOnDismissListener = listener;
     }
 
-    public void setAnchorView(View anchor) {
+    /**
+      * Sets the view to which the popup window is anchored.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param anchor the view to which the popup window should be anchored
+      */
+    public void setAnchorView(@NonNull View anchor) {
         mAnchorView = anchor;
-        mPopup.setAnchorView(anchor);
     }
 
-    public void setForceShowIcon(boolean forceShow) {
-        mForceShowIcon = forceShow;
-        mPopup.setForceShowIcon(forceShow);
+    /**
+     * Sets whether the popup menu's adapter is forced to show icons in the
+     * menu item views.
+     * <p>
+     * Changes take effect on the next call to show().
+     *
+     * @param forceShowIcon {@code true} to force icons to be shown, or
+     *                  {@code false} for icons to be optionally shown
+     */
+    public void setForceShowIcon(boolean forceShowIcon) {
+        mForceShowIcon = forceShowIcon;
     }
 
+    /**
+      * Sets the alignment of the popup window relative to the anchor view.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param gravity alignment of the popup relative to the anchor
+      */
     public void setGravity(int gravity) {
         mDropDownGravity = gravity;
-        mPopup.setGravity(gravity);
     }
 
+    /**
+     * @return alignment of the popup relative to the anchor
+     */
     public int getGravity() {
         return mDropDownGravity;
     }
@@ -110,7 +132,11 @@
         }
     }
 
-    public ShowableListMenu getPopup() {
+    @NonNull
+    public MenuPopup getPopup() {
+        if (mPopup == null) {
+            mPopup = createPopup();
+        }
         return mPopup;
     }
 
@@ -129,14 +155,28 @@
             return false;
         }
 
-        mInitXOffset = 0;
-        mInitYOffset = 0;
-        mShowTitle = false;
-
-        showPopup();
+        showPopup(0, 0, false, false);
         return true;
     }
 
+    /**
+     * Shows the popup menu and makes a best-effort to anchor it to the
+     * specified (x,y) coordinate relative to the anchor view.
+     * <p>
+     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+     * display the popup with its top-left corner at (x,y) relative to the
+     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+     * popup's top-right corner will be at (x,y).
+     * <p>
+     * If the popup cannot be displayed fully on-screen, this method will
+     * attempt to scroll the anchor view's ancestors and/or offset the popup
+     * such that it may be displayed fully on-screen.
+     *
+     * @param x x coordinate relative to the anchor view
+     * @param y y coordinate relative to the anchor view
+     * @return {@code true} if the popup was shown or was already showing prior
+     *         to calling this method, {@code false} otherwise
+     */
     public boolean tryShow(int x, int y) {
         if (isShowing()) {
             return true;
@@ -146,51 +186,104 @@
             return false;
         }
 
-        mInitXOffset = x;
-        mInitYOffset = y;
-        mShowTitle = true;
-
-        showPopup();
+        showPopup(x, y, true, true);
         return true;
     }
 
-    private void showPopup() {
-        mPopup = createMenuPopup();
-        mPopup.setAnchorView(mAnchorView);
-        mPopup.setCallback(mPresenterCallback);
-        mPopup.setForceShowIcon(mForceShowIcon);
-        mPopup.setGravity(mDropDownGravity);
-        mPopup.setHorizontalOffset(mInitXOffset);
-        mPopup.setShowTitle(mShowTitle);
-        mPopup.setVerticalOffset(mInitYOffset);
+    /**
+     * Creates the popup and assigns cached properties.
+     *
+     * @return an initialized popup
+     */
+    @NonNull
+    private MenuPopup createPopup() {
+        final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableCascadingSubmenus);
 
-        // In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface,
-        // we must set the listener to this outer Helper rather than to the inner MenuPopup.
-        // Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done
-        // its own handling.
-        mPopup.setOnDismissListener(this);
+        final MenuPopup popup;
+        if (enableCascadingSubmenus) {
+            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        } else {
+            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        }
 
-        mPopup.addMenu(mMenu);
-        mPopup.show();
+        // Assign immutable properties.
+        popup.addMenu(mMenu);
+        popup.setOnDismissListener(mInternalOnDismissListener);
+
+        // Assign mutable properties. These may be reassigned later.
+        popup.setAnchorView(mAnchorView);
+        popup.setCallback(mPresenterCallback);
+        popup.setForceShowIcon(mForceShowIcon);
+        popup.setGravity(mDropDownGravity);
+
+        return popup;
     }
 
+    private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
+        if (resolveOffsets) {
+            // If the resolved drop-down gravity is RIGHT, the popup's right
+            // edge will be aligned with the anchor view. Adjust by the anchor
+            // width such that the top-right corner is at the X offset.
+            final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+                    mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+                xOffset -= mAnchorView.getWidth();
+            }
+        }
+
+        final MenuPopup popup = getPopup();
+        popup.setHorizontalOffset(xOffset);
+        popup.setVerticalOffset(yOffset);
+        popup.setShowTitle(showTitle);
+        popup.show();
+    }
+
+    /**
+     * Dismisses the popup, if showing.
+     */
     public void dismiss() {
         if (isShowing()) {
             mPopup.dismiss();
         }
     }
 
-    @Override
-    public void onDismiss() {
+    /**
+     * Called after the popup has been dismissed.
+     * <p>
+     * <strong>Note:</strong> Subclasses should call the super implementation
+     * last to ensure that any necessary tear down has occurred before the
+     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+     * is called.
+     */
+    protected void onDismiss() {
         mPopup = null;
+
+        if (mOnDismissListener != null) {
+            mOnDismissListener.onDismiss();
+        }
     }
 
     public boolean isShowing() {
         return mPopup != null && mPopup.isShowing();
     }
 
-    public void setCallback(MenuPresenter.Callback cb) {
+    public void setCallback(@Nullable MenuPresenter.Callback cb) {
         mPresenterCallback = cb;
-        mPopup.setCallback(cb);
+        if (mPopup != null) {
+            mPopup.setCallback(cb);
+        }
     }
+
+    /**
+     * Listener used to proxy dismiss callbacks to the helper's owner.
+     */
+    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
+        @Override
+        public void onDismiss() {
+            MenuPopupHelper.this.onDismiss();
+        }
+    };
 }
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c3a7460..3e65320 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -360,6 +360,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         pullChildren();
 
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 3b7bce4..a694aca 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,6 +30,10 @@
  * orientation when it can't fit its child views horizontally.
  */
 public class ButtonBarLayout extends LinearLayout {
+    // Whether to allow vertically stacked button bars. This is disabled for
+    // configurations with a small (e.g. less than 320dp) screen height. -->
+    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
+
     /** Whether the current configuration allows stacking. */
     private boolean mAllowStacking;
 
@@ -38,8 +42,12 @@
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        final boolean allowStackingDefault =
+                context.getResources().getConfiguration().screenHeightDp
+                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+                allowStackingDefault);
         ta.recycle();
     }
 
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
new file mode 100644
index 0000000..d747686
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.Window;
+
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+import java.util.ArrayList;
+
+/**
+ * This class represents the special screen elements to control a window on freeform
+ * environment.
+ * As such this class handles the following things:
+ * <ul>
+ * <li>The caption, containing the system buttons like maximize, close and such as well as
+ * allowing the user to drag the window around.</li>
+ * </ul>
+ * After creating the view, the function {@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):
+ * <ul>
+ * <li>setOutlineProvider</li>
+ * <li>setSurfaceFormat</li>
+ * <li>..</li>
+ * </ul>
+ *
+ * Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
+ * overlaying caption on the content and drawing.
+ *
+ * First, no matter where the content View gets added, it will always be the first child and the
+ * caption will be the second. This way the caption will always be drawn on top of the content when
+ * overlaying is enabled.
+ *
+ * Second, the touch dispatch is customized to handle overlaying. This is what happens when touch
+ * is dispatched on the caption area while overlaying it on content:
+ * <ul>
+ * <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
+ * down action is performed on top close or maximize buttons; the reason for that is we want these
+ * buttons to always work.</li>
+ * <li>The content View will receive the touch event. Mind that content is actually underneath the
+ * caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
+ * {@link #buildTouchDispatchChildList()}.</li>
+ * <li>If the touch event is not consumed by the content View, it will go to the caption View
+ * and the dragging logic will be executed.</li>
+ * </ul>
+ */
+public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
+        GestureDetector.OnGestureListener {
+    private final static String TAG = "DecorCaptionView";
+    private PhoneWindow mOwner = null;
+    private boolean mShow = false;
+
+    // True if the window is being dragged.
+    private boolean mDragging = false;
+
+    // True when the left mouse button got released while dragging.
+    private boolean mLeftMouseButtonReleased;
+
+    private boolean mOverlayWithAppContent = false;
+
+    private View mCaption;
+    private View mContent;
+    private View mMaximize;
+    private View mClose;
+
+    // Fields for detecting drag events.
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private boolean mCheckForDragging;
+    private int mDragSlop;
+
+    // Fields for detecting and intercepting click events on close/maximize.
+    private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
+    // We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
+    // with existing click detection.
+    private GestureDetector mGestureDetector;
+    private final Rect mCloseRect = new Rect();
+    private final Rect mMaximizeRect = new Rect();
+    private View mClickTarget;
+
+    public DecorCaptionView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public DecorCaptionView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mGestureDetector = new GestureDetector(context, this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCaption = getChildAt(0);
+    }
+
+    public void setPhoneWindow(PhoneWindow owner, boolean show) {
+        mOwner = owner;
+        mShow = show;
+        mOverlayWithAppContent = owner.getOverlayDecorCaption();
+        if (mOverlayWithAppContent) {
+            // The caption is covering the content, so we make its background transparent to make
+            // the content visible.
+            mCaption.setBackgroundColor(Color.TRANSPARENT);
+        }
+        updateCaptionVisibility();
+        // By changing the outline provider to BOUNDS, the window can remove its
+        // background without removing the shadow.
+        mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+        mMaximize = findViewById(R.id.maximize_window);
+        mClose = findViewById(R.id.close_window);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // If the user starts touch on the maximize/close buttons, we immediately intercept, so
+        // that these buttons are always clickable.
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            final int x = (int) ev.getX();
+            final int y = (int) ev.getY();
+            if (mMaximizeRect.contains(x, y)) {
+                mClickTarget = mMaximize;
+            }
+            if (mCloseRect.contains(x, y)) {
+                mClickTarget = mClose;
+            }
+        }
+        return mClickTarget != null;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mClickTarget != null) {
+            mGestureDetector.onTouchEvent(event);
+            final int action = event.getAction();
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+                mClickTarget = null;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent e) {
+        // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+        // the old input device events get cancelled first. So no need to remember the kind of
+        // input device we are listening to.
+        final int x = (int) e.getX();
+        final int y = (int) e.getY();
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                if (!mShow) {
+                    // When there is no caption we should not react to anything.
+                    return false;
+                }
+                // Checking for a drag action is started if we aren't dragging already and the
+                // starting event is either a left mouse button or any other input device.
+                if (((e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+                        (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0))) {
+                    mCheckForDragging = true;
+                    mTouchDownX = x;
+                    mTouchDownY = y;
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (!mDragging && mCheckForDragging && passedSlop(x, y)) {
+                    mCheckForDragging = false;
+                    mDragging = true;
+                    mLeftMouseButtonReleased = false;
+                    startMovingTask(e.getRawX(), e.getRawY());
+                } else if (mDragging && !mLeftMouseButtonReleased) {
+                    if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+                            (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+                        // There is no separate mouse button up call and if the user mixes mouse
+                        // button drag actions, we stop dragging once he releases the button.
+                        mLeftMouseButtonReleased = true;
+                        break;
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (!mDragging) {
+                    break;
+                }
+                // Abort the ongoing dragging.
+                mDragging = false;
+                return !mCheckForDragging;
+        }
+        return mDragging || mCheckForDragging;
+    }
+
+    @Override
+    public ArrayList<View> buildTouchDispatchChildList() {
+        mTouchDispatchList.ensureCapacity(3);
+        if (mCaption != null) {
+            mTouchDispatchList.add(mCaption);
+        }
+        if (mContent != null) {
+            mTouchDispatchList.add(mContent);
+        }
+        return mTouchDispatchList;
+    }
+
+    private boolean passedSlop(int x, int y) {
+        return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
+    }
+
+    /**
+     * The phone window configuration has changed and the caption needs to be updated.
+     * @param show True if the caption should be shown.
+     */
+    public void onConfigurationChanged(boolean show) {
+        mShow = show;
+        updateCaptionVisibility();
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (!(params instanceof MarginLayoutParams)) {
+            throw new IllegalArgumentException(
+                    "params " + params + " must subclass MarginLayoutParams");
+        }
+        // Make sure that we never get more then one client area in our view.
+        if (index >= 2 || getChildCount() >= 2) {
+            throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
+        }
+        // To support the overlaying content in the caption, we need to put the content view as the
+        // first child to get the right Z-Ordering.
+        super.addView(child, 0, params);
+        mContent = child;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            captionHeight = mCaption.getMeasuredHeight();
+        } else {
+            captionHeight = 0;
+        }
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            } else {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
+                        captionHeight);
+            }
+        }
+
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+                MeasureSpec.getSize(heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
+            captionHeight = mCaption.getBottom() - mCaption.getTop();
+            mMaximize.getHitRect(mMaximizeRect);
+            mClose.getHitRect(mCloseRect);
+        } else {
+            captionHeight = 0;
+            mMaximizeRect.setEmpty();
+            mCloseRect.setEmpty();
+        }
+
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
+            } else {
+                mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
+                        captionHeight + mContent.getMeasuredHeight());
+            }
+        }
+    }
+    /**
+     * Determine if the workspace is entirely covered by the window.
+     * @return Returns true when the window is filling the entire screen/workspace.
+     **/
+    private boolean isFillingScreen() {
+        return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
+                (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
+    }
+
+    /**
+     * Updates the visibility of the caption.
+     **/
+    private void updateCaptionVisibility() {
+        // Don't show the caption if the window has e.g. entered full screen.
+        boolean invisible = isFillingScreen() || !mShow;
+        mCaption.setVisibility(invisible ? GONE : VISIBLE);
+        mCaption.setOnTouchListener(this);
+    }
+
+    /**
+     * Maximize the window by moving it to the maximized workspace stack.
+     **/
+    private void maximizeWindow() {
+        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Cannot change task workspace.");
+            }
+        }
+    }
+
+    public boolean isCaptionShowing() {
+        return mShow;
+    }
+
+    public int getCaptionHeight() {
+        return (mCaption != null) ? mCaption.getHeight() : 0;
+    }
+
+    public void removeContentView() {
+        if (mContent != null) {
+            removeView(mContent);
+            mContent = null;
+        }
+    }
+
+    public View getCaption() {
+        return mCaption;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MarginLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
+                MarginLayoutParams.MATCH_PARENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(LayoutParams p) {
+        return new MarginLayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MarginLayoutParams;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return false;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        if (mClickTarget == mMaximize) {
+            maximizeWindow();
+        } else if (mClickTarget == mClose) {
+            mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 60ef4a4..889c7b3 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -126,6 +126,8 @@
     private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
             Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
 
+    private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
+
     private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
 
     // Maximum allowed number of repeated or ordered characters in a sequence before we'll
@@ -578,6 +580,29 @@
     }
 
     /**
+     * Sets the device owner information. If the information is {@code null} or empty then the
+     * device owner info is cleared.
+     *
+     * @param info Device owner information which will be displayed instead of the user
+     * owner info.
+     */
+    public void setDeviceOwnerInfo(String info) {
+        if (info != null && info.isEmpty()) {
+            info = null;
+        }
+
+        setString(LOCK_SCREEN_DEVICE_OWNER_INFO, info, UserHandle.USER_SYSTEM);
+    }
+
+    public String getDeviceOwnerInfo() {
+        return getString(LOCK_SCREEN_DEVICE_OWNER_INFO, UserHandle.USER_SYSTEM);
+    }
+
+    public boolean isDeviceOwnerInfoEnabled() {
+        return getDeviceOwnerInfo() != null;
+    }
+
+    /**
      * Compute the password quality from the given password string.
      */
     static public int computePasswordQuality(String password) {
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
deleted file mode 100644
index de6e228..0000000
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ /dev/null
@@ -1,668 +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.
- */
-
-package com.android.internal.widget;
-
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.AttributeSet;
-import android.view.Choreographer;
-import android.view.DisplayListCanvas;
-import android.view.MotionEvent;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.Window;
-import android.view.WindowCallbacks;
-import android.util.Log;
-import android.util.TypedValue;
-
-import com.android.internal.R;
-import com.android.internal.policy.PhoneWindow;
-
-/**
- * This class represents the special screen elements to control a window on free form
- * environment. All thse screen elements are added in the "non client area" which is the area of
- * the window which is handled by the OS and not the application.
- * As such this class handles the following things:
- * <ul>
- * <li>The caption, containing the system buttons like maximize, close and such as well as
- * allowing the user to drag the window around.</li>
- * <li>The shadow - which is changing dependent on the window focus.</li>
- * <li>The border around the client area (if there is one).</li>
- * <li>The resize handles which allow to resize the window.</li>
- * </ul>
- * After creating the view, the function
- * {@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):
- * <ul>
- * <li>setElevation</li>
- * <li>setOutlineProvider</li>
- * <li>setSurfaceFormat</li>
- * <li>..</li>
- * </ul>
- * This will be mitigated once b/22527834 will be addressed.
- */
-public class NonClientDecorView extends LinearLayout
-        implements View.OnClickListener, View.OnTouchListener, WindowCallbacks {
-    private final static String TAG = "NonClientDecorView";
-    // The height of a window which has focus in DIP.
-    private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
-    // The height of a window which has not in DIP.
-    private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
-    private PhoneWindow mOwner = null;
-    private boolean mWindowHasShadow = false;
-    private boolean mShowDecor = false;
-    // True when this object is listening for window size changes.
-    private boolean mAttachedCallbacksToRootViewImpl = false;
-
-    // True if the window is being dragged.
-    private boolean mDragging = false;
-
-    // True when the left mouse button got released while dragging.
-    private boolean mLeftMouseButtonReleased;
-
-    // True if this window is resizable (which is currently only true when the decor is shown).
-    public boolean mVisible = false;
-
-    // The current focus state of the window for updating the window elevation.
-    private boolean mWindowHasFocus = true;
-
-    // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
-    // size calculation takes the shadow size into account. We set the elevation currently
-    // to max until the first layout command has been executed.
-    private boolean mAllowUpdateElevation = false;
-
-    // The resize frame renderer.
-    private ResizeFrameThread mFrameRendererThread = null;
-
-    private Drawable mResizingBackgroundDrawable;
-    private Drawable mCaptionBackgroundDrawable;
-
-    public NonClientDecorView(Context context) {
-        super(context);
-    }
-
-    public NonClientDecorView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @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.
-            getViewRootImpl().addWindowCallbacks(this);
-            mAttachedCallbacksToRootViewImpl = true;
-        } else if (mFrameRendererThread != null) {
-            // We are resizing and this call happened due to a configuration change. Tell the
-            // 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);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent e) {
-        // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
-        // the old input device events get cancelled first. So no need to remember the kind of
-        // input device we are listening to.
-        switch (e.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                if (!mShowDecor) {
-                    // When there is no decor we should not react to anything.
-                    return false;
-                }
-                // A drag action is started if we aren't dragging already and the starting event is
-                // either a left mouse button or any other input device.
-                if (!mDragging &&
-                        (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
-                                (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
-                    mDragging = true;
-                    mLeftMouseButtonReleased = false;
-                    startMovingTask(e.getRawX(), e.getRawY());
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                if (mDragging && !mLeftMouseButtonReleased) {
-                    if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
-                            (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
-                        // There is no separate mouse button up call and if the user mixes mouse
-                        // button drag actions, we stop dragging once he releases the button.
-                        mLeftMouseButtonReleased = true;
-                        break;
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                if (!mDragging) {
-                    break;
-                }
-                // Abort the ongoing dragging.
-                mDragging = false;
-                return true;
-        }
-        return mDragging;
-    }
-
-    /**
-     * The phone window configuration has changed and the decor needs to be updated.
-     * @param showDecor True if the decor should be shown.
-     * @param windowHasShadow True when the window should show a shadow.
-     **/
-    public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
-        mShowDecor = showDecor;
-        updateCaptionVisibility();
-        if (windowHasShadow != mWindowHasShadow) {
-            mWindowHasShadow = windowHasShadow;
-            initializeElevation();
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (view.getId() == R.id.maximize_window) {
-            maximizeWindow();
-        } else if (view.getId() == R.id.close_window) {
-            mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        mWindowHasFocus = hasWindowFocus;
-        updateElevation();
-        super.onWindowFocusChanged(hasWindowFocus);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        // If the application changed its SystemUI metrics, we might also have to adapt
-        // our shadow elevation.
-        updateElevation();
-        mAllowUpdateElevation = true;
-
-        super.onLayout(changed, left, top, right, bottom);
-    }
-
-    @Override
-    public void addView(View child, int index, ViewGroup.LayoutParams params) {
-        // Make sure that we never get more then one client area in our view.
-        if (index >= 2 || getChildCount() >= 2) {
-            throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
-        }
-        super.addView(child, index, params);
-    }
-
-    /**
-     * Determine if the workspace is entirely covered by the window.
-     * @return Returns true when the window is filling the entire screen/workspace.
-     **/
-    private boolean isFillingScreen() {
-        return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
-                (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
-                        View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
-    }
-
-    /**
-     * Updates the visibility of the caption.
-     **/
-    private void updateCaptionVisibility() {
-        // Don't show the decor if the window has e.g. entered full screen.
-        boolean invisible = isFillingScreen() || !mShowDecor;
-        View caption = getChildAt(0);
-        caption.setVisibility(invisible ? GONE : VISIBLE);
-        caption.setOnTouchListener(this);
-        mVisible = !invisible;
-    }
-
-    /**
-     * The elevation gets set for the first time and the framework needs to be informed that
-     * the surface layer gets created with the shadow size in mind.
-     **/
-    private void initializeElevation() {
-        // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
-        mAllowUpdateElevation = false;
-        if (mWindowHasShadow) {
-            updateElevation();
-        } else {
-            mOwner.setElevation(0);
-        }
-    }
-
-    /**
-     * The shadow height gets controlled by the focus to visualize highlighted windows.
-     * Note: This will overwrite application elevation properties.
-     * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
-     *       will get no shadow as they are expected to be "full screen".
-     **/
-    private void updateElevation() {
-        float elevation = 0;
-        // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
-        // is bound to the content size and not the target size.
-        if (mWindowHasShadow && mFrameRendererThread == null) {
-            boolean fill = isFillingScreen();
-            elevation = fill ? 0 :
-                    (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
-                            DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
-            // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
-            if (!mAllowUpdateElevation && !fill) {
-                elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
-            }
-            // Convert the DP elevation into physical pixels.
-            elevation = dipToPx(elevation);
-        }
-        // Don't change the elevation if it didn't change since it can require some time.
-        if (mOwner.getDecorView().getElevation() != elevation) {
-            mOwner.setElevation(elevation);
-        }
-    }
-
-    /**
-     * Converts a DIP measure into physical pixels.
-     * @param dip The dip value.
-     * @return Returns the number of pixels.
-     */
-    private float dipToPx(float dip) {
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
-                getResources().getDisplayMetrics());
-    }
-
-    /**
-     * Maximize the window by moving it to the maximized workspace stack.
-     **/
-    private void maximizeWindow() {
-        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Cannot change task workspace.");
-            }
-        }
-    }
-
-    @Override
-    public void onWindowDragResizeStart(Rect initialBounds) {
-        if (mOwner.isDestroyed()) {
-            // If the owner's window is gone, we should not be able to come here anymore.
-            releaseResources();
-            return;
-        }
-        if (mFrameRendererThread != null) {
-            return;
-        }
-        final ThreadedRenderer renderer =
-                (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
-        if (renderer != null) {
-            mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
-            // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
-            // If we want to get the shadow shown while resizing, we would need to elevate a new
-            // element which owns the caption and has the elevation.
-            updateElevation();
-        }
-    }
-
-    @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.onRequestDraw(reportNextDraw);
-        } else if (reportNextDraw) {
-            // If render thread is gone, just report immediately.
-            if (isAttachedToWindow()) {
-                getViewRootImpl().reportDrawFinish();
-            }
-        }
-    }
-
-    @Override
-    public void onWindowDragResizeEnd() {
-        releaseThreadedRenderer();
-    }
-
-    @Override
-    public void onWindowSizeIsChanging(Rect newBounds) {
-        if (mFrameRendererThread != null) {
-            mFrameRendererThread.setTargetRect(newBounds);
-        }
-    }
-
-    /**
-     * Release the renderer thread which is usually done when the user stops resizing.
-     */
-    private void releaseThreadedRenderer() {
-        if (mFrameRendererThread != null) {
-            mFrameRendererThread.releaseRenderer();
-            mFrameRendererThread = null;
-            // Bring the shadow back.
-            updateElevation();
-        }
-    }
-
-    /**
-     * Called when the parent window is destroyed to release all resources. Note that this will also
-     * destroy the renderer thread.
-     */
-    private void releaseResources() {
-        releaseThreadedRenderer();
-    }
-
-    /**
-     * The thread which draws the chrome while we are resizing.
-     * 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
-        // or might not have been applied to the output already.
-        private final Rect mTargetRect = new Rect();
-
-        // The render nodes for the multi threaded renderer.
-        private ThreadedRenderer mRenderer;
-        private RenderNode mFrameAndBackdropNode;
-
-        private final Rect mOldTargetRect = new Rect();
-        private final Rect mNewTargetRect = new Rect();
-        private Choreographer mChoreographer;
-
-        // Cached size values from the last render for the case that the view hierarchy is gone
-        // during a configuration change.
-        private int mLastContentWidth;
-        private int mLastContentHeight;
-        private int mLastCaptionHeight;
-        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 a render node for the content and frame backdrop
-            // which can be resized independently from the content.
-            mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
-
-            mRenderer.addRenderNode(mFrameAndBackdropNode, true);
-
-            // Set the initial bounds and draw once so that we do not get a broken frame.
-            mTargetRect.set(initialBounds);
-            synchronized (this) {
-                changeWindowSizeLocked(initialBounds);
-            }
-
-            // Kick off our draw thread.
-            start();
-        }
-
-        /**
-         * Call this function asynchronously when the window size has been changed. The change will
-         * be picked up once per frame and the frame will be re-rendered accordingly.
-         * @param newTargetBounds The new target bounds.
-         */
-        public void setTargetRect(Rect newTargetBounds) {
-            synchronized (this) {
-                mTargetRect.set(newTargetBounds);
-                // Notify of a bounds change.
-                pingRenderLocked();
-            }
-        }
-
-        /**
-         * The window got replaced due to a configuration change.
-         */
-        public void onConfigurationChange() {
-            synchronized (this) {
-                if (mRenderer != null) {
-                    // Enforce a window redraw.
-                    mOldTargetRect.set(0, 0, 0, 0);
-                    pingRenderLocked();
-                }
-            }
-        }
-
-        /**
-         * All resources of the renderer will be released. This function can be called from the
-         * the UI thread as well as the renderer thread.
-         */
-        public void releaseRenderer() {
-            synchronized (this) {
-                if (mRenderer != null) {
-                    // Invalidate the current content bounds.
-                    mRenderer.setContentDrawBounds(0, 0, 0, 0);
-
-                    // Remove the render node again
-                    // (see comment above - better to do that only once).
-                    mRenderer.removeRenderNode(mFrameAndBackdropNode);
-
-                    mRenderer = null;
-
-                    // Exit the renderer loop.
-                    pingRenderLocked();
-                }
-            }
-        }
-
-        @Override
-        public void run() {
-            try {
-                Looper.prepare();
-                synchronized (this) {
-                    mChoreographer = Choreographer.getInstance();
-
-                    // Draw at least once.
-                    mChoreographer.postFrameCallback(this);
-                }
-                Looper.loop();
-            } finally {
-                releaseRenderer();
-            }
-            synchronized (this) {
-                // Make sure no more messages are being sent.
-                mChoreographer = null;
-            }
-        }
-
-        /**
-         * The implementation of the FrameCallback.
-         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
-         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
-         */
-        @Override
-        public void doFrame(long frameTimeNanos) {
-            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);
-                }
-            }
-        }
-
-        /**
-         * 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,
-                        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 onRequestDraw(boolean reportNextDraw) {
-            synchronized (this) {
-                mReportNextDraw = reportNextDraw;
-                mOldTargetRect.set(0, 0, 0, 0);
-                pingRenderLocked();
-            }
-        }
-
-        /**
-         * Resizing the frame to fit the new window size.
-         * @param newBounds The window bounds which needs to be drawn.
-         */
-        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);
-            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;
-                }
-            }
-            // 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;
-            final int top = mLastYOffset + newBounds.top;
-            final int width = newBounds.width();
-            final int height = newBounds.height();
-
-            mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
-
-            // Draw the caption and content backdrops in to our render node.
-            DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
-            mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
-            mCaptionBackgroundDrawable.draw(canvas);
-
-            // The backdrop: clear everything with the background. Clipping is done elsewhere.
-            mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
-            mResizingBackgroundDrawable.draw(canvas);
-            mFrameAndBackdropNode.end(canvas);
-
-            // We need to render the node explicitly
-            mRenderer.drawRenderNode(mFrameAndBackdropNode);
-
-            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;
-            }
-        }
-
-        /**
-         * Sends a message to the renderer to wake up and perform the next action which can be
-         * either the next rendering or the self destruction if mRenderer is null.
-         * Note: This call must be synchronized.
-         */
-        private void pingRenderLocked() {
-            if (mChoreographer != null) {
-                mChoreographer.postFrameCallback(this);
-            }
-        }
-    }
-}
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 6217d6e..948a6bb 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -1095,7 +1095,16 @@
                     View child = getChildAt(i);
                     ii = infoForChild(child);
                     if (ii != null && ii.position == mCurItem) {
-                        if (child.requestFocus(focusDirection)) {
+                        final Rect focusRect;
+                        if (currentFocused == null) {
+                            focusRect = null;
+                        } else {
+                            focusRect = mTempRect;
+                            currentFocused.getFocusedRect(mTempRect);
+                            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+                            offsetRectIntoDescendantCoords(child, mTempRect);
+                        }
+                        if (child.requestFocus(focusDirection, focusRect)) {
                             break;
                         }
                     }
@@ -1321,6 +1330,11 @@
         }
     }
 
+    public Object getCurrent() {
+        final ItemInfo itemInfo = infoForPosition(getCurrentItem());
+        return itemInfo == null ? null : itemInfo.object;
+    }
+
     @Override
     public void removeView(View view) {
         if (mInLayout) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ecee2b2..4d648ce 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -89,6 +89,7 @@
     android_util_Binder.cpp \
     android_util_EventLog.cpp \
     android_util_Log.cpp \
+    android_util_PathParser.cpp \
     android_util_Process.cpp \
     android_util_StringBlock.cpp \
     android_util_XmlBlock.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ffc69a9..bd41c5d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -110,6 +110,7 @@
 extern int register_android_content_AssetManager(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
+extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
 extern int register_android_graphics_Canvas(JNIEnv* env);
@@ -579,7 +580,8 @@
     char heapminfreeOptsBuf[sizeof("-XX:HeapMinFree=")-1 + PROPERTY_VALUE_MAX];
     char heapmaxfreeOptsBuf[sizeof("-XX:HeapMaxFree=")-1 + PROPERTY_VALUE_MAX];
     char usejitOptsBuf[sizeof("-Xusejit:")-1 + PROPERTY_VALUE_MAX];
-    char jitcodecachesizeOptsBuf[sizeof("-Xjitcodecachesize:")-1 + PROPERTY_VALUE_MAX];
+    char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX];
+    char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX];
     char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX];
     char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
     char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
@@ -607,15 +609,6 @@
       kEMIntFast,
       kEMJitCompiler,
     } executionMode = kEMDefault;
-    char profilePeriod[sizeof("-Xprofile-period:")-1 + PROPERTY_VALUE_MAX];
-    char profileDuration[sizeof("-Xprofile-duration:")-1 + PROPERTY_VALUE_MAX];
-    char profileInterval[sizeof("-Xprofile-interval:")-1 + PROPERTY_VALUE_MAX];
-    char profileBackoff[sizeof("-Xprofile-backoff:")-1 + PROPERTY_VALUE_MAX];
-    char profileTopKThreshold[sizeof("-Xprofile-top-k-threshold:")-1 + PROPERTY_VALUE_MAX];
-    char profileTopKChangeThreshold[sizeof("-Xprofile-top-k-change-threshold:")-1 +
-                                    PROPERTY_VALUE_MAX];
-    char profileType[sizeof("-Xprofile-type:")-1 + PROPERTY_VALUE_MAX];
-    char profileMaxStackDepth[sizeof("-Xprofile-max-stack-depth:")-1 + PROPERTY_VALUE_MAX];
     char localeOption[sizeof("-Duser.locale=") + PROPERTY_VALUE_MAX];
     char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:")-1 + PROPERTY_VALUE_MAX];
     char nativeBridgeLibrary[sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX];
@@ -692,7 +685,8 @@
      * JIT related options.
      */
     parseRuntimeOption("dalvik.vm.usejit", usejitOptsBuf, "-Xusejit:");
-    parseRuntimeOption("dalvik.vm.jitcodecachesize", jitcodecachesizeOptsBuf, "-Xjitcodecachesize:");
+    parseRuntimeOption("dalvik.vm.jitmaxsize", jitmaxsizeOptsBuf, "-Xjitmaxsize:");
+    parseRuntimeOption("dalvik.vm.jitinitialsize", jitinitialsizeOptsBuf, "-Xjitinitialsize:");
     parseRuntimeOption("dalvik.vm.jitthreshold", jitthresholdOptsBuf, "-Xjitthreshold:");
 
     property_get("ro.config.low_ram", propBuf, "");
@@ -834,75 +828,22 @@
         addOption(localeOption);
     }
 
-    /*
-     * Set profiler options
-     */
-    // Whether or not the profiler should be enabled.
-    property_get("dalvik.vm.profiler", propBuf, "0");
-    if (propBuf[0] == '1') {
-        addOption("-Xenable-profiler");
-    }
-
-    // Whether the profile should start upon app startup or be delayed by some random offset
-    // (in seconds) that is bound between 0 and a fixed value.
-    property_get("dalvik.vm.profile.start-immed", propBuf, "0");
-    if (propBuf[0] == '1') {
-        addOption("-Xprofile-start-immediately");
-    }
-
-    // Number of seconds during profile runs.
-    parseRuntimeOption("dalvik.vm.profile.period-secs", profilePeriod, "-Xprofile-period:");
-
-    // Length of each profile run (seconds).
-    parseRuntimeOption("dalvik.vm.profile.duration-secs",
-                       profileDuration,
-                       "-Xprofile-duration:");
-
-    // Polling interval during profile run (microseconds).
-    parseRuntimeOption("dalvik.vm.profile.interval-us", profileInterval, "-Xprofile-interval:");
-
-    // Coefficient for period backoff.  The the period is multiplied
-    // by this value after each profile run.
-    parseRuntimeOption("dalvik.vm.profile.backoff-coeff", profileBackoff, "-Xprofile-backoff:");
-
-    // Top K% of samples that are considered relevant when
-    // deciding if the app should be recompiled.
-    parseRuntimeOption("dalvik.vm.profile.top-k-thr",
-                       profileTopKThreshold,
-                       "-Xprofile-top-k-threshold:");
-
-    // The threshold after which a change in the structure of the
-    // top K% profiled samples becomes significant and triggers
-    // recompilation. A change in profile is considered
-    // significant if X% (top-k-change-threshold) of the top K%
-    // (top-k-threshold property) samples has changed.
-    parseRuntimeOption("dalvik.vm.profile.top-k-ch-thr",
-                       profileTopKChangeThreshold,
-                       "-Xprofile-top-k-change-threshold:");
-
-    // Type of profile data.
-    parseRuntimeOption("dalvik.vm.profiler.type", profileType, "-Xprofile-type:");
-
-    // Depth of bounded stack data
-    parseRuntimeOption("dalvik.vm.profile.stack-depth",
-                       profileMaxStackDepth,
-                       "-Xprofile-max-stack-depth:");
-
-    /*
-     * Tracing options.
-     */
-    property_get("dalvik.vm.method-trace", propBuf, "false");
-    if (strcmp(propBuf, "true") == 0) {
-        addOption("-Xmethod-trace");
-        parseRuntimeOption("dalvik.vm.method-trace-file",
-                           methodTraceFileBuf,
-                           "-Xmethod-trace-file:");
-        parseRuntimeOption("dalvik.vm.method-trace-file-siz",
-                           methodTraceFileSizeBuf,
-                           "-Xmethod-trace-file-size:");
-        property_get("dalvik.vm.method-trace-stream", propBuf, "false");
+    // Trace files are stored in /data/misc/trace which is writable only in debug mode.
+    property_get("ro.debuggable", propBuf, "0");
+    if (strcmp(propBuf, "1") == 0) {
+        property_get("dalvik.vm.method-trace", propBuf, "false");
         if (strcmp(propBuf, "true") == 0) {
-            addOption("-Xmethod-trace-stream");
+            addOption("-Xmethod-trace");
+            parseRuntimeOption("dalvik.vm.method-trace-file",
+                               methodTraceFileBuf,
+                               "-Xmethod-trace-file:");
+            parseRuntimeOption("dalvik.vm.method-trace-file-siz",
+                               methodTraceFileSizeBuf,
+                               "-Xmethod-trace-file-size:");
+            property_get("dalvik.vm.method-trace-stream", propBuf, "false");
+            if (strcmp(propBuf, "true") == 0) {
+                addOption("-Xmethod-trace-stream");
+            }
         }
     }
 
@@ -1299,6 +1240,7 @@
     REG_JNI(register_android_os_SystemClock),
     REG_JNI(register_android_util_EventLog),
     REG_JNI(register_android_util_Log),
+    REG_JNI(register_android_util_PathParser),
     REG_JNI(register_android_content_AssetManager),
     REG_JNI(register_android_content_StringBlock),
     REG_JNI(register_android_content_XmlBlock),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 703a9bd..a805b6d 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1006,6 +1006,7 @@
         // is disposed.
         int dupFd = dup(blob.fd());
         if (dupFd < 0) {
+            ALOGE("Error allocating dup fd. Error:%d", errno);
             blob.release();
             SkSafeUnref(ctable);
             doThrowRE(env, "Could not allocate dup blob fd.");
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 7fd288a..9b774b3 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -63,6 +63,11 @@
     layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
 }
 
+bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
+    const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
+    return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+}
+
 float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) {
     switch (paint->getTextAlign()) {
         case Paint::kCenter_Align:
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 1ee6245..5bf1eec 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -40,6 +40,8 @@
             TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
             size_t bufSize);
 
+    static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
+
     static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
 
     static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index b50046f..9c11dd1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -39,6 +39,7 @@
 
 #include <minikin/GraphemeBreak.h>
 #include <minikin/Measurement.h>
+#include <unicode/utf16.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
 #include "Paint.h"
@@ -852,45 +853,44 @@
         return false;
     }
 
-    static jboolean hasGlyphVariation(const Paint* paint, TypefaceImpl* typeface, jint bidiFlags,
-            const jchar* chars, size_t size) {
-        // TODO: query font for whether character has variation selector; requires a corresponding
-        // function in Minikin.
-        return false;
-    }
-
     static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
             jint bidiFlags, jstring string) {
         const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         ScopedStringChars str(env, string);
 
-        /* start by rejecting variation selectors (not supported yet) */
+        /* Start by rejecting unsupported base code point and variation selector pairs. */
         size_t nChars = 0;
+        const uint32_t kStartOfString = 0xFFFFFFFF;
+        uint32_t prevCp = kStartOfString;
         for (size_t i = 0; i < str.size(); i++) {
-            jchar c = str[i];
-            if (0xDC00 <= c && c <= 0xDFFF) {
+            jchar cu = str[i];
+            uint32_t cp = cu;
+            if (U16_IS_TRAIL(cu)) {
                 // invalid UTF-16, unpaired trailing surrogate
                 return false;
-            } else if (0xD800 <= c && c <= 0xDBFF) {
+            } else if (U16_IS_LEAD(cu)) {
                 if (i + 1 == str.size()) {
                     // invalid UTF-16, unpaired leading surrogate at end of string
                     return false;
                 }
                 i++;
-                jchar c2 = str[i];
-                if (!(0xDC00 <= c2 && c2 <= 0xDFFF)) {
+                jchar cu2 = str[i];
+                if (!U16_IS_TRAIL(cu2)) {
                     // invalid UTF-16, unpaired leading surrogate
                     return false;
                 }
-                // UTF-16 encoding of range U+E0100..U+E01EF is DB40 DD00 .. DB40 DDEF
-                if (c == 0xDB40 && 0xDD00 <= c2 && c2 <= 0xDDEF) {
-                    return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
-                }
-            } else if (0xFE00 <= c && c <= 0xFE0F) {
-                return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
+                cp = U16_GET_SUPPLEMENTARY(cu, cu2);
+            }
+
+            if (prevCp != kStartOfString &&
+                ((0xFE00 <= cp && cp <= 0xFE0F) || (0xE0100 <= cp && cp <= 0xE01EF)) &&
+                !MinikinUtils::hasVariationSelector(typeface, prevCp, cp)) {
+                // No font has a glyph for the code point and variation selector pair.
+                return false;
             }
             nChars++;
+            prevCp = cp;
         }
         Layout layout;
         MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, str.get(), 0, str.size(),
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 3d96fab..e4e73a4 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -544,39 +544,6 @@
     float totalAdvance;
 };
 
-// Same values used by Skia
-#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
-#define kStdUnderline_Offset    (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void drawTextDecorations(Canvas* canvas, float x, float y, float length, const SkPaint& paint) {
-    uint32_t flags;
-    SkDrawFilter* drawFilter = canvas->getDrawFilter();
-    if (drawFilter) {
-        SkPaint paintCopy(paint);
-        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
-        flags = paintCopy.getFlags();
-    } else {
-        flags = paint.getFlags();
-    }
-    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
-        SkScalar left = x;
-        SkScalar right = x + length;
-        float textSize = paint.getTextSize();
-        float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
-        if (flags & SkPaint::kUnderlineText_Flag) {
-            SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
-            canvas->drawRect(left, top, right, bottom, paint);
-        }
-        if (flags & SkPaint::kStrikeThruText_Flag) {
-            SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
-            canvas->drawRect(left, top, right, bottom, paint);
-        }
-    }
-}
-
 void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount,
              float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) {
     // minikin may modify the original paint
@@ -586,8 +553,8 @@
     MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
 
     size_t nGlyphs = layout.nGlyphs();
-    uint16_t* glyphs = new uint16_t[nGlyphs];
-    float* pos = new float[nGlyphs * 2];
+    std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
+    std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
 
     x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
 
@@ -597,13 +564,9 @@
         bounds.offset(x, y);
     }
 
-    DrawTextFunctor f(layout, canvas, glyphs, pos, paint, x, y, bounds, layout.getAdvance());
+    DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(),
+            paint, x, y, bounds, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
-
-    drawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
-
-    delete[] glyphs;
-    delete[] pos;
 }
 
 static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 568473c..c8b1784 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -184,51 +184,43 @@
     jint _minorRemaining;
     EGLint *minor = (EGLint *) 0;
 
-    if (!major_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "major == null";
-        goto exit;
+    if (major_ref) {
+        if (majorOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "majorOffset < 0";
+            goto exit;
+        }
+        _majorRemaining = _env->GetArrayLength(major_ref) - majorOffset;
+        if (_majorRemaining < 1) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "length - majorOffset < 1 < needed";
+            goto exit;
+        }
+        major_base = (EGLint *)
+            _env->GetIntArrayElements(major_ref, (jboolean *)0);
+        major = major_base + majorOffset;
     }
-    if (majorOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "majorOffset < 0";
-        goto exit;
-    }
-    _majorRemaining = _env->GetArrayLength(major_ref) - majorOffset;
-    if (_majorRemaining < 1) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length - majorOffset < 1 < needed";
-        goto exit;
-    }
-    major_base = (EGLint *)
-        _env->GetIntArrayElements(major_ref, (jboolean *)0);
-    major = major_base + majorOffset;
 
-    if (!minor_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "minor == null";
-        goto exit;
+    if (minor_ref) {
+        if (minorOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "minorOffset < 0";
+            goto exit;
+        }
+        _minorRemaining = _env->GetArrayLength(minor_ref) - minorOffset;
+        if (_minorRemaining < 1) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "length - minorOffset < 1 < needed";
+            goto exit;
+        }
+        minor_base = (EGLint *)
+            _env->GetIntArrayElements(minor_ref, (jboolean *)0);
+        minor = minor_base + minorOffset;
     }
-    if (minorOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "minorOffset < 0";
-        goto exit;
-    }
-    _minorRemaining = _env->GetArrayLength(minor_ref) - minorOffset;
-    if (_minorRemaining < 1) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length - minorOffset < 1 < needed";
-        goto exit;
-    }
-    minor_base = (EGLint *)
-        _env->GetIntArrayElements(minor_ref, (jboolean *)0);
-    minor = minor_base + minorOffset;
 
     _returnValue = eglInitialize(
         (EGLDisplay)dpy_native,
@@ -289,26 +281,22 @@
     jint _num_configRemaining;
     EGLint *num_config = (EGLint *) 0;
 
-    if (!configs_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "configs == null";
-        goto exit;
+    if (configs_ref) {
+        if (configsOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "configsOffset < 0";
+            goto exit;
+        }
+        _configsRemaining = _env->GetArrayLength(configs_ref) - configsOffset;
+        if (_configsRemaining < config_size) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "length - configsOffset < config_size < needed";
+            goto exit;
+        }
+        configs = new EGLConfig[_configsRemaining];
     }
-    if (configsOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "configsOffset < 0";
-        goto exit;
-    }
-    _configsRemaining = _env->GetArrayLength(configs_ref) - configsOffset;
-    if (_configsRemaining < config_size) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length - configsOffset < config_size < needed";
-        goto exit;
-    }
-    configs = new EGLConfig[_configsRemaining];
 
     if (!num_config_ref) {
         _exception = 1;
@@ -401,26 +389,22 @@
         goto exit;
     }
 
-    if (!configs_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "configs == null";
-        goto exit;
+    if (configs_ref) {
+        if (configsOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "configsOffset < 0";
+            goto exit;
+        }
+        _configsRemaining = _env->GetArrayLength(configs_ref) - configsOffset;
+        if (_configsRemaining < config_size) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "length - configsOffset < config_size < needed";
+            goto exit;
+        }
+        configs = new EGLConfig[_configsRemaining];
     }
-    if (configsOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "configsOffset < 0";
-        goto exit;
-    }
-    _configsRemaining = _env->GetArrayLength(configs_ref) - configsOffset;
-    if (_configsRemaining < config_size) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length - configsOffset < config_size < needed";
-        goto exit;
-    }
-    configs = new EGLConfig[_configsRemaining];
 
     if (!num_config_ref) {
         _exception = 1;
@@ -546,18 +530,32 @@
     EGLint *attrib_list = (EGLint *) 0;
     android::sp<ANativeWindow> window;
 
-    if (!attrib_list_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list == null";
-        goto exit;
+    if (attrib_list_ref) {
+        if (offset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "offset < 0";
+            goto exit;
+        }
+        _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
+        attrib_list_base = (EGLint *)
+            _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
+        attrib_list = attrib_list_base + offset;
+        attrib_list_sentinel = 0;
+        for (int i = _remaining - 1; i >= 0; i--)  {
+            if (*((EGLint*)(attrib_list + i)) == EGL_NONE){
+                attrib_list_sentinel = 1;
+                break;
+            }
+        }
+        if (attrib_list_sentinel == 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "attrib_list must contain EGL_NONE!";
+            goto exit;
+        }
     }
-    if (offset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "offset < 0";
-        goto exit;
-    }
+
     if (win == NULL) {
 not_valid_surface:
         _exception = 1;
@@ -571,24 +569,6 @@
     if (window == NULL)
         goto not_valid_surface;
 
-    _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
-    attrib_list_base = (EGLint *)
-        _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
-    attrib_list = attrib_list_base + offset;
-    attrib_list_sentinel = 0;
-    for (int i = _remaining - 1; i >= 0; i--)  {
-        if (*((EGLint*)(attrib_list + i)) == EGL_NONE){
-            attrib_list_sentinel = 1;
-            break;
-        }
-    }
-    if (attrib_list_sentinel == 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list must contain EGL_NONE!";
-        goto exit;
-    }
-
     _returnValue = eglCreateWindowSurface(
         (EGLDisplay)dpy_native,
         (EGLConfig)config_native,
@@ -703,34 +683,30 @@
     jint _remaining;
     EGLint *attrib_list = (EGLint *) 0;
 
-    if (!attrib_list_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list == null";
-        goto exit;
-    }
-    if (offset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "offset < 0";
-        goto exit;
-    }
-    _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
-    attrib_list_base = (EGLint *)
-        _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
-    attrib_list = attrib_list_base + offset;
-    attrib_list_sentinel = false;
-    for (int i = _remaining - 1; i >= 0; i--)  {
-        if (attrib_list[i] == EGL_NONE){
-            attrib_list_sentinel = true;
-            break;
+    if (attrib_list_ref) {
+        if (offset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "offset < 0";
+            goto exit;
         }
-    }
-    if (attrib_list_sentinel == false) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list must contain EGL_NONE!";
-        goto exit;
+        _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
+        attrib_list_base = (EGLint *)
+            _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
+        attrib_list = attrib_list_base + offset;
+        attrib_list_sentinel = false;
+        for (int i = _remaining - 1; i >= 0; i--)  {
+            if (attrib_list[i] == EGL_NONE){
+                attrib_list_sentinel = true;
+                break;
+            }
+        }
+        if (attrib_list_sentinel == false) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "attrib_list must contain EGL_NONE!";
+            goto exit;
+        }
     }
 
     _returnValue = eglCreatePbufferSurface(
@@ -882,34 +858,30 @@
     jint _remaining;
     EGLint *attrib_list = (EGLint *) 0;
 
-    if (!attrib_list_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list == null";
-        goto exit;
-    }
-    if (offset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "offset < 0";
-        goto exit;
-    }
-    _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
-    attrib_list_base = (EGLint *)
-        _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
-    attrib_list = attrib_list_base + offset;
-    attrib_list_sentinel = false;
-    for (int i = _remaining - 1; i >= 0; i--)  {
-        if (attrib_list[i] == EGL_NONE){
-            attrib_list_sentinel = true;
-            break;
+    if (attrib_list_ref) {
+        if (offset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "offset < 0";
+            goto exit;
         }
-    }
-    if (attrib_list_sentinel == false) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "attrib_list must contain EGL_NONE!";
-        goto exit;
+        _remaining = _env->GetArrayLength(attrib_list_ref) - offset;
+        attrib_list_base = (EGLint *)
+            _env->GetIntArrayElements(attrib_list_ref, (jboolean *)0);
+        attrib_list = attrib_list_base + offset;
+        attrib_list_sentinel = false;
+        for (int i = _remaining - 1; i >= 0; i--)  {
+            if (attrib_list[i] == EGL_NONE){
+                attrib_list_sentinel = true;
+                break;
+            }
+        }
+        if (attrib_list_sentinel == false) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "attrib_list must contain EGL_NONE!";
+            goto exit;
+        }
     }
 
     _returnValue = eglCreatePbufferFromClientBuffer(
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index b9f61a9..ac3bf7a 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -1783,22 +1783,18 @@
     jint _nameRemaining;
     char *name = (char *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!size_ref) {
         _exception = 1;
@@ -2111,22 +2107,18 @@
     jint _nameRemaining;
     char *name = (char *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!size_ref) {
         _exception = 1;
@@ -2434,28 +2426,24 @@
     jint _shadersRemaining;
     GLuint *shaders = (GLuint *) 0;
 
-    if (!count_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "count == null";
-        goto exit;
+    if (count_ref) {
+        if (countOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "countOffset < 0";
+            goto exit;
+        }
+        _countRemaining = _env->GetArrayLength(count_ref) - countOffset;
+        if (_countRemaining < 1) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "length - countOffset < 1 < needed";
+            goto exit;
+        }
+        count_base = (GLsizei *)
+            _env->GetIntArrayElements(count_ref, (jboolean *)0);
+        count = count_base + countOffset;
     }
-    if (countOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "countOffset < 0";
-        goto exit;
-    }
-    _countRemaining = _env->GetArrayLength(count_ref) - countOffset;
-    if (_countRemaining < 1) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length - countOffset < 1 < needed";
-        goto exit;
-    }
-    count_base = (GLsizei *)
-        _env->GetIntArrayElements(count_ref, (jboolean *)0);
-    count = count_base + countOffset;
 
     if (!shaders_ref) {
         _exception = 1;
@@ -2526,20 +2514,18 @@
             goto exit;
         }
     }
-    if (shaders_buf) {
-        shaders = (GLuint *)getPointer(_env, shaders_buf, (jarray*)&_shadersArray, &_shadersRemaining, &_shadersBufferOffset);
-        if (_shadersRemaining < maxcount) {
-            _exception = 1;
-            _exceptionType = "java/lang/IllegalArgumentException";
-            _exceptionMessage = "remaining() < maxcount < needed";
-            goto exit;
-        }
+    shaders = (GLuint *)getPointer(_env, shaders_buf, (jarray*)&_shadersArray, &_shadersRemaining, &_shadersBufferOffset);
+    if (_shadersRemaining < maxcount) {
+        _exception = 1;
+        _exceptionType = "java/lang/IllegalArgumentException";
+        _exceptionMessage = "remaining() < maxcount < needed";
+        goto exit;
     }
     if (count_buf && count == NULL) {
         char * _countBase = (char *)_env->GetIntArrayElements(_countArray, (jboolean *) 0);
         count = (GLsizei *) (_countBase + _countBufferOffset);
     }
-    if (shaders_buf && shaders == NULL) {
+    if (shaders == NULL) {
         char * _shadersBase = (char *)_env->GetIntArrayElements(_shadersArray, (jboolean *) 0);
         shaders = (GLuint *) (_shadersBase + _shadersBufferOffset);
     }
@@ -3262,22 +3248,18 @@
     jint _sourceRemaining;
     char *source = (char *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!source_ref) {
         _exception = 1;
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 8eb5044..c9b5af7 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -501,8 +501,10 @@
     jint _remaining;
     GLvoid *pixels = (GLvoid *) 0;
 
-    pixels = (GLvoid *)getPointer(_env, pixels_buf, (jarray*)&_array, &_remaining, &_bufferOffset);
-    if (pixels == NULL) {
+    if (pixels_buf) {
+        pixels = (GLvoid *)getPointer(_env, pixels_buf, (jarray*)&_array, &_remaining, &_bufferOffset);
+    }
+    if (pixels_buf && pixels == NULL) {
         char * _pixelsBase = (char *)_env->GetPrimitiveArrayCritical(_array, (jboolean *) 0);
         pixels = (GLvoid *) (_pixelsBase + _bufferOffset);
     }
@@ -1910,22 +1912,18 @@
     jint _nameRemaining;
     char *name = (char *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!size_ref) {
         _exception = 1;
@@ -3591,22 +3589,18 @@
     jint _nameRemaining;
     GLchar* _name = (GLchar*)0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        _length_base = (GLsizei*)_env->GetIntArrayElements(
+                length_ref, (jboolean*)0);
+        _length = _length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    _length_base = (GLsizei*)_env->GetIntArrayElements(
-            length_ref, (jboolean*)0);
-    _length = _length_base + lengthOffset;
 
     if (!name_ref) {
         _exception = 1;
@@ -3900,22 +3894,18 @@
     jint _valuesRemaining;
     GLint *values = (GLint *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!values_ref) {
         _exception = 1;
@@ -3969,9 +3959,11 @@
     jint _valuesRemaining;
     GLint *values = (GLint *) 0;
 
-    length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    if (length_buf) {
+        length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    }
     values = (GLint *)getPointer(_env, values_buf, (jarray*)&_valuesArray, &_valuesRemaining, &_valuesBufferOffset);
-    if (length == NULL) {
+    if (length_buf && length == NULL) {
         char * _lengthBase = (char *)_env->GetIntArrayElements(_lengthArray, (jboolean *) 0);
         length = (GLsizei *) (_lengthBase + _lengthBufferOffset);
     }
@@ -4772,22 +4764,18 @@
     jint _binaryRemaining;
     GLvoid *binary = (GLvoid *) 0;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!binaryFormat_ref) {
         _exception = 1;
@@ -4853,10 +4841,12 @@
     jint _binaryRemaining;
     GLvoid *binary = (GLvoid *) 0;
 
-    length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    if (length_buf) {
+        length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    }
     binaryFormat = (GLenum *)getPointer(_env, binaryFormat_buf, (jarray*)&_binaryFormatArray, &_binaryFormatRemaining, &_binaryFormatBufferOffset);
     binary = (GLvoid *)getPointer(_env, binary_buf, (jarray*)&_binaryArray, &_binaryRemaining, &_binaryBufferOffset);
-    if (length == NULL) {
+    if (length_buf && length == NULL) {
         char * _lengthBase = (char *)_env->GetIntArrayElements(_lengthArray, (jboolean *) 0);
         length = (GLsizei *) (_lengthBase + _lengthBufferOffset);
     }
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index e427388..5751add 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -714,22 +714,18 @@
         _env->GetIntArrayElements(props_ref, (jboolean *)0);
     props = props_base + propsOffset;
 
-    if (!length_ref) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "length == null";
-        goto exit;
+    if (length_ref) {
+        if (lengthOffset < 0) {
+            _exception = 1;
+            _exceptionType = "java/lang/IllegalArgumentException";
+            _exceptionMessage = "lengthOffset < 0";
+            goto exit;
+        }
+        _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+        length_base = (GLsizei *)
+            _env->GetIntArrayElements(length_ref, (jboolean *)0);
+        length = length_base + lengthOffset;
     }
-    if (lengthOffset < 0) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "lengthOffset < 0";
-        goto exit;
-    }
-    _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
-    length_base = (GLsizei *)
-        _env->GetIntArrayElements(length_ref, (jboolean *)0);
-    length = length_base + lengthOffset;
 
     if (!params_ref) {
         _exception = 1;
@@ -795,13 +791,15 @@
     GLint *params = (GLint *) 0;
 
     props = (GLenum *)getPointer(_env, props_buf, (jarray*)&_propsArray, &_propsRemaining, &_propsBufferOffset);
-    length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    if (length_buf) {
+        length = (GLsizei *)getPointer(_env, length_buf, (jarray*)&_lengthArray, &_lengthRemaining, &_lengthBufferOffset);
+    }
     params = (GLint *)getPointer(_env, params_buf, (jarray*)&_paramsArray, &_paramsRemaining, &_paramsBufferOffset);
     if (props == NULL) {
         char * _propsBase = (char *)_env->GetIntArrayElements(_propsArray, (jboolean *) 0);
         props = (GLenum *) (_propsBase + _propsBufferOffset);
     }
-    if (length == NULL) {
+    if (length_buf && length == NULL) {
         char * _lengthBase = (char *)_env->GetIntArrayElements(_lengthArray, (jboolean *) 0);
         length = (GLsizei *) (_lengthBase + _lengthBufferOffset);
     }
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 180c693..5be7be0 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -646,13 +646,9 @@
     const char * _exceptionMessage = NULL;
     const char* _nativelabel = 0;
 
-    if (!label) {
-        _exception = 1;
-        _exceptionType = "java/lang/IllegalArgumentException";
-        _exceptionMessage = "label == null";
-        goto exit;
+    if (label) {
+        _nativelabel = _env->GetStringUTFChars(label, 0);
     }
-    _nativelabel = _env->GetStringUTFChars(label, 0);
 
     glObjectLabelKHR(
         (GLenum)identifier,
@@ -660,8 +656,6 @@
         (GLsizei)length,
         (GLchar *)_nativelabel
     );
-
-exit:
     if (_nativelabel) {
         _env->ReleaseStringUTFChars(label, _nativelabel);
     }
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
new file mode 100644
index 0000000..0927120
--- /dev/null
+++ b/core/jni/android_util_PathParser.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+
+#include <PathParser.h>
+#include <SkPath.h>
+#include <utils/VectorDrawableUtils.h>
+
+#include <android/log.h>
+#include "core_jni_helpers.h"
+
+namespace android {
+
+using namespace uirenderer;
+
+static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr,
+        jint strLength) {
+    const char* pathString = env->GetStringUTFChars(inputPathStr, NULL);
+    SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle);
+
+    PathParser::ParseResult result;
+    PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
+    env->ReleaseStringUTFChars(inputPathStr, pathString);
+    if (result.failureOccurred) {
+        ALOGE(result.failureMessage.c_str());
+    }
+    return !result.failureOccurred;
+}
+
+static long createEmptyPathData(JNIEnv*, jobject) {
+    PathData* pathData = new PathData();
+    return reinterpret_cast<jlong>(pathData);
+}
+
+static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+    PathData* newPathData = new PathData(*pathData);
+    return reinterpret_cast<jlong>(newPathData);
+}
+
+static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) {
+    const char* pathString = env->GetStringUTFChars(inputStr, NULL);
+    PathData* pathData = new PathData();
+    PathParser::ParseResult result;
+    PathParser::getPathDataFromString(pathData, &result, pathString, strLength);
+    env->ReleaseStringUTFChars(inputStr, pathString);
+    if (!result.failureOccurred) {
+        return reinterpret_cast<jlong>(pathData);
+    } else {
+        delete pathData;
+        ALOGE(result.failureMessage.c_str());
+        return NULL;
+    }
+}
+
+static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr,
+        jlong toPathDataPtr, jfloat fraction) {
+    PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+    return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData,
+            *toPathData, fraction);
+}
+
+static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle);
+    delete pathData;
+}
+
+static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) {
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+    return VectorDrawableUtils::canMorph(*fromPathData, *toPathData);
+}
+
+static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) {
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+    *outPathData = *fromPathData;
+}
+
+static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+    SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr);
+    VectorDrawableUtils::verbsToPath(skPath, *pathData);
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath},
+    {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData},
+    {"nCreatePathData", "!(J)J", (void*)createPathData},
+    {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath},
+    {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData},
+    {"nFinalize", "!(J)V", (void*)deletePathData},
+    {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData},
+    {"nSetPathData", "!(JJ)V", (void*)setPathData},
+    {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData},
+};
+
+int register_android_util_PathParser(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/util/PathParser", gMethods, NELEM(gMethods));
+}
+};
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index d04adbf..d7e2c02 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -24,6 +24,7 @@
 #include <android_runtime/Log.h>
 #include <utils/Log.h>
 #include <android/graphics/GraphicsJNI.h>
+#include "ScopedLocalRef.h"
 
 #include "core_jni_helpers.h"
 
@@ -35,6 +36,8 @@
     jfieldID mBitmap;
     jfieldID mHotSpotX;
     jfieldID mHotSpotY;
+    jfieldID mBitmapFrames;
+    jfieldID mDurationPerFrame;
     jmethodID getSystemIcon;
     jmethodID load;
 } gPointerIconClassInfo;
@@ -84,6 +87,19 @@
         env->DeleteLocalRef(bitmapObj);
     }
 
+    ScopedLocalRef<jobjectArray> bitmapFramesObj(env, reinterpret_cast<jobjectArray>(
+            env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmapFrames)));
+    if (bitmapFramesObj.get()) {
+        outPointerIcon->durationPerFrame = env->GetIntField(
+                loadedPointerIconObj, gPointerIconClassInfo.mDurationPerFrame);
+        jsize size = env->GetArrayLength(bitmapFramesObj.get());
+        outPointerIcon->bitmapFrames.resize(size);
+        for (jsize i = 0; i < size; ++i) {
+            ScopedLocalRef<jobject> bitmapObj(env, env->GetObjectArrayElement(bitmapFramesObj.get(), i));
+            GraphicsJNI::getSkBitmap(env, bitmapObj.get(), &(outPointerIcon->bitmapFrames[i]));
+        }
+    }
+
     env->DeleteLocalRef(loadedPointerIconObj);
     return OK;
 }
@@ -121,6 +137,12 @@
     gPointerIconClassInfo.mHotSpotY = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
             "mHotSpotY", "F");
 
+    gPointerIconClassInfo.mBitmapFrames = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
+            "mBitmapFrames", "[Landroid/graphics/Bitmap;");
+
+    gPointerIconClassInfo.mDurationPerFrame = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
+            "mDurationPerFrame", "I");
+
     gPointerIconClassInfo.getSystemIcon = GetStaticMethodIDOrDie(env, gPointerIconClassInfo.clazz,
             "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
 
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index 86f288d..ca08085 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -19,6 +19,8 @@
 
 #include "jni.h"
 
+#include <vector>
+
 #include <utils/Errors.h>
 #include <SkBitmap.h>
 
@@ -69,6 +71,8 @@
     SkBitmap bitmap;
     float hotSpotX;
     float hotSpotY;
+    std::vector<SkBitmap> bitmapFrames;
+    int32_t durationPerFrame;
 
     inline bool isNullIcon() {
         return style == POINTER_ICON_STYLE_NULL;
@@ -79,6 +83,8 @@
         bitmap.reset();
         hotSpotX = 0;
         hotSpotY = 0;
+        bitmapFrames.clear();
+        durationPerFrame = 0;
     }
 };
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3f1be45..73c7487 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -21,6 +21,7 @@
 #include <linux/fs.h>
 
 #include <list>
+#include <sstream>
 #include <string>
 
 #include <fcntl.h>
@@ -74,8 +75,10 @@
   MOUNT_EXTERNAL_WRITE = 3,
 };
 
-static void RuntimeAbort(JNIEnv* env) {
-  env->FatalError("RuntimeAbort");
+static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
+  std::ostringstream oss;
+  oss << __FILE__ << ":" << line << ": " << msg;
+  env->FatalError(oss.str().c_str());
 }
 
 // This signal handler is for zygote mode, since the zygote must reap its children
@@ -169,12 +172,11 @@
 
   ScopedIntArrayRO gids(env, javaGids);
   if (gids.get() == NULL) {
-      RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "Getting gids int array failed");
   }
   int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
   if (rc == -1) {
-    ALOGE("setgroups failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "setgroups failed");
   }
 }
 
@@ -194,8 +196,7 @@
     ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
     ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
     if (javaRlimit.size() != 3) {
-      ALOGE("rlimits array must have a second dimension of size 3");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "rlimits array must have a second dimension of size 3");
     }
 
     rlim.rlim_cur = javaRlimit[1];
@@ -205,7 +206,7 @@
     if (rc == -1) {
       ALOGE("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
             rlim.rlim_max);
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setrlimit failed");
     }
   }
 }
@@ -216,8 +217,7 @@
 static void EnableKeepCapabilities(JNIEnv* env) {
   int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
   if (rc == -1) {
-    ALOGE("prctl(PR_SET_KEEPCAPS) failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "prctl(PR_SET_KEEPCAPS) failed");
   }
 }
 
@@ -229,8 +229,7 @@
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
               "your kernel is compiled with file capabilities support");
       } else {
-        ALOGE("prctl(PR_CAPBSET_DROP) failed");
-        RuntimeAbort(env);
+        RuntimeAbort(env, __LINE__, "prctl(PR_CAPBSET_DROP) failed");
       }
     }
   }
@@ -251,7 +250,7 @@
 
   if (capset(&capheader, &capdata[0]) == -1) {
     ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective);
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "capset failed");
   }
 }
 
@@ -259,7 +258,7 @@
   errno = -set_sched_policy(0, SP_DEFAULT);
   if (errno != 0) {
     ALOGE("set_sched_policy(0, SP_DEFAULT) failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "set_sched_policy(0, SP_DEFAULT) failed");
   }
 }
 
@@ -370,8 +369,7 @@
   jsize count = env->GetArrayLength(fdsToClose);
   ScopedIntArrayRO ar(env, fdsToClose);
   if (ar.get() == NULL) {
-      ALOGE("Bad fd array");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Bad fd array");
   }
   jsize i;
   int devnull;
@@ -379,13 +377,13 @@
     devnull = open("/dev/null", O_RDWR);
     if (devnull < 0) {
       ALOGE("Failed to open /dev/null: %s", strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Failed to open /dev/null");
       continue;
     }
     ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
     if (dup2(devnull, ar[i]) < 0) {
       ALOGE("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Failed dup2()");
     }
     close(devnull);
   }
@@ -493,8 +491,7 @@
         // FUSE hasn't been created yet by init.
         // In either case, continue without external storage.
       } else {
-        ALOGE("Cannot continue without emulated storage");
-        RuntimeAbort(env);
+        RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage");
       }
     }
 
@@ -522,13 +519,13 @@
     int rc = setresgid(gid, gid, gid);
     if (rc == -1) {
       ALOGE("setresgid(%d) failed: %s", gid, strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setresgid failed");
     }
 
     rc = setresuid(uid, uid, uid);
     if (rc == -1) {
       ALOGE("setresuid(%d) failed: %s", uid, strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setresuid failed");
     }
 
     if (NeedsNoRandomizeWorkaround()) {
@@ -550,8 +547,7 @@
         se_info = new ScopedUtfChars(env, java_se_info);
         se_info_c_str = se_info->c_str();
         if (se_info_c_str == NULL) {
-          ALOGE("se_info_c_str == NULL");
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "se_info_c_str == NULL");
         }
     }
     const char* se_name_c_str = NULL;
@@ -560,15 +556,14 @@
         se_name = new ScopedUtfChars(env, java_se_name);
         se_name_c_str = se_name->c_str();
         if (se_name_c_str == NULL) {
-          ALOGE("se_name_c_str == NULL");
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "se_name_c_str == NULL");
         }
     }
     rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
     if (rc == -1) {
       ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
             is_system_server, se_info_c_str, se_name_c_str);
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed");
     }
 
     // Make it easier to debug audit logs by setting the main thread's name to the
@@ -588,8 +583,7 @@
     env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
                               is_system_server ? NULL : instructionSet);
     if (env->ExceptionCheck()) {
-      ALOGE("Error calling post fork hooks.");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
     }
   } else if (pid > 0) {
     // the parent process
@@ -641,7 +635,7 @@
       int status;
       if (waitpid(pid, &status, WNOHANG) == pid) {
           ALOGE("System server process %d has died. Restarting Zygote!", pid);
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "System server process has died. Restarting Zygote!");
       }
   }
   return pid;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 561bcbc..6338088 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1683,6 +1683,10 @@
     <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"
         android:protectionLevel="system|signature" />
 
+    <!-- @hide -->
+    <permission android:name="android.permission.STORAGE_INTERNAL"
+        android:protectionLevel="signature" />
+
     <!-- Allows access to ASEC non-destructive API calls
          @hide  -->
     <permission android:name="android.permission.ASEC_ACCESS"
@@ -1827,6 +1831,12 @@
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to bind to third party quick settings tiles.
+         <p>Should only be requested by the System, should be required by
+         QSTileService declarations.-->
+    <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to force a BACK operation on whatever is the
          top activity.
          <p>Not for use by third-party applications.
@@ -2702,7 +2712,9 @@
                  android:killAfterRestore="false"
                  android:icon="@drawable/ic_launcher_android"
                  android:supportsRtl="true"
-                 android:theme="@style/Theme.Material.DayNight.DarkActionBar">
+                 android:theme="@style/Theme.Material.DayNight.DarkActionBar"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
         <activity android:name="com.android.internal.app.ChooserActivity"
                 android:theme="@style/Theme.DeviceDefault.Resolver"
                 android:finishOnCloseSystemDialogs="true"
@@ -2829,6 +2841,17 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.SystemUserHomeActivity"
+                  android:enabled="false"
+                  android:process=":ui"
+                  android:systemUserOnly="true"
+                  android:theme="@style/Theme.Translucent.NoTitleBar">
+            <intent-filter android:priority="-100">
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+
         <receiver android:name="com.android.server.BootReceiver"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
diff --git a/core/res/res/drawable-mdpi/pointer_wait_0.png b/core/res/res/drawable-mdpi/pointer_wait_0.png
new file mode 100644
index 0000000..adb7806
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_1.png b/core/res/res/drawable-mdpi/pointer_wait_1.png
new file mode 100644
index 0000000..fc6b42f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_10.png b/core/res/res/drawable-mdpi/pointer_wait_10.png
new file mode 100644
index 0000000..02968b5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_11.png b/core/res/res/drawable-mdpi/pointer_wait_11.png
new file mode 100644
index 0000000..24f866b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_12.png b/core/res/res/drawable-mdpi/pointer_wait_12.png
new file mode 100644
index 0000000..d1a31bc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_13.png b/core/res/res/drawable-mdpi/pointer_wait_13.png
new file mode 100644
index 0000000..b0c6798
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_14.png b/core/res/res/drawable-mdpi/pointer_wait_14.png
new file mode 100644
index 0000000..721e86d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_15.png b/core/res/res/drawable-mdpi/pointer_wait_15.png
new file mode 100644
index 0000000..adb0199
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_16.png b/core/res/res/drawable-mdpi/pointer_wait_16.png
new file mode 100644
index 0000000..3695c18
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_17.png b/core/res/res/drawable-mdpi/pointer_wait_17.png
new file mode 100644
index 0000000..861605e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_18.png b/core/res/res/drawable-mdpi/pointer_wait_18.png
new file mode 100644
index 0000000..f5dfdcf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_19.png b/core/res/res/drawable-mdpi/pointer_wait_19.png
new file mode 100644
index 0000000..9d51f79
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_2.png b/core/res/res/drawable-mdpi/pointer_wait_2.png
new file mode 100644
index 0000000..d73a154
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_20.png b/core/res/res/drawable-mdpi/pointer_wait_20.png
new file mode 100644
index 0000000..81d1d51
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_21.png b/core/res/res/drawable-mdpi/pointer_wait_21.png
new file mode 100644
index 0000000..331820b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_22.png b/core/res/res/drawable-mdpi/pointer_wait_22.png
new file mode 100644
index 0000000..2678d32
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_23.png b/core/res/res/drawable-mdpi/pointer_wait_23.png
new file mode 100644
index 0000000..d54d9eb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_24.png b/core/res/res/drawable-mdpi/pointer_wait_24.png
new file mode 100644
index 0000000..442ace7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_25.png b/core/res/res/drawable-mdpi/pointer_wait_25.png
new file mode 100644
index 0000000..27ce60d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_26.png b/core/res/res/drawable-mdpi/pointer_wait_26.png
new file mode 100644
index 0000000..8143634
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_27.png b/core/res/res/drawable-mdpi/pointer_wait_27.png
new file mode 100644
index 0000000..496ab9a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_28.png b/core/res/res/drawable-mdpi/pointer_wait_28.png
new file mode 100644
index 0000000..a2aab2b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_29.png b/core/res/res/drawable-mdpi/pointer_wait_29.png
new file mode 100644
index 0000000..646d153
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_3.png b/core/res/res/drawable-mdpi/pointer_wait_3.png
new file mode 100644
index 0000000..9f45afe
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_30.png b/core/res/res/drawable-mdpi/pointer_wait_30.png
new file mode 100644
index 0000000..27b3fc4
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_31.png b/core/res/res/drawable-mdpi/pointer_wait_31.png
new file mode 100644
index 0000000..6dbe184
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_32.png b/core/res/res/drawable-mdpi/pointer_wait_32.png
new file mode 100644
index 0000000..9f072ef
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_33.png b/core/res/res/drawable-mdpi/pointer_wait_33.png
new file mode 100644
index 0000000..881ec5f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_34.png b/core/res/res/drawable-mdpi/pointer_wait_34.png
new file mode 100644
index 0000000..94961e3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_35.png b/core/res/res/drawable-mdpi/pointer_wait_35.png
new file mode 100644
index 0000000..dfa65d7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_4.png b/core/res/res/drawable-mdpi/pointer_wait_4.png
new file mode 100644
index 0000000..5d3d652
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_5.png b/core/res/res/drawable-mdpi/pointer_wait_5.png
new file mode 100644
index 0000000..d440a82
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_6.png b/core/res/res/drawable-mdpi/pointer_wait_6.png
new file mode 100644
index 0000000..ae65590
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_7.png b/core/res/res/drawable-mdpi/pointer_wait_7.png
new file mode 100644
index 0000000..cd84aa5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_8.png b/core/res/res/drawable-mdpi/pointer_wait_8.png
new file mode 100644
index 0000000..0b81a9a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_9.png b/core/res/res/drawable-mdpi/pointer_wait_9.png
new file mode 100644
index 0000000..c13a90c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_0.png b/core/res/res/drawable-xhdpi/pointer_wait_0.png
new file mode 100644
index 0000000..5396784
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_1.png b/core/res/res/drawable-xhdpi/pointer_wait_1.png
new file mode 100644
index 0000000..25edbf5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_10.png b/core/res/res/drawable-xhdpi/pointer_wait_10.png
new file mode 100644
index 0000000..96d93a9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_11.png b/core/res/res/drawable-xhdpi/pointer_wait_11.png
new file mode 100644
index 0000000..cd78675
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_12.png b/core/res/res/drawable-xhdpi/pointer_wait_12.png
new file mode 100644
index 0000000..1b2c7b2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_13.png b/core/res/res/drawable-xhdpi/pointer_wait_13.png
new file mode 100644
index 0000000..3b00f10
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_14.png b/core/res/res/drawable-xhdpi/pointer_wait_14.png
new file mode 100644
index 0000000..eca5c3f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_15.png b/core/res/res/drawable-xhdpi/pointer_wait_15.png
new file mode 100644
index 0000000..0fc2085
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_16.png b/core/res/res/drawable-xhdpi/pointer_wait_16.png
new file mode 100644
index 0000000..db13cf6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_17.png b/core/res/res/drawable-xhdpi/pointer_wait_17.png
new file mode 100644
index 0000000..9b6fac5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_18.png b/core/res/res/drawable-xhdpi/pointer_wait_18.png
new file mode 100644
index 0000000..c56ff6c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_19.png b/core/res/res/drawable-xhdpi/pointer_wait_19.png
new file mode 100644
index 0000000..22b7c90
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_2.png b/core/res/res/drawable-xhdpi/pointer_wait_2.png
new file mode 100644
index 0000000..4bdbe3f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_20.png b/core/res/res/drawable-xhdpi/pointer_wait_20.png
new file mode 100644
index 0000000..6d042fb
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_21.png b/core/res/res/drawable-xhdpi/pointer_wait_21.png
new file mode 100644
index 0000000..e3ab63f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_22.png b/core/res/res/drawable-xhdpi/pointer_wait_22.png
new file mode 100644
index 0000000..b25f6b7d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_23.png b/core/res/res/drawable-xhdpi/pointer_wait_23.png
new file mode 100644
index 0000000..49faba9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_24.png b/core/res/res/drawable-xhdpi/pointer_wait_24.png
new file mode 100644
index 0000000..e91c340
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_25.png b/core/res/res/drawable-xhdpi/pointer_wait_25.png
new file mode 100644
index 0000000..f4785c6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_26.png b/core/res/res/drawable-xhdpi/pointer_wait_26.png
new file mode 100644
index 0000000..ea902f8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_27.png b/core/res/res/drawable-xhdpi/pointer_wait_27.png
new file mode 100644
index 0000000..7d628c3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_28.png b/core/res/res/drawable-xhdpi/pointer_wait_28.png
new file mode 100644
index 0000000..92d6dc1
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_29.png b/core/res/res/drawable-xhdpi/pointer_wait_29.png
new file mode 100644
index 0000000..5a8d189
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_3.png b/core/res/res/drawable-xhdpi/pointer_wait_3.png
new file mode 100644
index 0000000..de4d79c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_30.png b/core/res/res/drawable-xhdpi/pointer_wait_30.png
new file mode 100644
index 0000000..ba04b5e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_31.png b/core/res/res/drawable-xhdpi/pointer_wait_31.png
new file mode 100644
index 0000000..3ef8e98
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_32.png b/core/res/res/drawable-xhdpi/pointer_wait_32.png
new file mode 100644
index 0000000..3297a7d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_33.png b/core/res/res/drawable-xhdpi/pointer_wait_33.png
new file mode 100644
index 0000000..b0ac3b9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_34.png b/core/res/res/drawable-xhdpi/pointer_wait_34.png
new file mode 100644
index 0000000..0eaa386
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_35.png b/core/res/res/drawable-xhdpi/pointer_wait_35.png
new file mode 100644
index 0000000..73894d8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_4.png b/core/res/res/drawable-xhdpi/pointer_wait_4.png
new file mode 100644
index 0000000..ea44e85
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_5.png b/core/res/res/drawable-xhdpi/pointer_wait_5.png
new file mode 100644
index 0000000..46c399d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_6.png b/core/res/res/drawable-xhdpi/pointer_wait_6.png
new file mode 100644
index 0000000..3b9aff6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_7.png b/core/res/res/drawable-xhdpi/pointer_wait_7.png
new file mode 100644
index 0000000..a54edc0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_8.png b/core/res/res/drawable-xhdpi/pointer_wait_8.png
new file mode 100644
index 0000000..2f30732
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_9.png b/core/res/res/drawable-xhdpi/pointer_wait_9.png
new file mode 100644
index 0000000..f39c7a7
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/decor_caption_title.xml
similarity index 84%
rename from core/res/res/drawable/non_client_decor_title.xml
rename to core/res/res/drawable/decor_caption_title.xml
index e50daea..591605d3 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/core/res/res/drawable/decor_caption_title.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_window_focused="true"
-          android:drawable="@drawable/non_client_decor_title_focused" />
-    <item android:drawable="@drawable/non_client_decor_title_unfocused" />
+          android:drawable="@drawable/decor_caption_title_focused" />
+    <item android:drawable="@drawable/decor_caption_title_unfocused" />
 </selector>
diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_focused.xml
rename to core/res/res/drawable/decor_caption_title_focused.xml
diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_unfocused.xml
rename to core/res/res/drawable/decor_caption_title_unfocused.xml
diff --git a/core/res/res/drawable/pointer_wait.xml b/core/res/res/drawable/pointer_wait.xml
new file mode 100644
index 0000000..8955ce8
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
+  <item android:drawable="@drawable/pointer_wait_1" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_2" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_3" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_4" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_5" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_6" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_7" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_8" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_9" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_10" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_11" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_12" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_13" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_14" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_15" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_16" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_17" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_18" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_19" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_20" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_21" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_22" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_23" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_24" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_25" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_26" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_27" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_28" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_29" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_30" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_31" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_32" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_33" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_34" android:duration="25"/>
+  <item android:drawable="@drawable/pointer_wait_35" android:duration="25"/>
+</animation-list>
diff --git a/core/res/res/drawable/pointer_wait_icon.xml b/core/res/res/drawable/pointer_wait_icon.xml
new file mode 100644
index 0000000..d9b03b0
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_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_wait"
+    android:hotSpotX="7dp"
+    android:hotSpotY="7dp" />
diff --git a/core/res/res/layout/alert_dialog_button_bar_material.xml b/core/res/res/layout/alert_dialog_button_bar_material.xml
index 6e102f3..f7974a5 100644
--- a/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -27,7 +27,6 @@
     android:paddingTop="4dp"
     android:paddingBottom="4dp"
     android:gravity="bottom"
-    android:allowStacking="@bool/allow_stacked_button_bar"
     style="?attr/buttonBarStyle">
 
     <Button
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index a4388f6..821b588 100644
--- a/core/res/res/layout/date_picker_header_material.xml
+++ b/core/res/res/layout/date_picker_header_material.xml
@@ -19,19 +19,27 @@
               android:id="@+id/date_picker_header"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:paddingBottom="18dp"
               android:paddingStart="?attr/dialogPreferredPadding"
               android:paddingEnd="?attr/dialogPreferredPadding"
-              android:orientation="vertical">
+              android:paddingTop="16dp"
+              android:paddingBottom="18dp"
+              android:orientation="vertical"
+              android:clipToPadding="false"
+              android:clipChildren="false">
 
-    <!-- Top padding should stay on this view so that
-         the touch target is a bit larger. -->
     <TextView
         android:id="@+id/date_picker_header_year"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingTop="16dp"
-        android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" />
+        android:focusable="true"
+        android:layout_marginStart="-8dp"
+        android:layout_marginEnd="-8dp"
+        android:layout_marginTop="-8dp"
+        android:layout_marginBottom="-8dp"
+        android:padding="8dp"
+        android:background="?attr/selectableItemBackground"
+        android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel"
+        android:nextFocusForward="@+id/prev" />
 
     <TextView
         android:id="@+id/date_picker_header_date"
diff --git a/core/res/res/layout/date_picker_month_item_material.xml b/core/res/res/layout/date_picker_month_item_material.xml
index cb79cee..52f7b8e 100644
--- a/core/res/res/layout/date_picker_month_item_material.xml
+++ b/core/res/res/layout/date_picker_month_item_material.xml
@@ -21,4 +21,5 @@
     android:layout_height="wrap_content"
     android:paddingStart="@dimen/day_picker_padding_horizontal"
     android:paddingEnd="@dimen/day_picker_padding_horizontal"
-    android:paddingTop="@dimen/day_picker_padding_top" />
+    android:paddingTop="@dimen/day_picker_padding_top"
+    android:focusable="true"/>
diff --git a/core/res/res/layout/day_picker_content_material.xml b/core/res/res/layout/day_picker_content_material.xml
index b582d74..d77e8dc 100644
--- a/core/res/res/layout/day_picker_content_material.xml
+++ b/core/res/res/layout/day_picker_content_material.xml
@@ -33,7 +33,9 @@
         android:src="@drawable/ic_chevron_start"
         android:background="?attr/selectableItemBackgroundBorderless"
         android:contentDescription="@string/date_picker_prev_month_button"
-        android:visibility="invisible" />
+        android:visibility="invisible"
+        android:nextFocusForward="@+id/next"
+        android:nextFocusDown="@+id/month_view" />
 
     <ImageButton
         android:id="@+id/next"
@@ -44,6 +46,8 @@
         android:src="@drawable/ic_chevron_end"
         android:background="?attr/selectableItemBackgroundBorderless"
         android:contentDescription="@string/date_picker_next_month_button"
-        android:visibility="invisible" />
+        android:visibility="invisible"
+        android:nextFocusForward="@+id/month_view"
+        android:nextFocusDown="@+id/month_view"/>
 
 </FrameLayout>
diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/decor_caption_dark.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_dark.xml
rename to core/res/res/layout/decor_caption_dark.xml
index 40b8960..273264d 100644
--- a/core/res/res/layout/non_client_decor_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -17,16 +17,17 @@
 */
 -->
 
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"
-        android:background="@drawable/non_client_decor_title"
+        android:background="@drawable/decor_caption_title"
         android:focusable="false"
         android:descendantFocusability="blocksDescendants" >
         <LinearLayout
@@ -53,4 +54,4 @@
             android:contentDescription="@string/close_button_text"
             android:background="@drawable/decor_close_button_dark" />
     </LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/decor_caption_light.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_light.xml
rename to core/res/res/layout/decor_caption_light.xml
index c75d526..fd9198e 100644
--- a/core/res/res/layout/non_client_decor_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -17,16 +17,17 @@
 */
 -->
 
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"
-        android:background="@drawable/non_client_decor_title"
+        android:background="@drawable/decor_caption_title"
         android:focusable="false"
         android:descendantFocusability="blocksDescendants" >
         <LinearLayout
@@ -53,4 +54,4 @@
             android:contentDescription="@string/close_button_text"
             android:background="@drawable/decor_close_button_light" />
     </LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index f073c33..fc53a1a 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -24,18 +24,23 @@
     android:background="@android:color/transparent"
     android:layout_removeBorders="true">
 
-    <ListView android:id="@android:id/list"
-        style="?attr/preferenceFragmentListStyle"
+    <FrameLayout
+        android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0px"
-        android:layout_weight="1"
-        android:paddingTop="0dip"
-        android:paddingBottom="@dimen/preference_fragment_padding_bottom"
-        android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
-        android:clipToPadding="false"
-        android:drawSelectorOnTop="false"
-        android:cacheColorHint="@android:color/transparent"
-        android:scrollbarAlwaysDrawVerticalTrack="true" />
+        android:layout_weight="1">
+        <ListView android:id="@android:id/list"
+            style="?attr/preferenceFragmentListStyle"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingTop="0dip"
+            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+            android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+            android:clipToPadding="false"
+            android:drawSelectorOnTop="false"
+            android:cacheColorHint="@android:color/transparent"
+            android:scrollbarAlwaysDrawVerticalTrack="true" />
+    </FrameLayout>
 
     <TextView android:id="@android:id/empty"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index 62bdffd..e411c0e 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -24,18 +24,23 @@
     android:background="@android:color/transparent"
     android:layout_removeBorders="true">
 
-    <ListView android:id="@android:id/list"
-        style="?attr/preferenceFragmentListStyle"
+    <FrameLayout
+        android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0px"
-        android:layout_weight="1"
-        android:paddingTop="0dip"
-        android:paddingBottom="@dimen/preference_fragment_padding_bottom"
-        android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
-        android:clipToPadding="false"
-        android:drawSelectorOnTop="false"
-        android:cacheColorHint="@android:color/transparent"
-        android:scrollbarAlwaysDrawVerticalTrack="true" />
+        android:layout_weight="1">
+        <ListView android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingTop="0dip"
+            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
+            style="?attr/preferenceFragmentListStyle"
+            android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
+            android:clipToPadding="false"
+            android:drawSelectorOnTop="false"
+            android:cacheColorHint="@android:color/transparent"
+            android:scrollbarAlwaysDrawVerticalTrack="true" />
+    </FrameLayout>
 
     <TextView android:id="@android:id/empty"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_widget_switch.xml b/core/res/res/layout/preference_widget_switch.xml
index 25e8aa6..80c572b 100644
--- a/core/res/res/layout/preference_widget_switch.xml
+++ b/core/res/res/layout/preference_widget_switch.xml
@@ -17,7 +17,7 @@
 <!-- Layout used by SwitchPreference for the switch widget style. This is inflated
      inside android.R.layout.preference. -->
 <Switch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+android:id/switchWidget"
+    android:id="@+android:id/switch_widget"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:focusable="false"
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index b58c0bd..7a7a814 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -420,7 +420,7 @@
     <string name="permdesc_useFingerprint" msgid="9165097460730684114">"Autoriser l\'application à utiliser le matériel d\'empreintes digitales pour l\'authentification"</string>
     <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Empreinte numérique partiellement détectée. Veuillez réessayer."</string>
     <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Impossible de reconnaître l\'empreinte numérique. Veuillez réessayer."</string>
-    <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"Le capteur d\'empreintes numériques est sale. Veuillez le nettoyer, puis réessayer."</string>
+    <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"Le lecteur d\'empreintes numériques est sale. Veuillez le nettoyer, puis réessayer."</string>
     <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Vous avez déplacé votre doigt trop rapidement. Veuillez réessayer."</string>
     <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Vous avez déplacé votre doigt trop lentement. Veuillez réessayer."</string>
   <string-array name="fingerprint_acquired_vendor">
diff --git a/core/res/res/values-h320dp/bools.xml b/core/res/res/values-h320dp/bools.xml
deleted file mode 100644
index 3bbfe96..0000000
--- a/core/res/res/values-h320dp/bools.xml
+++ /dev/null
@@ -1,20 +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>
-    <bool name="allow_stacked_button_bar">true</bool>
-</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index f4b6346..a6fdad3 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -699,7 +699,7 @@
     <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Fiók feloldása"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Túl sok mintarajzolási próbálkozás"</string>
     <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"A feloldáshoz jelentkezzen be Google-fiókjával."</string>
-    <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Felhasználónév (e-mail cím)"</string>
+    <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Felhasználónév (e-mail-cím)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Jelszó"</string>
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Bejelentkezés"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Érvénytelen felhasználónév vagy jelszó."</string>
@@ -1277,7 +1277,7 @@
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"A PIN kódok nem egyeznek."</string>
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Túl sok mintarajzolási próbálkozás"</string>
     <string name="kg_login_instructions" msgid="1100551261265506448">"A feloldáshoz jelentkezzen be Google-fiókjával."</string>
-    <string name="kg_login_username_hint" msgid="5718534272070920364">"Felhasználónév (e-mail cím)"</string>
+    <string name="kg_login_username_hint" msgid="5718534272070920364">"Felhasználónév (e-mail-cím)"</string>
     <string name="kg_login_password_hint" msgid="9057289103827298549">"Jelszó"</string>
     <string name="kg_login_submit_button" msgid="5355904582674054702">"Bejelentkezés"</string>
     <string name="kg_login_invalid_input" msgid="5754664119319872197">"Érvénytelen felhasználónév vagy jelszó."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f900301..6f6d1b0 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -420,7 +420,7 @@
     <string name="permdesc_useFingerprint" msgid="9165097460730684114">"指紋ハードウェアを認証に使用することをアプリに許可します"</string>
     <string name="fingerprint_acquired_partial" msgid="735082772341716043">"指紋を一部しか検出できませんでした。もう一度お試しください。"</string>
     <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"指紋を処理できませんでした。もう一度お試しください。"</string>
-    <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
+    <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋認証センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
     <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"指の動きが速すぎました。もう一度お試しください。"</string>
     <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"指の動きが遅すぎました。もう一度お試しください。"</string>
   <string-array name="fingerprint_acquired_vendor">
diff --git a/core/res/res/values-mcc208-mnc01/config.xml b/core/res/res/values-mcc208-mnc01/config.xml
index c56da24..5930e3a 100644
--- a/core/res/res/values-mcc208-mnc01/config.xml
+++ b/core/res/res/values-mcc208-mnc01/config.xml
@@ -28,9 +28,6 @@
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
     <string-array translatable="false" name="config_tether_apndata">
         <item>Orange Internet,orange.fr,,,orange,orange,,,,,208,01,1,DUN</item>
-        <item>[ApnSettingV3]Carrefour WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,33</item>
-        <item>[ApnSettingV3]VM WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,52</item>
-        <item>[ApnSettingV3]NRJWEB,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,4E</item>
     </string-array>
 
 </resources>
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index a32f266..d3640e5 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -29,11 +29,6 @@
     <string-array translatable="false" name="config_tether_apndata">
         <item>SFR option modem,websfr,,,,,,,,,208,10,,DUN</item>
         <item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
-        <item>[ApnSettingV3]Auchan,wap65,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,A MOBILE</item>
-        <item>[ApnSettingV3]LeclercMobile,wap66,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,LeclercMobile</item>
-        <item>[ApnSettingV3]Coriolis,fnetcoriolis,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,12</item>
-        <item>[ApnSettingV3]WEB La Poste Mobile,wapdebitel,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4C</item>
-        <item>[ApnSettingV3]Darty Surf Mails,wap68,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,44</item>
     </string-array>
 
     <string-array translatable="false" name="config_operatorConsideredNonRoaming">
diff --git a/core/res/res/values-mcc214-mnc07/config.xml b/core/res/res/values-mcc214-mnc07/config.xml
index 91571a5..4b7cc7c 100644
--- a/core/res/res/values-mcc214-mnc07/config.xml
+++ b/core/res/res/values-mcc214-mnc07/config.xml
@@ -28,7 +28,6 @@
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
     <string-array translatable="false" name="config_tether_apndata">
         <item>Conexión Compartida,movistar.es,,,MOVISTAR,MOVISTAR,,,,,214,07,1,DUN</item>
-        <item>[ApnSettingV3]Jazztel Internet,jazzinternet,,,,,,,,,214,07,,DUN,,,true,0,,,,,,,spn,JAZZTEL</item>
     </string-array>
 
 </resources>
diff --git a/core/res/res/values-mcc222-mnc10/config.xml b/core/res/res/values-mcc222-mnc10/config.xml
index 5a74462..cd6e8c6 100644
--- a/core/res/res/values-mcc222-mnc10/config.xml
+++ b/core/res/res/values-mcc222-mnc10/config.xml
@@ -20,16 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds.  Do not translate. -->
 <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",
diff --git a/core/res/res/values-mcc234-mnc20/config.xml b/core/res/res/values-mcc234-mnc20/config.xml
index 814960a..27c91d2 100644
--- a/core/res/res/values-mcc234-mnc20/config.xml
+++ b/core/res/res/values-mcc234-mnc20/config.xml
@@ -21,16 +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"
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 936ba59..74d7c98 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -229,7 +229,7 @@
     <string name="permgrouplab_contacts" msgid="3657758145679177612">"Kontakter"</string>
     <string name="permgroupdesc_contacts" msgid="6951499528303668046">"se kontaktene dine"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Posisjon"</string>
-    <string name="permgroupdesc_location" msgid="1346617465127855033">"tilgang til enhetens plassering"</string>
+    <string name="permgroupdesc_location" msgid="1346617465127855033">"få tilgang til enhetens plassering"</string>
     <string name="permgrouplab_calendar" msgid="5863508437783683902">"Kalender"</string>
     <string name="permgroupdesc_calendar" msgid="3889615280211184106">"åpne kalenderen din"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 2e06a0e..9af1378b 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -358,7 +358,7 @@
     <string name="permlab_accessImsCallService" msgid="3574943847181793918">"accesează serviciul de apelare IMS"</string>
     <string name="permdesc_accessImsCallService" msgid="8992884015198298775">"Permite aplicației să folosească serviciul IMS pentru apeluri, fără intervenția dvs."</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"citeşte starea și identitatea telefonului"</string>
-    <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Permite aplicației să acceseze funcţiile de telefon ale dispozitivului. Cu această permisiune aplicația stabilește numărul de telefon și ID-urile de dispozitiv, dacă un apel este activ, precum și numărul de la distanţă conectat printr-un apel."</string>
+    <string name="permdesc_readPhoneState" msgid="1639212771826125528">"Permite aplicației să acceseze funcţiile de telefon ale dispozitivului. Cu această permisiune aplicația stabilește numărul de telefon și ID-urile de dispozitiv, dacă un apel este activ, precum și numărul de la distanță conectat printr-un apel."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"împiedicarea computerului tablet PC să intre în repaus"</string>
     <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"împiedică intrarea televizorului în stare de inactivitate"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"împiedicare intrare telefon în repaus"</string>
@@ -398,9 +398,9 @@
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, utilizând adrese cu difuzare multiplă, nu doar televizorul dvs. Această funcție utilizează mai multă energie decât modul fără difuzare multiplă."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permite aplicației să primească pachetele trimise către toate dispozitivele dintr-o rețea Wi-Fi, utilizând adrese cu difuzare multiplă, nu doar telefonul dvs. Această funcție utilizează mai multă energie decât modul fără difuzare multiplă."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"accesează setările Bluetooth"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permite aplicației să configureze tableta Bluetooth locală, să descopere și să se împerecheze cu dispozitive la distanţă."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permite aplicației să configureze tableta Bluetooth locală, să descopere și să se împerecheze cu dispozitive la distanță."</string>
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Permite aplicației să configureze televizorul Bluetooth local, precum și să descopere și să se asocieze cu dispozitive la distanță."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permite aplicației să configureze telefonul Bluetooth local, să descopere și să se împerecheze cu dispozitive la distanţă."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permite aplicației să configureze telefonul Bluetooth local, să descopere și să se împerecheze cu dispozitive la distanță."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"se conectează și se deconectează de la WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Permite aplicației să stabilească dacă o rețea WiMAX este activată și să vadă informațiile cu privire la toate reţelele WiMAX conectate."</string>
     <string name="permlab_changeWimaxState" msgid="340465839241528618">"schimbaţi starea WiMAX"</string>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 94e9c4e..9c45c12 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -22,10 +22,6 @@
     <dimen name="thumbnail_width">360dp</dimen>
     <!-- The height that is used when creating thumbnails of applications. -->
     <dimen name="thumbnail_height">360dp</dimen>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="max_action_buttons">5</integer>
     <!-- Default height of an action bar. -->
     <dimen name="action_bar_default_height">56dip</dimen>
     <!-- Vertical padding around action bar icons. -->
@@ -88,7 +84,7 @@
     <!-- Size of the generic status lines keyguard's status view  -->
     <dimen name="kg_status_line_font_size">16sp</dimen>
 
-    <!-- Top margin for the clock view --> 
+    <!-- Top margin for the clock view -->
     <dimen name="kg_clock_top_margin">0dp</dimen>
 
     <!-- Size of margin on the right of keyguard's status view -->
diff --git a/core/res/res/values-w360dp/dimens.xml b/core/res/res/values-w360dp/dimens.xml
deleted file mode 100644
index 0f5d656..0000000
--- a/core/res/res/values-w360dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="max_action_buttons">3</integer>
-</resources>
diff --git a/core/res/res/values-w500dp/dimens.xml b/core/res/res/values-w500dp/dimens.xml
deleted file mode 100644
index 68841ca..0000000
--- a/core/res/res/values-w500dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="max_action_buttons">4</integer>
-</resources>
diff --git a/core/res/res/values-w600dp/dimens.xml b/core/res/res/values-w600dp/dimens.xml
deleted file mode 100644
index 83c45b5..0000000
--- a/core/res/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="max_action_buttons">5</integer>
-</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 6e79f3d..b3c70a5 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -376,10 +376,10 @@
     <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"允许应用更改平板电脑的时区。"</string>
     <string name="permdesc_setTimeZone" product="tv" msgid="888864653946175955">"允许应用更改电视的时区。"</string>
     <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"允许应用更改手机的时区。"</string>
-    <string name="permlab_getAccounts" msgid="1086795467760122114">"查找设备上的帐户"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"允许该应用获取平板电脑已知的帐户列表,其中可能包括由已安装的应用创建的所有帐户。"</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"允许应用获取电视已知的帐户列表,其中可能包括由已安装的应用创建的所有帐户。"</string>
-    <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"允许该应用获取手机已知的帐户列表,其中可能包括由已安装的应用创建的所有帐户。"</string>
+    <string name="permlab_getAccounts" msgid="1086795467760122114">"查找设备上的帐号"</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"允许该应用获取平板电脑已知的帐号列表,其中可能包括由已安装的应用创建的所有帐号。"</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"允许应用获取电视已知的帐号列表,其中可能包括由已安装的应用创建的所有帐号。"</string>
+    <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"允许该应用获取手机已知的帐号列表,其中可能包括由已安装的应用创建的所有帐号。"</string>
     <string name="permlab_accessNetworkState" msgid="4951027964348974773">"查看网络连接"</string>
     <string name="permdesc_accessNetworkState" msgid="8318964424675960975">"允许该应用查看网络连接的相关信息,例如存在和连接的网络。"</string>
     <string name="permlab_createNetworkSockets" msgid="7934516631384168107">"拥有完全的网络访问权限"</string>
@@ -436,11 +436,11 @@
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"指纹图标"</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"读取同步设置"</string>
-    <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"允许该应用读取某个帐户的同步设置。例如,此权限可确定“联系人”应用是否与某个帐户同步。"</string>
+    <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"允许该应用读取某个帐号的同步设置。例如,此权限可确定“联系人”应用是否与某个帐号同步。"</string>
     <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"启用和停用同步"</string>
-    <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"允许该应用修改某个帐户的同步设置。例如,此权限可用于在“联系人”应用与某个帐户之间启用同步。"</string>
+    <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"允许该应用修改某个帐号的同步设置。例如,此权限可用于在“联系人”应用与某个帐号之间启用同步。"</string>
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"读取同步统计信息"</string>
-    <string name="permdesc_readSyncStats" msgid="1510143761757606156">"允许该应用读取某个帐户的同步统计信息,包括同步活动历史记录和同步数据量。"</string>
+    <string name="permdesc_readSyncStats" msgid="1510143761757606156">"允许该应用读取某个帐号的同步统计信息,包括同步活动历史记录和同步数据量。"</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"读取您的USB存储设备中的内容"</string>
     <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"读取您的SD卡中的内容"</string>
     <string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"允许应用读取您USB存储设备中的内容。"</string>
@@ -696,9 +696,9 @@
     <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"您已经<xliff:g id="NUMBER">%d</xliff:g>次错误地尝试解锁手机。手机现在将恢复为出厂默认设置。"</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g>秒后重试。"</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘记了图案?"</string>
-    <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"帐户解锁"</string>
+    <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"帐号解锁"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"图案尝试次数过多"</string>
-    <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"要解除锁定,请使用您的Google帐户登录。"</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_submit_button" msgid="7130893694795786300">"登录"</string>
@@ -1088,13 +1088,13 @@
     <string name="ime_action_default" msgid="2840921885558045721">"执行"</string>
     <string name="dial_number_using" msgid="5789176425167573586">"拨打电话\n<xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="create_contact_using" msgid="4947405226788104538">"创建电话号码为\n<xliff:g id="NUMBER">%s</xliff:g> 的联系人"</string>
-    <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"以下一个或多个应用请求获得相应权限,以便在当前和以后访问您的帐户。"</string>
+    <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"以下一个或多个应用请求获得相应权限,以便在当前和以后访问您的帐号。"</string>
     <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"您是否同意此请求?"</string>
     <string name="grant_permissions_header_text" msgid="6874497408201826708">"访问权限请求"</string>
     <string name="allow" msgid="7225948811296386551">"允许"</string>
     <string name="deny" msgid="2081879885755434506">"拒绝"</string>
     <string name="permission_request_notification_title" msgid="6486759795926237907">"权限请求"</string>
-    <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"应用对帐户 <xliff:g id="ACCOUNT">%s</xliff:g>\n 提出权限请求。"</string>
+    <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"应用对帐号 <xliff:g id="ACCOUNT">%s</xliff:g>\n 提出权限请求。"</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>
@@ -1143,13 +1143,13 @@
     <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="ACCOUNT_NAME">%3$s</xliff:g> 在进行“<xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>”同步时删除了 <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> 项内容。您要如何处理这些删除的内容?"</string>
+    <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"帐号 <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g> 在进行“<xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>”同步时删除了 <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</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>
-    <string name="choose_account_label" msgid="5655203089746423927">"选择帐户"</string>
-    <string name="add_account_label" msgid="2935267344849993553">"添加帐户"</string>
-    <string name="add_account_button_label" msgid="3611982894853435874">"添加帐户"</string>
+    <string name="choose_account_label" msgid="5655203089746423927">"选择帐号"</string>
+    <string name="add_account_label" msgid="2935267344849993553">"添加帐号"</string>
+    <string name="add_account_button_label" msgid="3611982894853435874">"添加帐号"</string>
     <string name="number_picker_increment_button" msgid="2412072272832284313">"增大"</string>
     <string name="number_picker_decrement_button" msgid="476050778386779067">"减小"</string>
     <string name="number_picker_increment_scroll_mode" msgid="3073101067441638428">"触摸 <xliff:g id="VALUE">%s</xliff:g> 次并按住。"</string>
@@ -1276,13 +1276,13 @@
     <string name="kg_invalid_puk" msgid="3638289409676051243">"请重新输入正确的PUK码。如果尝试错误次数过多,SIM卡将永久停用。"</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN码不匹配"</string>
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"图案尝试次数过多"</string>
-    <string name="kg_login_instructions" msgid="1100551261265506448">"要解锁,请登录您的Google帐户。"</string>
+    <string name="kg_login_instructions" msgid="1100551261265506448">"要解锁,请登录您的Google帐号。"</string>
     <string name="kg_login_username_hint" msgid="5718534272070920364">"用户名(电子邮件地址)"</string>
     <string name="kg_login_password_hint" msgid="9057289103827298549">"密码"</string>
     <string name="kg_login_submit_button" msgid="5355904582674054702">"登录"</string>
     <string name="kg_login_invalid_input" msgid="5754664119319872197">"用户名或密码无效。"</string>
     <string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"忘记了用户名或密码?\n请访问 "<b>"google.com/accounts/recovery"</b>"。"</string>
-    <string name="kg_login_checking_password" msgid="1052685197710252395">"正在检查帐户…"</string>
+    <string name="kg_login_checking_password" msgid="1052685197710252395">"正在检查帐号…"</string>
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8276745642049502550">"您已经<xliff:g id="NUMBER_0">%1$d</xliff:g>次输错了PIN码。\n\n请在<xliff:g id="NUMBER_1">%2$d</xliff:g>秒后重试。"</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7813713389422226531">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次输错密码。\n\n请在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒后重试。"</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。\n\n请在 <xliff:g id="NUMBER_1">%2$d</xliff:g> 秒后重试。"</string>
@@ -1292,9 +1292,9 @@
     <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2072996269148483637">"您已经<xliff:g id="NUMBER">%d</xliff:g>次错误地尝试解锁平板电脑。平板电脑现在将恢复为出厂默认设置。"</string>
     <string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"您已经 <xliff:g id="NUMBER">%d</xliff:g> 次错误地尝试解锁电视。电视现在将恢复为出厂默认设置。"</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="4817627474419471518">"您已经<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">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐户解锁平板电脑。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用电子邮件帐户解锁电视。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
-    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐户解锁手机。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐号解锁平板电脑。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用电子邮件帐号解锁电视。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
+    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐号解锁手机。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string>
     <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"删除"</string>
     <string name="safe_media_volume_warning" product="default" msgid="2276318909314492312">"要将音量调高到推荐水平以上吗?\n\n长时间保持高音量可能会损伤听力。"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d7dd3ec..04f4fc2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3242,6 +3242,14 @@
              </p>
          -->
         <attr name="canRequestFilterKeyEvents" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to control
+             display magnification.
+             <p>
+             Required to allow setting the {@link android.accessibilityservice
+             #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
+             </p>
+         -->
+        <attr name="canControlMagnification" format="boolean" />
         <!-- Short description of the accessibility serivce purpose or behavior.-->
         <attr name="description" />
     </declare-styleable>
@@ -4240,7 +4248,8 @@
         <attr name="autoLink" />
         <!-- If set to false, keeps the movement method from being set
              to the link movement method even if autoLink causes links
-             to be found. -->
+             to be found or the input text contains a
+             {@link android.text.style.ClickableSpan ClickableSpan}. -->
         <attr name="linksClickable" format="boolean" />
         <!-- If set, specifies that this TextView has a numeric input method.
              The default is false.
@@ -7623,6 +7632,8 @@
         <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_WAIT -->
+        <attr name="pointerIconWait" format="reference"/>
         <!-- Reference to a pointer drawable with STYLE_CELL -->
         <attr name="pointerIconCell" format="reference"/>
         <!-- Reference to a pointer drawable with STYLE_CROSSHAIR -->
@@ -7714,6 +7725,7 @@
     <declare-styleable name="PreferenceFragment">
         <!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->
         <attr name="layout" />
+        <attr name="divider" />
     </declare-styleable>
 
     <!-- Base attributes available to PreferenceActivity. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 184f2ab..e376903 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1283,6 +1283,7 @@
         <attr name="multiArch" />
         <attr name="extractNativeLibs" />
         <attr name="forceDeviceEncrypted" format="boolean" />
+        <attr name="encryptionAware" />
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 7c63950..457131a 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -25,8 +25,4 @@
     <bool name="show_ongoing_ime_switcher">true</bool>
     <bool name="action_bar_expanded_action_views_exclusive">true</bool>
     <bool name="target_honeycomb_needs_options_menu">true</bool>
-
-    <!-- Whether to allow vertically stacked button bars. This is disabled for
-         configurations with a small (e.g. less than 320dp) screen height. -->
-    <bool name="allow_stacked_button_bar">false</bool>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 76f7062..539baa5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -411,7 +411,7 @@
     <bool translatable="false" name="config_wifi_revert_country_code_on_cellular_loss">false</bool>
 
     <!-- Boolean indicating whether or not wifi firmware debugging is enabled -->
-    <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">false</bool>
+    <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">true</bool>
 
     <!-- Integer specifying the basic autojoin parameters -->
     <integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_threshold">-65</integer>
@@ -1091,6 +1091,14 @@
          This feature should be disabled for most devices. -->
     <integer name="config_virtualKeyQuietTimeMillis">0</integer>
 
+    <!-- A list of potential packages, in priority order, that may contain an
+         ephemeral resolver. Each package will be be queried for a component
+         that has been granted the PACKAGE_EPHEMERAL_AGENT permission.
+         This may be empty if ephemeral apps are not supported. -->
+    <string-array name="config_ephemeralResolverPackage" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- Component name of the default wallpaper. This will be ImageWallpaper if not
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
@@ -1530,8 +1538,8 @@
     <!-- If the time difference is greater than this threshold in milliseconds,
          then update the time. -->
     <integer name="config_ntpThreshold">5000</integer>
-    <!-- Timeout to wait for NTP server response. -->
-    <integer name="config_ntpTimeout">20000</integer>
+    <!-- Timeout to wait for NTP server response in milliseconds. -->
+    <integer name="config_ntpTimeout">5000</integer>
 
     <!-- Default network policy warning threshold, in megabytes. -->
     <integer name="config_networkPolicyDefaultWarning">2048</integer>
@@ -1938,9 +1946,9 @@
      See {@link com.android.server.notification.NotificationSignalExtractor} -->
     <string-array name="config_notificationSignalExtractors">
         <item>com.android.server.notification.ValidateNotificationPeople</item>
-        <item>com.android.server.notification.PackagePriorityExtractor</item>
+        <item>com.android.server.notification.TopicPriorityExtractor</item>
         <item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
-        <item>com.android.server.notification.PackageVisibilityExtractor</item>
+        <item>com.android.server.notification.TopicVisibilityExtractor</item>
     </string-array>
 
     <!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 28756f5..01daf26 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -25,10 +25,7 @@
     <!-- The standard size (both width and height) of an application icon that
          will be displayed in the app launcher and elsewhere. -->
     <dimen name="app_icon_size">48dip</dimen>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="max_action_buttons">2</integer>
+
     <dimen name="toast_y_offset">64dip</dimen>
     <!-- Height of the status bar -->
     <dimen name="status_bar_height">24dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 695dafa..9d5e5ac 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -23,12 +23,14 @@
   <item type="id" name="empty" />
   <item type="id" name="hint" />
   <item type="id" name="icon" />
+  <item type="id" name="icon_frame" />
   <item type="id" name="icon_badge" />
   <item type="id" name="icon1" />
   <item type="id" name="icon2" />
   <item type="id" name="input" />
   <item type="id" name="left_icon" />
   <item type="id" name="list" />
+  <item type="id" name="list_container" />
   <item type="id" name="menu" />
   <item type="id" name="message" />
   <item type="id" name="primary" />
@@ -48,6 +50,7 @@
   <item type="id" name="lock_screen" />
   <item type="id" name="edit" />
   <item type="id" name="widget_frame" />
+  <item type="id" name="switch_widget" />
   <item type="id" name="button1" />
   <item type="id" name="button2" />
   <item type="id" name="button3" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 037f1c4..b6b2e20 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2679,6 +2679,10 @@
     <public type="attr" name="contextPopupMenuStyle" />
     <public type="attr" name="textAppearancePopupMenuHeader" />
     <public type="attr" name="windowBackgroundFallback" />
+    <public type="attr" name="forceDeviceEncrypted" />
+    <public type="attr" name="encryptionAware" />
+    <public type="attr" name="preferenceFragmentStyle" />
+    <public type="attr" name="canControlMagnification" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
@@ -2699,5 +2703,8 @@
     <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
 
     <public type="id" name="accessibilityActionSetProgress" />
+    <public type="id" name="icon_frame" />
+    <public type="id" name="list_container" />
+    <public type="id" name="switch_widget" />
 
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index faa76f2..1964bec 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -613,6 +613,12 @@
     <string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit
         card numbers and passwords.</string>
 
+    <!-- Title for the capability of an accessibility service to control display magnification. -->
+    <string name="capability_title_canControlMagnification">Control display magnification</string>
+    <!-- Description for the capability of an accessibility service to control display magnification. -->
+    <string name="capability_desc_canControlMagnification">Control the display\'s zoom level and
+        positioning.</string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4066,4 +4072,6 @@
         <item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
         <item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
     </plurals>
+
+    <string name="default_notification_topic_label">Miscellaneous</string>
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index b831df8..eb99077 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1339,6 +1339,7 @@
         <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="pointerIconWait">@drawable/pointer_wait_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>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 58640eb..4b2a451 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -40,6 +40,7 @@
         <item name="layout">@layout/preference_list_fragment_material</item>
         <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item>
         <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item>
+        <item name="divider">?attr/listDivider</item>
     </style>
 
     <style name="PreferenceActivity.Material">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cda7faa..edd3555 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -162,7 +162,7 @@
   <java-symbol type="id" name="submit_area" />
   <java-symbol type="id" name="switch_new" />
   <java-symbol type="id" name="switch_old" />
-  <java-symbol type="id" name="switchWidget" />
+  <java-symbol type="id" name="switch_widget" />
   <java-symbol type="id" name="text" />
   <java-symbol type="id" name="time" />
   <java-symbol type="id" name="time_current" />
@@ -401,7 +401,6 @@
   <java-symbol type="integer" name="db_connection_pool_size" />
   <java-symbol type="integer" name="db_journal_size_limit" />
   <java-symbol type="integer" name="db_wal_autocheckpoint" />
-  <java-symbol type="integer" name="max_action_buttons" />
   <java-symbol type="integer" name="config_soundEffectVolumeDb" />
   <java-symbol type="integer" name="config_lockSoundVolumeDb" />
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
@@ -564,6 +563,8 @@
   <java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" />
   <java-symbol type="string" name="capability_title_canRequestTouchExploration" />
   <java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
+  <java-symbol type="string" name="capability_desc_canControlMagnification" />
+  <java-symbol type="string" name="capability_title_canControlMagnification" />
   <java-symbol type="string" name="cfTemplateForwarded" />
   <java-symbol type="string" name="cfTemplateForwardedTime" />
   <java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -627,6 +628,7 @@
   <java-symbol type="string" name="widget_default_package_name" />
   <java-symbol type="string" name="widget_default_class_name" />
   <java-symbol type="string" name="emergency_calls_only" />
+  <java-symbol type="array" name="config_ephemeralResolverPackage" />
   <java-symbol type="string" name="enable_accessibility_canceled" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
@@ -1956,9 +1958,9 @@
   <java-symbol type="id" name="maximize_window" />
   <java-symbol type="id" name="close_window" />
   <java-symbol type="id" name="client_decor_placeholder" />
-  <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" />
+  <java-symbol type="layout" name="decor_caption_light" />
+  <java-symbol type="layout" name="decor_caption_dark" />
+  <java-symbol type="drawable" name="decor_caption_title_focused" />
 
   <!-- From TelephonyProvider -->
   <java-symbol type="xml" name="apns" />
@@ -2319,7 +2321,6 @@
 
   <java-symbol type="string" name="lockscreen_access_pattern_area" />
 
-  <java-symbol type="bool" name="allow_stacked_button_bar" />
   <java-symbol type="bool" name="config_eap_sim_based_auth_supported" />
 
   <java-symbol type="array" name="config_cell_retries_per_error_code" />
@@ -2335,4 +2336,5 @@
   <java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
 
   <java-symbol type="string" name="config_packagedKeyboardName" />
+  <java-symbol type="string" name="default_notification_topic_label" />
 </resources>
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
new file mode 100644
index 0000000..add3f40
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
new file mode 100644
index 0000000..7038f46
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
@@ -0,0 +1,365 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="BaseChar1"/>
+    <GlyphID id="2" name="BaseChar1_VS1"/>
+    <GlyphID id="3" name="BaseChar1_VS17"/>
+    <GlyphID id="4" name="BaseChar1_VS18"/>
+    <GlyphID id="5" name="BaseChar2"/>
+    <GlyphID id="6" name="BaseChar2_VS2"/>
+    <GlyphID id="7" name="BaseChar2_VS18"/>
+    <GlyphID id="8" name="BaseChar2_VS19"/>
+    <GlyphID id="9" name="BaseChar3"/>
+    <GlyphID id="10" name="BaseChar4_VS3"/>
+    <GlyphID id="11" name="BaseChar4_VS19"/>
+    <GlyphID id="12" name="BaseChar4_VS20"/>
+    <GlyphID id="13" name="BaseChar5"/>
+    <GlyphID id="14" name="BaseChar5_VS1"/>
+    <GlyphID id="15" name="BaseChar5_VS17"/>
+    <GlyphID id="16" name="BaseChar5_VS18"/>
+    <GlyphID id="17" name="BaseChar6"/>
+    <GlyphID id="18" name="BaseChar6_VS2"/>
+    <GlyphID id="19" name="BaseChar6_VS18"/>
+    <GlyphID id="20" name="BaseChar6_VS19"/>
+    <GlyphID id="21" name="BaseChar7"/>
+    <GlyphID id="22" name="BaseChar8_VS3"/>
+    <GlyphID id="23" name="BaseChar8_VS19"/>
+    <GlyphID id="24" name="BaseChar8_VS20"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Wed Sep  9 08:01:17 2015"/>
+    <modified value="Wed Sep  9 08:48:07 2015"/>
+    <xMin value="30"/>
+    <yMin value="-200"/>
+    <xMax value="629"/>
+    <yMax value="800"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="659"/>
+    <minLeftSideBearing value="0"/>
+    <minRightSideBearing value="30"/>
+    <xMaxExtent value="629"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+    <numberOfHMetrics value="18"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="54"/>
+    <maxPoints value="73"/>
+    <maxContours value="10"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="2"/>
+    <maxTwilightPoints value="12"/>
+    <maxStorage value="28"/>
+    <maxFunctionDefs value="119"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="61"/>
+    <maxSizeOfInstructions value="2967"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="BaseChar1" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS1" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS17" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar2" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS2" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar3" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS3" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS20" width="500" lsb="93"/>
+    <mtx name="BaseChar5" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS1" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS17" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar6" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS2" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar7" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS3" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS20" width="500" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="BaseChar1" />
+      <map code="0x0062" name="BaseChar2" />
+      <map code="0x0063" name="BaseChar3" />
+      <!-- No cmap4 entry for BaseChar4 -->
+      <map code="0x1F000" name="BaseChar5" />
+      <map code="0x1F001" name="BaseChar6" />
+      <map code="0x1F002" name="BaseChar7" />
+      <!-- No cmap4 entry for BaseChar8 -->
+    </cmap_format_12>
+    <cmap_format_14 format="14" platformID="0" platEncID="5" length="24" numVarSelectorRecords="3">
+      <map uvs="0xFE00" uv="0x0061" name="BaseChar1_VS1" />
+      <map uvs="0xE0100" uv="0x0061" name="BaseChar1_VS17" />
+      <map uvs="0xE0101" uv="0x0061" name="BaseChar1_VS18" />
+      <map uvs="0xE0102" uv="0x0061" name="None" />
+
+      <map uvs="0xFE01" uv="0x0062" name="BaseChar2_VS2" />
+      <map uvs="0xE0101" uv="0x0062" name="BaseChar2_VS18" />
+      <map uvs="0xE0102" uv="0x0062" name="BaseChar2_VS19" />
+      <map uvs="0xE0103" uv="0x0062" name="None" />
+
+      <map uvs="0xFE02" uv="0x0064" name="BaseChar4_VS3" />
+      <map uvs="0xE0102" uv="0x0064" name="BaseChar4_VS19" />
+      <map uvs="0xE0103" uv="0x0064" name="BaseChar4_VS20" />
+      <!-- There is no default glyph for U+0064 U+E0104 but there is a entry for
+           default UVS entry.  hasGlyph should return false in this
+           case.  -->
+      <map uvs="0xE0104" uv="0x0064" name="None" />
+
+      <map uvs="0xFE00" uv="0x1F000" name="BaseChar5_VS1" />
+      <map uvs="0xE0100" uv="0x1F000" name="BaseChar5_VS17" />
+      <map uvs="0xE0101" uv="0x1F000" name="BaseChar5_VS18" />
+      <map uvs="0xE0102" uv="0x1F000" name="None" />
+
+      <map uvs="0xFE01" uv="0x1F001" name="BaseChar6_VS2" />
+      <map uvs="0xE0101" uv="0x1F001" name="BaseChar6_VS18" />
+      <map uvs="0xE0102" uv="0x1F001" name="BaseChar6_VS19" />
+      <map uvs="0xE0103" uv="0x1F001" name="None" />
+
+      <map uvs="0xFE02" uv="0x1F003" name="BaseChar8_VS3" />
+      <map uvs="0xE0102" uv="0x1F003" name="BaseChar8_VS19" />
+      <map uvs="0xE0103" uv="0x1F003" name="BaseChar8_VS20" />
+      <!-- There is no default glyph for U+1F003 U+E0104 but there is a entry for
+           default UVS entry.  hasGlyph should return false in this
+           case.  -->
+      <map uvs="0xE0104" uv="0x1F003" name="None" />
+    </cmap_format_14>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+
+    <TTGlyph name="BaseChar1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar7" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      hasGlyphTestFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      hasGlyphTestFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      hasGlyphTestFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index 7164747..998c72a 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -811,6 +811,65 @@
     }
 
     @SmallTest
+    public void testEndBeforeStart() throws Throwable {
+        // This test calls two animators that are not yet started. One animator has completed a
+        // previous run but hasn't started since then, the other one has never run. When end() is
+        // called on these two animators, we expected their animation listeners to receive both
+        // onAnimationStarted(Animator) and onAnimationEnded(Animator) callbacks, in that sequence.
+
+        a1.setStartDelay(20);
+
+        // First start a1's first run.
+        final MyListener normalEndingListener = new MyListener();
+        a1.addListener(normalEndingListener);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(a1.isStarted());
+                assertFalse(normalEndingListener.startCalled);
+                assertFalse(normalEndingListener.endCalled);
+                // Start normally
+                a1.start();
+            }
+        });
+
+        Thread.sleep(a1.getTotalDuration() + POLL_INTERVAL);
+
+        // a1 should have finished by now.
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Call end() on both a1 and a2 without calling start()
+                final MyListener l1 = new MyListener();
+                a1.addListener(l1);
+                final MyListener l2 = new MyListener();
+                a2.addListener(l2);
+
+                assertFalse(a1.isStarted());
+                assertFalse(l1.startCalled);
+                assertFalse(l1.endCalled);
+                assertFalse(a2.isStarted());
+                assertFalse(l2.startCalled);
+                assertFalse(l1.endCalled);
+
+                a1.end();
+                a2.end();
+
+                // Check that both animators' listeners have received the animation callbacks.
+                assertTrue(l1.startCalled);
+                assertTrue(l1.endCalled);
+                assertFalse(a1.isStarted());
+                assertTrue(l1.endTime >= l1.startTime);
+
+                assertTrue(l2.startCalled);
+                assertTrue(l2.endCalled);
+                assertFalse(a2.isStarted());
+                assertTrue(l2.endTime >= l1.startTime);
+            }
+        });
+    }
+
+    @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.
diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
index 4c9b00a..dcf2c89 100644
--- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
@@ -33,7 +33,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mAppsQueryHelper = new AppsQueryHelperTestable(getContext());
+        mAppsQueryHelper = new AppsQueryHelperTestable();
     }
 
     public void testQueryAppsSystemAppsOnly() {
@@ -78,33 +78,20 @@
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps);
     }
 
-    public void testQueryAppsDefaultIme() {
-        // Test query default system IMEs
-        List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES,
+    public void testQueryAppsImes() {
+        // Test query system IMEs
+        List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES,
                 true, UserHandle.of(UserHandle.myUserId()));
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps);
 
-        // Test query default IMEs
-        apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+        // Test query IMEs
+        apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_IMES, false,
                 UserHandle.of(UserHandle.myUserId()));
         assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps);
-
-        // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process
-        try {
-            mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
-                    UserHandle.of(UserHandle.USER_NULL));
-            fail("queryApps must fail if wrong user was passed");
-        } catch (IllegalArgumentException e) {
-            // OK
-        }
     }
 
     private class AppsQueryHelperTestable extends AppsQueryHelper {
 
-        public AppsQueryHelperTestable(Context context) {
-            super(context);
-        }
-
         @Override
         protected List<ApplicationInfo> getAllApps(int userId) {
             final ApplicationInfo ai1 = new ApplicationInfo();
@@ -145,18 +132,19 @@
         }
 
         @Override
-        protected List<InputMethodInfo> getInputMethodList() {
+        protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int userId) {
             final ResolveInfo sysApp1 = new ResolveInfo();
             sysApp1.serviceInfo = new ServiceInfo();
             sysApp1.serviceInfo.packageName = "sys_app1";
             sysApp1.serviceInfo.name = "name";
-            InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true);
+            sysApp1.serviceInfo.applicationInfo = new ApplicationInfo();
+            sysApp1.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
             final ResolveInfo app4 = new ResolveInfo();
             app4.serviceInfo = new ServiceInfo();
             app4.serviceInfo.packageName = "app4";
             app4.serviceInfo.name = "name";
-            InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true);
-            return Arrays.asList(imi1, imi2);
+            app4.serviceInfo.applicationInfo = new ApplicationInfo();
+            return Arrays.asList(sysApp1, app4);
         }
     }
 
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index e97bb33..2a3d463 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -20,6 +20,9 @@
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Arrays;
+import java.util.HashSet;
+
 /**
  * PaintTest tests {@link Paint}.
  */
@@ -94,4 +97,63 @@
                     testCase.mWidthWithHinting, widths);
         }
     }
+
+    private static class HasGlyphTestCase {
+        public final int mBaseCodepoint;
+        public final HashSet<Integer> mVariationSelectors;
+
+        public HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors) {
+            mBaseCodepoint = baseCodepoint;
+            mVariationSelectors = new HashSet<>(Arrays.asList(variationSelectors));
+        }
+    }
+
+    private static String codePointsToString(int[] codepoints) {
+        StringBuilder sb = new StringBuilder();
+        for (int codepoint : codepoints) {
+            sb.append(Character.toChars(codepoint));
+        }
+        return sb.toString();
+    }
+
+    public void testHasGlyph_variationSelectors() {
+        final Typeface fontTypeface = Typeface.createFromAsset(
+                getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf");
+        Paint p = new Paint();
+        p.setTypeface(fontTypeface);
+
+        // Usually latin letters U+0061..U+0064 and Mahjong Tiles U+1F000..U+1F003 don't have
+        // variation selectors.  This test may fail if system pre-installed fonts have a variation
+        // selector support for U+0061..U+0064 and U+1F000..U+1F003.
+        HasGlyphTestCase[] HAS_GLYPH_TEST_CASES = {
+            new HasGlyphTestCase(0x0061, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+            new HasGlyphTestCase(0x0062, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+            new HasGlyphTestCase(0x0063, new Integer[] {}),
+            new HasGlyphTestCase(0x0064, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+
+            new HasGlyphTestCase(0x1F000, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+            new HasGlyphTestCase(0x1F001, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+            new HasGlyphTestCase(0x1F002, new Integer[] {}),
+            new HasGlyphTestCase(0x1F003, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+        };
+
+        for (HasGlyphTestCase testCase : HAS_GLYPH_TEST_CASES) {
+            for (int vs = 0xFE00; vs <= 0xE01EF; ++vs) {
+                // Move to variation selector supplements after variation selectors.
+                if (vs == 0xFF00) {
+                    vs = 0xE0100;
+                }
+                final String signature =
+                        "hasGlyph(U+" + Integer.toHexString(testCase.mBaseCodepoint) +
+                        " U+" + Integer.toHexString(vs) + ")";
+                final String testString =
+                        codePointsToString(new int[] {testCase.mBaseCodepoint, vs});
+                if (testCase.mVariationSelectors.contains(vs)) {
+                    assertTrue(signature + " is expected to be true", p.hasGlyph(testString));
+                } else {
+                    assertFalse(signature + " is expected to be false", p.hasGlyph(testString));
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index 87b3785..3ce45e9 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -61,11 +61,13 @@
         final int expectedHighlightTextColor = tmpTp.getColor();
         final float expectedHighlightTextSize = tmpTp.getTextSize();
 
-        // Create and wait until SuggestionsPopupWindow is shown.
         final EditText editText = (EditText) activity.findViewById(R.id.textview);
         final Editor editor = editText.getEditorForTesting();
         assertNotNull(editor);
-        activity.runOnUiThread(new Runnable() {
+
+        // Request to show SuggestionsPopupWindow.
+        Runnable showSuggestionWindowRunner = new Runnable() {
+            @Override
             public void run() {
                 SpannableStringBuilder ssb = new SpannableStringBuilder();
                 ssb.append(sampleText);
@@ -78,8 +80,7 @@
                 Selection.setSelection(editText.getText(), singleWordSpanStart, singleWordSpanEnd);
                 editText.onTextContextMenuItem(TextView.ID_REPLACE);
             }
-        });
-        getInstrumentation().waitForIdleSync();
+        };
 
         // In this test, the SuggestionsPopupWindow looks like
         //   abc def ghi
@@ -91,7 +92,8 @@
         // | DELETE        |
         // -----------------
         // *XX* means that XX is highlighted.
-        activity.runOnUiThread(new Runnable() {
+        Runnable popupVaridator = new Runnable() {
+            @Override
             public void run() {
                 Editor.SuggestionsPopupWindow popupWindow =
                         editor.getSuggestionsPopupWindowForTesting();
@@ -155,6 +157,25 @@
                     assertEquals(multiWordSpanEnd, spanned.getSpanEnd(taSpan[0]));
                 }
             }
+        };
+
+        // Show the SuggestionWindow and verify the contents.
+        activity.runOnUiThread(showSuggestionWindowRunner);
+        getInstrumentation().waitForIdleSync();
+        activity.runOnUiThread(popupVaridator);
+
+        // Request to hide the SuggestionPopupWindow and wait until it is hidden.
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                editText.setText("");
+            }
         });
+        getInstrumentation().waitForIdleSync();
+
+        // Show and verify the contents again.
+        activity.runOnUiThread(showSuggestionWindowRunner);
+        getInstrumentation().waitForIdleSync();
+        activity.runOnUiThread(popupVaridator);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
new file mode 100644
index 0000000..951e87a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.util;
+
+import junit.framework.TestCase;
+
+public final class HexDumpTest extends TestCase {
+    public void testBytesToHexString() {
+        assertEquals("abcdef", HexDump.toHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false));
+        assertEquals("ABCDEF", HexDump.toHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true));
+    }
+}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 58bf654..4186007 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -81,7 +81,7 @@
 key 39    SEMICOLON
 key 40    APOSTROPHE
 key 14    DEL
-        
+
 key 44    Z
 key 45    X
 key 46    C
@@ -93,7 +93,7 @@
 key 52    PERIOD
 key 53    SLASH
 key 28    ENTER
-        
+
 key 56    ALT_LEFT
 key 100   ALT_RIGHT
 key 42    SHIFT_LEFT
@@ -101,7 +101,7 @@
 key 15    TAB
 key 57    SPACE
 key 150   EXPLORER
-key 155   ENVELOPE        
+key 155   ENVELOPE
 
 key 12    MINUS
 key 13    EQUALS
@@ -110,3 +110,16 @@
 # On an AT keyboard: ESC, F10
 key 1     BACK
 key 68    MENU
+
+# App switch = Overview key
+key 580   APP_SWITCH
+
+# Media control keys
+key 160   MEDIA_CLOSE
+key 161   MEDIA_EJECT
+key 163   MEDIA_NEXT
+key 164   MEDIA_PLAY_PAUSE
+key 165   MEDIA_PREVIOUS
+key 166   MEDIA_STOP
+key 167   MEDIA_RECORD
+key 168   MEDIA_REWIND
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index 6e22be8..b8c105d 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -512,7 +512,7 @@
 onStartCommand()} directly.)</p>
 
 <p>For example, an activity can start the example service in the previous section ({@code
-HelloSevice}) using an explicit intent with {@link android.content.Context#startService
+HelloService}) using an explicit intent with {@link android.content.Context#startService
 startService()}:</p>
 
 <pre>
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index c1ccef0..21e3057 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -562,10 +562,11 @@
     <tr>
        <td rowspan="6">Camera</td>
        <td><code>android.hardware.camera</code></td>
-       <td>The application uses the device's camera. If the device supports
-           multiple cameras, the application uses the camera that facing
-           away from the screen.</td>
-       <td></td>
+       <td>The application uses the device's back-facing (main) camera.</td>
+       <td>Devices with only a front-facing camera do not list this feature, so the
+           <code>android.hardware.camera.any</code> feature should be
+           used instead if a camera facing any direction is acceptable for the
+           application.</td>
     </tr>
 <tr>
   <td><code>android.hardware.camera.autofocus</code></td>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 39d13df..0ee877e 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -913,16 +913,26 @@
     protected void onBoundsChange(Rect bounds) {}
 
     /**
-     * Return the intrinsic width of the underlying drawable object.  Returns
-     * -1 if it has no intrinsic width, such as with a solid color.
+     * Returns the drawable's intrinsic width.
+     * <p>
+     * Intrinsic width is the width at which the drawable would like to be laid
+     * out, including any inherent padding. If the drawable has no intrinsic
+     * width, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic width, or -1 if no intrinsic width
      */
     public int getIntrinsicWidth() {
         return -1;
     }
 
     /**
-     * Return the intrinsic height of the underlying drawable object. Returns
-     * -1 if it has no intrinsic height, such as with a solid color.
+     * Returns the drawable's intrinsic height.
+     * <p>
+     * Intrinsic height is the height at which the drawable would like to be
+     * laid out, including any inherent padding. If the drawable has no
+     * intrinsic height, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic height, or -1 if no intrinsic height
      */
     public int getIntrinsicHeight() {
         return -1;
@@ -1230,30 +1240,54 @@
      */
     public static abstract class ConstantState {
         /**
-         * Create a new drawable without supplying resources the caller
-         * is running in.  Note that using this means the density-dependent
-         * drawables (like bitmaps) will not be able to update their target
-         * density correctly. One should use {@link #newDrawable(Resources)}
-         * instead to provide a resource.
+         * Creates a new Drawable instance from its constant state.
+         * <p>
+         * <strong>Note:</strong> Using this method means density-dependent
+         * properties, such as pixel dimensions or bitmap images, will not be
+         * updated to match the density of the target display. To ensure
+         * correct scaling, use {@link #newDrawable(Resources)} instead to
+         * provide an appropriate Resources object.
+         *
+         * @return a new drawable object based on this constant state
+         * @see {@link #newDrawable(Resources)}
          */
+        @NonNull
         public abstract Drawable newDrawable();
 
         /**
-         * Create a new Drawable instance from its constant state.  This
-         * must be implemented for drawables that change based on the target
-         * density of their caller (that is depending on whether it is
-         * in compatibility mode).
+         * Creates a new Drawable instance from its constant state using the
+         * specified resources. This method should be implemented for drawables
+         * that have density-dependent properties.
+         * <p>
+         * The default implementation for this method calls through to
+         * {@link #newDrawable()}.
+         *
+         * @param res the resources of the context in which the drawable will
+         *            be displayed
+         * @return a new drawable object based on this constant state
          */
-        public Drawable newDrawable(Resources res) {
+        @NonNull
+        public Drawable newDrawable(@Nullable Resources res) {
             return newDrawable();
         }
 
         /**
-         * Create a new Drawable instance from its constant state. This must be
-         * implemented for drawables that can have a theme applied.
+         * Creates a new Drawable instance from its constant state using the
+         * specified resources and theme. This method should be implemented for
+         * drawables that have theme-dependent properties.
+         * <p>
+         * The default implementation for this method calls through to
+         * {@link #newDrawable(Resources)}.
+         *
+         * @param res the resources of the context in which the drawable will
+         *            be displayed
+         * @param theme the theme of the context in which the drawable will be
+         *              displayed
+         * @return a new drawable object based on this constant state
          */
-        public Drawable newDrawable(Resources res, Theme theme) {
-            return newDrawable(null);
+        @NonNull
+        public Drawable newDrawable(@Nullable Resources res, @Nullable Theme theme) {
+            return newDrawable(res);
         }
 
         /**
@@ -1390,7 +1424,7 @@
         }
     }
 
-    static int resolveDensity(@NonNull Resources r, int parentDensity) {
+    static int resolveDensity(@Nullable Resources r, int parentDensity) {
         final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
         return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
     }
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 927b9c9..36d4272 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -222,12 +222,20 @@
 
     @Override
     public int getIntrinsicWidth() {
-        return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight;
+        final int childWidth = getDrawable().getIntrinsicWidth();
+        if (childWidth < 0) {
+            return -1;
+        }
+        return childWidth + mState.mInsetLeft + mState.mInsetRight;
     }
 
     @Override
     public int getIntrinsicHeight() {
-        return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom;
+        final int childHeight = getDrawable().getIntrinsicHeight();
+        if (childHeight < 0) {
+            return -1;
+        }
+        return childHeight + mState.mInsetTop + mState.mInsetBottom;
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
index 2d378c6..e83513c 100644
--- a/graphics/java/android/graphics/drawable/RippleComponent.java
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.util.DisplayMetrics;
 import android.view.DisplayListCanvas;
 import android.view.RenderNodeAnimator;
 
@@ -50,7 +51,7 @@
     protected float mTargetRadius;
 
     /** Screen density used to adjust pixel-based constants. */
-    protected float mDensity;
+    protected float mDensityScale;
 
     /**
      * If set, force all ripple animations to not run on RenderThread, even if it would be
@@ -71,7 +72,7 @@
         }
     }
 
-    public final void setup(float maxRadius, float density) {
+    public final void setup(float maxRadius, int densityDpi) {
         if (maxRadius >= 0) {
             mHasMaxRadius = true;
             mTargetRadius = maxRadius;
@@ -79,7 +80,7 @@
             mTargetRadius = getTargetRadius(mBounds);
         }
 
-        mDensity = density;
+        mDensityScale = densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
 
         onTargetRadiusChanged(mTargetRadius);
     }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 52e7f24..aaab529 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -160,7 +160,7 @@
     private Paint mRipplePaint;
 
     /** Target density of the display into which ripples are drawn. */
-    private float mDensity = 1.0f;
+    private int mDensity;
 
     /** Whether bounds are being overridden. */
     private boolean mOverrideBounds;
@@ -544,8 +544,7 @@
             mBackground = new RippleBackground(this, mHotspotBounds, mForceSoftware);
         }
 
-        final float densityScale = mState.mDensity * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        mBackground.setup(mState.mMaxRadius, densityScale);
+        mBackground.setup(mState.mMaxRadius, mDensity);
         mBackground.enter(focused);
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index c660846..829733e 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -168,7 +168,7 @@
         }
 
         final int duration = (int)
-                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
+                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
         tweenRadius.setAutoCancel(true);
@@ -204,7 +204,7 @@
     private int getRadiusExitDuration() {
         final float remainingRadius = mTargetRadius - getCurrentRadius();
         return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
-                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
+                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
     }
 
     private float getCurrentRadius() {
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index eee9b24..f630055e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -231,6 +231,9 @@
     private int mDpiScaledHeight = 0;
     private Insets mDpiScaledInsets = Insets.NONE;
 
+    /** Whether DPI-scaled width, height, and insets need to be updated. */
+    private boolean mDpiScaledDirty = true;
+
     // Temp variable, only for saving "new" operation at the draw() time.
     private final float[] mTmpFloats = new float[9];
     private final Matrix mTmpMatrix = new Matrix();
@@ -259,9 +262,13 @@
      *            displayed, or {@code null} to use the constant state defaults
      */
     private void updateLocalState(Resources res) {
-        mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity);
+        final int density = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mDensity);
+        if (mTargetDensity != density) {
+            mTargetDensity = density;
+            mDpiScaledDirty = true;
+        }
+
         mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
-        computeVectorSize();
     }
 
     @Override
@@ -422,17 +429,26 @@
 
     @Override
     public int getIntrinsicWidth() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledWidth;
     }
 
     @Override
     public int getIntrinsicHeight() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledHeight;
     }
 
     /** @hide */
     @Override
     public Insets getOpticalInsets() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledInsets;
     }
 
@@ -444,7 +460,7 @@
         final VPathRenderer pathRenderer = mVectorState.mVPathRenderer;
         final Insets opticalInsets = pathRenderer.mOpticalInsets;
 
-        final int sourceDensity = pathRenderer.mSourceDensity;
+        final int sourceDensity = pathRenderer.mDensity;
         final int targetDensity = mTargetDensity;
         if (targetDensity != sourceDensity) {
             mDpiScaledWidth = Drawable.scaleFromDensity(
@@ -465,6 +481,8 @@
             mDpiScaledHeight = (int) pathRenderer.mBaseHeight;
             mDpiScaledInsets = opticalInsets;
         }
+
+        mDpiScaledDirty = false;
     }
 
     @Override
@@ -481,6 +499,11 @@
             return;
         }
 
+        final VPathRenderer path = state.mVPathRenderer;
+        final boolean changedDensity = path.setDensity(
+                Drawable.resolveDensity(t.getResources(), 0));
+        mDpiScaledDirty |= changedDensity;
+
         if (state.mThemeAttrs != null) {
             final TypedArray a = t.resolveAttributes(
                     state.mThemeAttrs, R.styleable.VectorDrawable);
@@ -492,6 +515,9 @@
             } finally {
                 a.recycle();
             }
+
+            // May have changed size.
+            mDpiScaledDirty = true;
         }
 
         // Apply theme to contained color state list.
@@ -499,7 +525,6 @@
             state.mTint = state.mTint.obtainForTheme(t);
         }
 
-        final VPathRenderer path = state.mVPathRenderer;
         if (path != null && path.canApplyTheme()) {
             path.applyTheme(t);
         }
@@ -565,21 +590,24 @@
     }
 
     @Override
-    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final VectorDrawableState state = mVectorState;
-        final VPathRenderer pathRenderer = new VPathRenderer();
-        state.mVPathRenderer = pathRenderer;
+        state.mVPathRenderer = new VPathRenderer();
+        state.mVPathRenderer.setDensity(Drawable.resolveDensity(r, 0));
 
-        final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
         updateStateFromTypedArray(a);
         a.recycle();
 
+        mDpiScaledDirty = true;
+
         state.mCacheDirty = true;
-        inflateInternal(res, parser, attrs, theme);
+        inflateChildElements(r, parser, attrs, theme);
 
         // Update local properties.
-        updateLocalState(res);
+        updateLocalState(r);
     }
 
     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
@@ -592,13 +620,6 @@
         // 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 targetDensity = Drawable.resolveDensity(a.getResources(), 0);
-        final int sourceDensity = pathRenderer.mSourceDensity;
-        final float densityScale = targetDensity / (float) sourceDensity;
-        pathRenderer.mSourceDensity = targetDensity;
-
         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
         if (tintMode != -1) {
             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
@@ -626,11 +647,9 @@
         }
 
         pathRenderer.mBaseWidth = a.getDimension(
-                R.styleable.VectorDrawable_width,
-                pathRenderer.mBaseWidth * densityScale);
+                R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
         pathRenderer.mBaseHeight = a.getDimension(
-                R.styleable.VectorDrawable_height,
-                pathRenderer.mBaseHeight * densityScale);
+                R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
 
         if (pathRenderer.mBaseWidth <= 0) {
             throw new XmlPullParserException(a.getPositionDescription() +
@@ -641,21 +660,17 @@
         }
 
         final int insetLeft = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetLeft,
-                (int) (pathRenderer.mOpticalInsets.left * densityScale));
+                R.styleable.VectorDrawable_opticalInsetLeft, pathRenderer.mOpticalInsets.left);
         final int insetTop = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetTop,
-                (int) (pathRenderer.mOpticalInsets.top * densityScale));
+                R.styleable.VectorDrawable_opticalInsetTop, pathRenderer.mOpticalInsets.top);
         final int insetRight = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetRight,
-                (int) (pathRenderer.mOpticalInsets.right * densityScale));
+                R.styleable.VectorDrawable_opticalInsetRight, pathRenderer.mOpticalInsets.right);
         final int insetBottom = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetBottom,
-                (int) (pathRenderer.mOpticalInsets.bottom * densityScale));
+                R.styleable.VectorDrawable_opticalInsetBottom, pathRenderer.mOpticalInsets.bottom);
         pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
 
-        final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
-                pathRenderer.getAlpha());
+        final float alphaInFloat = a.getFloat(
+                R.styleable.VectorDrawable_alpha, pathRenderer.getAlpha());
         pathRenderer.setAlpha(alphaInFloat);
 
         final String name = a.getString(R.styleable.VectorDrawable_name);
@@ -665,7 +680,7 @@
         }
     }
 
-    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
+    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
             Theme theme) throws XmlPullParserException, IOException {
         final VectorDrawableState state = mVectorState;
         final VPathRenderer pathRenderer = state.mVPathRenderer;
@@ -929,7 +944,7 @@
         int mRootAlpha = 0xFF;
         String mRootName = null;
 
-        int mSourceDensity = DisplayMetrics.DENSITY_DEFAULT;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
 
         final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
 
@@ -966,12 +981,37 @@
             mChangingConfigurations = copy.mChangingConfigurations;
             mRootAlpha = copy.mRootAlpha;
             mRootName = copy.mRootName;
-            mSourceDensity = copy.mSourceDensity;
+            mDensity = copy.mDensity;
             if (copy.mRootName != null) {
                 mVGTargetsMap.put(copy.mRootName, this);
             }
         }
 
+        public final boolean setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+                applyDensityScaling(sourceDensity, targetDensity);
+                return true;
+            }
+            return false;
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
+            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
+
+            final int insetLeft = Drawable.scaleFromDensity(
+                    mOpticalInsets.left, sourceDensity, targetDensity, false);
+            final int insetTop = Drawable.scaleFromDensity(
+                    mOpticalInsets.top, sourceDensity, targetDensity, false);
+            final int insetRight = Drawable.scaleFromDensity(
+                    mOpticalInsets.right, sourceDensity, targetDensity, false);
+            final int insetBottom = Drawable.scaleFromDensity(
+                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
+            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+        }
+
         public boolean canApplyTheme() {
             return mRootGroup.canApplyTheme();
         }
@@ -1321,7 +1361,7 @@
      * Common Path information for clip path and normal path.
      */
     private static abstract class VPath implements VObject {
-        protected PathParser.PathDataNode[] mNodes = null;
+        protected PathParser.PathData mPathData = null;
         String mPathName;
         int mChangingConfigurations;
 
@@ -1332,7 +1372,7 @@
         public VPath(VPath copy) {
             mPathName = copy.mPathName;
             mChangingConfigurations = copy.mChangingConfigurations;
-            mNodes = PathParser.deepCopyNodes(copy.mNodes);
+            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
         }
 
         public String getPathName() {
@@ -1345,18 +1385,14 @@
 
         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
         @SuppressWarnings("unused")
-        public PathParser.PathDataNode[] getPathData() {
-            return mNodes;
+        public PathParser.PathData getPathData() {
+            return mPathData;
         }
 
+        // TODO: Move the PathEvaluator and this setter and the getter above into native.
         @SuppressWarnings("unused")
-        public void setPathData(PathParser.PathDataNode[] nodes) {
-            if (!PathParser.canMorph(mNodes, nodes)) {
-                // This should not happen in the middle of animation.
-                mNodes = PathParser.deepCopyNodes(nodes);
-            } else {
-                PathParser.updateNodes(mNodes, nodes);
-            }
+        public void setPathData(PathParser.PathData pathData) {
+            mPathData.setPathData(pathData);
         }
 
         @Override
@@ -1392,8 +1428,8 @@
          * @param outPath the output path
          */
         protected void toPath(TempState temp, Path outPath) {
-            if (mNodes != null) {
-                PathParser.PathDataNode.nodesToPath(mNodes, outPath);
+            if (mPathData != null) {
+                PathParser.createPathFromPathData(outPath, mPathData);
             }
         }
 
@@ -1488,9 +1524,9 @@
                 mPathName = pathName;
             }
 
-            final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData);
-            if (pathData != null) {
-                mNodes = PathParser.createNodesFromPathData(pathData);
+            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
+            if (pathDataString != null) {
+                mPathData = new PathParser.PathData(pathDataString);
             }
         }
 
@@ -1719,9 +1755,9 @@
                 mPathName = pathName;
             }
 
-            final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData);
-            if (pathData != null) {
-                mNodes = PathParser.createNodesFromPathData(pathData);
+            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
+            if (pathString != null) {
+                mPathData = new PathParser.PathData(pathString);
             }
 
             final ColorStateList fillColors = a.getColorStateList(
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index c31a8b7..8c20ddc 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -101,20 +101,21 @@
      */
     public static void install() {
         Provider[] providers = Security.getProviders();
-        int bcProviderPosition = -1;
-        for (int position = 0; position < providers.length; position++) {
-            Provider provider = providers[position];
+        int bcProviderIndex = -1;
+        for (int i = 0; i < providers.length; i++) {
+            Provider provider = providers[i];
             if ("BC".equals(provider.getName())) {
-                bcProviderPosition = position;
+                bcProviderIndex = i;
                 break;
             }
         }
 
         Security.addProvider(new AndroidKeyStoreProvider());
         Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
-        if (bcProviderPosition != -1) {
+        if (bcProviderIndex != -1) {
             // Bouncy Castle provider found -- install the workaround provider above it.
-            Security.insertProviderAt(workaroundProvider, bcProviderPosition);
+            // insertProviderAt uses 1-based positions.
+            Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
         } else {
             // Bouncy Castle provider not found -- install the workaround provider at lowest
             // priority.
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 9d3d4ae..cc68fb2 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
     font/Font.cpp \
     renderstate/Blend.cpp \
     renderstate/MeshState.cpp \
+    renderstate/OffscreenBufferPool.cpp \
     renderstate/PixelBufferState.cpp \
     renderstate/RenderState.cpp \
     renderstate/Scissor.cpp \
@@ -28,12 +29,15 @@
     utils/NinePatchImpl.cpp \
     utils/StringUtils.cpp \
     utils/TestWindowContext.cpp \
+    utils/VectorDrawableUtils.cpp \
+    utils/TestUtils.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
     AnimatorManager.cpp \
     AssetAtlas.cpp \
     Caches.cpp \
+    Canvas.cpp \
     CanvasState.cpp \
     ClipArea.cpp \
     DamageAccumulator.cpp \
@@ -64,6 +68,7 @@
     PatchCache.cpp \
     PathCache.cpp \
     PathTessellator.cpp \
+    PathParser.cpp \
     PixelBuffer.cpp \
     Program.cpp \
     ProgramCache.cpp \
@@ -82,6 +87,7 @@
     TextDropShadowCache.cpp \
     Texture.cpp \
     TextureCache.cpp \
+    VectorDrawablePath.cpp \
     protos/hwui.proto
 
 hwui_cflags := \
@@ -203,7 +209,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
 LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+        $(hwui_cflags) \
+        -DHWUI_NULL_GPU
 
 LOCAL_SRC_FILES += \
     unit_tests/CanvasStateTests.cpp \
@@ -213,6 +221,8 @@
     unit_tests/FatVectorTests.cpp \
     unit_tests/LayerUpdateQueueTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
+    unit_tests/VectorDrawableTests.cpp \
+    unit_tests/OffscreenBufferPoolTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
@@ -245,9 +255,11 @@
 
 LOCAL_SRC_FILES += \
     tests/TestContext.cpp \
-    tests/TreeContentAnimation.cpp \
+    tests/TestSceneRunner.cpp \
     tests/main.cpp
 
+LOCAL_SRC_FILES += $(call all-cpp-files-under, tests/scenes)
+
 include $(BUILD_EXECUTABLE)
 
 # ------------------------
@@ -272,6 +284,7 @@
 LOCAL_SRC_FILES += \
     microbench/DisplayListCanvasBench.cpp \
     microbench/LinearAllocatorBench.cpp \
+    microbench/PathParserBench.cpp \
     microbench/ShadowBench.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 2fca5ea..d13d7ef 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -19,104 +19,32 @@
 #include "Caches.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
 #include "utils/GLUtils.h"
+#include "VertexBuffer.h"
+
+#include <algorithm>
+#include <math.h>
 
 namespace android {
 namespace uirenderer {
 
 ////////////////////////////////////////////////////////////////////////////////
-// OffscreenBuffer
-////////////////////////////////////////////////////////////////////////////////
-
-OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
-        uint32_t textureWidth, uint32_t textureHeight,
-        uint32_t viewportWidth, uint32_t viewportHeight)
-        : renderState(renderState)
-        , viewportWidth(viewportWidth)
-        , viewportHeight(viewportHeight)
-        , texture(caches) {
-    texture.width = textureWidth;
-    texture.height = textureHeight;
-
-    caches.textureState().activateTexture(0);
-    glGenTextures(1, &texture.id);
-    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
-    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
-    // not setting filter on texture, since it's set when drawing, based on transform
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
-}
-
-void OffscreenBuffer::updateMeshFromRegion() {
-    // avoid T-junctions as they cause artifacts in between the resultant
-    // geometry when complex transforms occur.
-    // TODO: generate the safeRegion only if necessary based on drawing transform
-    Region safeRegion = Region::createTJunctionFreeRegion(region);
-
-    size_t count;
-    const android::Rect* rects = safeRegion.getArray(&count);
-
-    const float texX = 1.0f / float(viewportWidth);
-    const float texY = 1.0f / float(viewportHeight);
-
-    FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
-    TextureVertex* mesh = &meshVector[0];
-    for (size_t i = 0; i < count; i++) {
-        const android::Rect* r = &rects[i];
-
-        const float u1 = r->left * texX;
-        const float v1 = (viewportHeight - r->top) * texY;
-        const float u2 = r->right * texX;
-        const float v2 = (viewportHeight - r->bottom) * texY;
-
-        TextureVertex::set(mesh++, r->left, r->top, u1, v1);
-        TextureVertex::set(mesh++, r->right, r->top, u2, v1);
-        TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
-        TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
-    }
-    elementCount = count * 6;
-    renderState.meshState().genOrUpdateMeshBuffer(&vbo,
-            sizeof(TextureVertex) * count * 4,
-            &meshVector[0],
-            GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
-}
-
-OffscreenBuffer::~OffscreenBuffer() {
-    texture.deleteTexture();
-    renderState.meshState().deleteMeshBuffer(vbo);
-    elementCount = 0;
-    vbo = 0;
-}
-
-////////////////////////////////////////////////////////////////////////////////
 // BakedOpRenderer
 ////////////////////////////////////////////////////////////////////////////////
 
-OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
-        uint32_t width, uint32_t height) {
-    // TODO: get from cache!
-    return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
-}
-
-void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
-    // TODO: return texture/offscreenbuffer to cache!
-    delete offscreenBuffer;
-}
-
-OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
+OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
-    OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
-    startLayer(buffer);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
 
-void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
+    LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
     mRenderTarget.offscreenBuffer = offscreenBuffer;
 
     // create and bind framebuffer
@@ -130,12 +58,10 @@
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
 
-    // Clear the FBO
-    mRenderState.scissor().setEnabled(false);
-    glClear(GL_COLOR_BUFFER_BIT);
-
     // Change the viewport & ortho projection
     setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+    clearColorBuffer(repaintRect);
 }
 
 void BakedOpRenderer::endLayer() {
@@ -149,16 +75,13 @@
     mRenderTarget.frameBufferId = -1;
 }
 
-void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
     mRenderState.bindFramebuffer(0);
     setViewport(width, height);
     mCaches.clearGarbage();
 
     if (!mOpaque) {
-        // TODO: partial invalidate!
-        mRenderState.scissor().setEnabled(false);
-        glClear(GL_COLOR_BUFFER_BIT);
-        mHasDrawn = true;
+        clearColorBuffer(repaintRect);
     }
 }
 
@@ -188,6 +111,20 @@
     mRenderState.blend().syncEnabled();
 }
 
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+    if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) {
+        // Full viewport is being cleared - disable scissor
+        mRenderState.scissor().setEnabled(false);
+    } else {
+        // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+        mRenderState.scissor().setEnabled(true);
+        mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+                rect.getWidth(), rect.getHeight());
+    }
+    glClear(GL_COLOR_BUFFER_BIT);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
 Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
     Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
     if (!texture) {
@@ -211,7 +148,7 @@
         mRenderTarget.offscreenBuffer->region.orSelf(dirty);
     }
     mRenderState.render(glop, mRenderTarget.orthoMatrix);
-    mHasDrawn = true;
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -249,6 +186,10 @@
     renderer.renderGlop(state, glop);
 }
 
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("todo");
+}
+
 void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -261,6 +202,69 @@
     renderer.renderGlop(state, glop);
 }
 
+namespace VertexBufferRenderFlags {
+    enum {
+        Offset = 0x1,
+        ShadowInterp = 0x2,
+    };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+        const VertexBuffer& vertexBuffer, float translateX, float translateY,
+        SkPaint& paint, int vertexBufferRenderFlags) {
+    if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+        bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+        const int transformFlags = TransformFlags::OffsetByFudgeFactor;
+        Glop glop;
+        GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+                .setRoundRectClipState(state.roundRectClipState)
+                .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+                .setFillPaint(paint, state.alpha)
+                .setTransform(state.computedState.transform, transformFlags)
+                .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+                .build();
+        renderer.renderGlop(state, glop);
+    }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+        const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+    SkPaint paint;
+    paint.setAntiAlias(true); // want to use AlphaVertex
+
+    // The caller has made sure casterAlpha > 0.
+    uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
+    if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+        ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+    }
+    if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+        paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+        renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+                paint, VertexBufferRenderFlags::ShadowInterp);
+    }
+
+    uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
+    if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+        spotShadowAlpha = Properties::overrideSpotShadowStrength;
+    }
+    if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+        paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+        renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+                paint, VertexBufferRenderFlags::ShadowInterp);
+    }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+    TessellationCache::vertexBuffer_pair_t buffers;
+    renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
+            op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
+            &op.shadowMatrixXY, &op.shadowMatrixZ,
+            op.lightCenter, renderer.getLightInfo().lightRadius,
+            buffers);
+
+    renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
 void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -273,6 +277,91 @@
     renderer.renderGlop(state, glop);
 }
 
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+        const TextOp& op, const BakedOpState& state) {
+    renderer.caches().textureState().activateTexture(0);
+
+    PaintUtils::TextShadow textShadow;
+    if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+        LOG_ALWAYS_FATAL("failed to query shadow attributes");
+    }
+
+    renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+    ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+            op.paint, (const char*) op.glyphs,
+            op.glyphCount, textShadow.radius, op.positions);
+    // If the drop shadow exceeds the max texture size or couldn't be
+    // allocated, skip drawing
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    const float sx = op.x - texture->left + textShadow.dx;
+    const float sy = op.y - texture->top + textShadow.dy;
+
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUnitQuad(nullptr)
+            .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+    FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+    if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        renderTextShadow(renderer, fontRenderer, op, state);
+    }
+
+    float x = op.x;
+    float y = op.y;
+    const Matrix4& transform = state.computedState.transform;
+    const bool pureTranslate = transform.isPureTranslate();
+    if (CC_LIKELY(pureTranslate)) {
+        x = floorf(x + transform.getTranslateX() + 0.5f);
+        y = floorf(y + transform.getTranslateY() + 0.5f);
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        fontRenderer.setTextureFiltering(false);
+    } else if (CC_UNLIKELY(transform.isPerspective())) {
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        fontRenderer.setTextureFiltering(true);
+    } else {
+        // We only pass a partial transform to the font renderer. That partial
+        // matrix defines how glyphs are rasterized. Typically we want glyphs
+        // to be rasterized at their final size on screen, which means the partial
+        // matrix needs to take the scale factor into account.
+        // When a partial matrix is used to transform glyphs during rasterization,
+        // the mesh is generated with the inverse transform (in the case of scale,
+        // the mesh is generated at 1.0 / scale for instance.) This allows us to
+        // apply the full transform matrix at draw time in the vertex shader.
+        // Applying the full matrix in the shader is the easiest way to handle
+        // rotation and perspective and allows us to always generated quads in the
+        // font renderer which greatly simplifies the code, clipping in particular.
+        float sx, sy;
+        transform.decomposeScale(sx, sy);
+        fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+                roundf(std::max(1.0f, sx)),
+                roundf(std::max(1.0f, sy))));
+        fontRenderer.setTextureFiltering(true);
+    }
+
+    // TODO: Implement better clipping for scaled/rotated text
+    const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+    Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+    int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+    SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+    TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
+
+    bool hasActiveLayer = false; // TODO
+    fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+            op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
 void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
     OffscreenBuffer* buffer = *op.layerHandle;
 
@@ -291,7 +380,7 @@
     renderer.renderGlop(state, glop);
 
     if (op.destroy) {
-        BakedOpRenderer::destroyOffscreenBuffer(buffer);
+        renderer.renderState().layerPool().putOrDelete(buffer);
     }
 }
 
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index aa1e67d..29f9a6f 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -29,33 +29,6 @@
 class RenderState;
 
 /**
- * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
- * encompasses enough information to draw it back on screen (minus paint properties, which are held
- * by LayerOp).
- */
-class OffscreenBuffer {
-public:
-    OffscreenBuffer(RenderState& renderState, Caches& caches,
-            uint32_t textureWidth, uint32_t textureHeight,
-            uint32_t viewportWidth, uint32_t viewportHeight);
-    ~OffscreenBuffer();
-
-    // must be called prior to rendering, to construct/update vertex buffer
-    void updateMeshFromRegion();
-
-    RenderState& renderState;
-    uint32_t viewportWidth;
-    uint32_t viewportHeight;
-    Texture texture;
-
-    // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
-    // drawing back to screen / parent FBO.
-    Region region;
-    GLsizei elementCount = 0;
-    GLuint vbo = 0;
-};
-
-/**
  * Main rendering manager for a collection of work - one frame + any contained FBOs.
  *
  * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
@@ -66,31 +39,39 @@
  */
 class BakedOpRenderer {
 public:
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+    /**
+     * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+     */
+    struct LightInfo {
+        float lightRadius = 0;
+        uint8_t ambientShadowAlpha = 0;
+        uint8_t spotShadowAlpha = 0;
+    };
+
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
             : mRenderState(renderState)
             , mCaches(caches)
-            , mOpaque(opaque) {
+            , mOpaque(opaque)
+            , mLightInfo(lightInfo) {
     }
 
-    static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
-            uint32_t width, uint32_t height);
-    static void destroyOffscreenBuffer(OffscreenBuffer*);
-
     RenderState& renderState() { return mRenderState; }
     Caches& caches() { return mCaches; }
 
-    void startFrame(uint32_t width, uint32_t height);
+    void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
     void endFrame();
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
-    void startLayer(OffscreenBuffer* offscreenBuffer);
+    OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
+    void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
+    const LightInfo& getLightInfo() { return mLightInfo; }
 
     void renderGlop(const BakedOpState& state, const Glop& glop);
     bool didDraw() { return mHasDrawn; }
 private:
     void setViewport(uint32_t width, uint32_t height);
+    void clearColorBuffer(const Rect& clearRect);
 
     RenderState& mRenderState;
     Caches& mCaches;
@@ -106,6 +87,8 @@
         uint32_t viewportHeight = 0;
         Matrix4 orthoMatrix;
     } mRenderTarget;
+
+    const LightInfo mLightInfo;
 };
 
 /**
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index ddb8c84..9a40c3b 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -89,6 +89,21 @@
          *         and early return null in one place.
          */
     }
+
+    /**
+     * Constructor for unbounded ops without transform/clip (namely shadows)
+     *
+     * Since the op doesn't have known bounds, we conservatively set the mapped bounds
+     * to the current clipRect, and clipSideFlags to Full.
+     */
+    ResolvedRenderState(const Snapshot& snapshot) {
+        transform = *snapshot.transform;
+        clipRect = snapshot.getRenderTargetClip();
+        clippedBounds = clipRect;
+        transform.mapRect(clippedBounds);
+        clipSideFlags = OpClipSideFlags::Full;
+    }
+
     Matrix4 transform;
     Rect clipRect;
     int clipSideFlags = 0;
@@ -104,8 +119,7 @@
 public:
     static BakedOpState* tryConstruct(LinearAllocator& allocator,
             const Snapshot& snapshot, const RecordedOp& recordedOp) {
-        BakedOpState* bakedOp = new (allocator) BakedOpState(
-                snapshot, recordedOp);
+        BakedOpState* bakedOp = new (allocator) BakedOpState(snapshot, recordedOp);
         if (bakedOp->computedState.clippedBounds.isEmpty()) {
             // bounds are empty, so op is rejected
             allocator.rewindIfLastAlloc(bakedOp);
@@ -114,6 +128,14 @@
         return bakedOp;
     }
 
+    static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
+            const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+        if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
+
+        // clip isn't empty, so construct the op
+        return new (allocator) BakedOpState(snapshot, shadowOpPtr);
+    }
+
     static void* operator new(size_t size, LinearAllocator& allocator) {
         return allocator.alloc(size);
     }
@@ -134,6 +156,13 @@
             , roundRectClipState(snapshot.roundRectClipState)
             , projectionPathMask(snapshot.projectionPathMask)
             , op(&recordedOp) {}
+
+    BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
+            : computedState(snapshot)
+            , alpha(snapshot.alpha)
+            , roundRectClipState(snapshot.roundRectClipState)
+            , projectionPathMask(snapshot.projectionPathMask)
+            , op(shadowOpPtr) {}
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp
new file mode 100644
index 0000000..bc88c81
--- /dev/null
+++ b/libs/hwui/Canvas.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 "Canvas.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+    uint32_t flags;
+    SkDrawFilter* drawFilter = getDrawFilter();
+    if (drawFilter) {
+        SkPaint paintCopy(paint);
+        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+        flags = paintCopy.getFlags();
+    } else {
+        flags = paint.getFlags();
+    }
+    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+        // Same values used by Skia
+        const float kStdStrikeThru_Offset   = (-6.0f / 21.0f);
+        const float kStdUnderline_Offset    = (1.0f / 9.0f);
+        const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+        SkScalar left = x;
+        SkScalar right = x + length;
+        float textSize = paint.getTextSize();
+        float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+        if (flags & SkPaint::kUnderlineText_Flag) {
+            SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+            SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+            drawRect(left, top, right, bottom, paint);
+        }
+        if (flags & SkPaint::kStrikeThruText_Flag) {
+            SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+            SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+            drawRect(left, top, right, bottom, paint);
+        }
+    }
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 4bd4ac8..b585a27 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -149,16 +149,12 @@
     // Text
     /**
      * drawText: count is of glyphs
-     * totalAdvance is ignored in software renderering, used by hardware renderer for
-     * text decorations (underlines, strikethroughs).
+     * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
      */
     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) = 0;
-    /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */
-    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) = 0;
     /** drawTextOnPath: count is of glyphs */
     virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) = 0;
@@ -171,6 +167,9 @@
      * to be added to each glyph's position to get its absolute position.
      */
     virtual bool drawTextAbsolutePos() const = 0;
+
+protected:
+    void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
 };
 
 }; // namespace android
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 03b1706..4cfbb2a 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -18,6 +18,7 @@
 #include "Extensions.h"
 
 #include <GLES2/gl2.h>
+#include <log/log.h>
 
 #include <thread>
 #include <mutex>
@@ -29,6 +30,7 @@
 static std::once_flag sInitializedFlag;
 
 const DeviceInfo* DeviceInfo::get() {
+    LOG_ALWAYS_FATAL_IF(!sDeviceInfo, "DeviceInfo not yet initialized.");
     return sDeviceInfo;
 }
 
@@ -40,7 +42,6 @@
 }
 
 void DeviceInfo::load() {
-    mExtensions.load();
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
 }
 
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index f5e5735..759c12a 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -423,18 +423,6 @@
     addDrawOp(op);
 }
 
-void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions,
-        int count, int posCount, const SkPaint& paint) {
-    if (!text || count <= 0) return;
-
-    int bytesCount = 2 * count;
-    positions = refBuffer<float>(positions, count * 2);
-
-    DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount),
-                                             bytesCount, count, positions, refPaint(&paint));
-    addDrawOp(op);
-}
-
 void DisplayListCanvas::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,
@@ -450,6 +438,7 @@
     DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
             x, y, positions, refPaint(&paint), totalAdvance, bounds);
     addDrawOp(op);
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
 
 void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index fc08504..bf98f79 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,6 +17,14 @@
 #ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
 #define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
 
+#include "Canvas.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RenderNode.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "utils/Macros.h"
+
 #include <SkDrawFilter.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
@@ -25,13 +33,6 @@
 #include <SkTLazy.h>
 #include <cutils/compiler.h>
 
-#include "Canvas.h"
-#include "CanvasState.h"
-#include "DisplayList.h"
-#include "SkiaCanvasProxy.h"
-#include "RenderNode.h"
-#include "ResourceCache.h"
-
 namespace android {
 namespace uirenderer {
 
@@ -66,7 +67,7 @@
     virtual ~DisplayListCanvas();
 
     void reset(int width, int height);
-    __attribute__((warn_unused_result)) DisplayList* finishRecording();
+    WARN_UNUSED_RESULT DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // HWUI Canvas state operations
@@ -211,8 +212,6 @@
     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; }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 772aa72..977b53c 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1278,24 +1278,6 @@
     float mVOffset;
 };
 
-class DrawPosTextOp : public DrawSomeTextOp {
-public:
-    DrawPosTextOp(const char* text, int bytesCount, int count,
-            const float* positions, const SkPaint* paint)
-            : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
-        /* TODO: inherit from DrawBounded and init mLocalBounds */
-    }
-
-    virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
-        renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint);
-    }
-
-    virtual const char* name() override { return "DrawPosText"; }
-
-private:
-    const float* mPositions;
-};
-
 class DrawTextOp : public DrawStrokableOp {
 public:
     DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index e257715..02caaa4 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,7 +35,7 @@
 #endif
 
 
-void Extensions::load() {
+Extensions::Extensions() {
     auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
     mHasNPot = extensions.has("GL_OES_texture_npot");
     mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 8ccfabd..67cc747 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -31,7 +31,7 @@
 
 class Extensions {
 public:
-    void load();
+    Extensions();
 
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index ccf0b48..5f33cae 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,13 +21,20 @@
 #include "Extensions.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
-#include "OpenGLRenderer.h"
 #include "PixelBuffer.h"
 #include "Rect.h"
 #include "renderstate/RenderState.h"
 #include "utils/Blur.h"
 #include "utils/Timing.h"
 
+
+#if HWUI_NEW_OPS
+#include "BakedOpState.h"
+#include "BakedOpRenderer.h"
+#else
+#include "OpenGLRenderer.h"
+#endif
+
 #include <algorithm>
 #include <cutils/properties.h>
 #include <SkGlyph.h>
@@ -59,14 +66,25 @@
     int transformFlags = pureTranslate
             ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
     Glop glop;
+#if HWUI_NEW_OPS
+    GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
+            .setRoundRectClipState(bakedState->roundRectClipState)
+            .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
+            .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+            .setTransform(bakedState->computedState.transform, transformFlags)
+            .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+            .build();
+    renderer->renderGlop(*bakedState, glop);
+#else
     GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
+            .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
             .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
             .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha)
             .setTransform(*(renderer->currentSnapshot()), transformFlags)
             .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
-            .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
             .build();
     renderer->renderGlop(glop);
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -539,7 +557,7 @@
 }
 
 FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
+        int numGlyphs, float radius, const float* positions) {
     checkInit();
 
     DropShadow image;
@@ -558,7 +576,7 @@
     mBounds = nullptr;
 
     Rect bounds;
-    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
+    mCurrentFont->measure(paint, text, numGlyphs, &bounds, positions);
 
     uint32_t intRadius = Blur::convertRadiusToInt(radius);
     uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
@@ -590,7 +608,7 @@
         // text has non-whitespace, so draw and blur to create the shadow
         // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
         // TODO: don't draw pure whitespace in the first place, and avoid needing this check
-        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
+        mCurrentFont->render(paint, text, numGlyphs, penX, penY,
                 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
 
         // Unbind any PBO we might have used
@@ -635,15 +653,15 @@
 }
 
 bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
-        const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
+        int numGlyphs, int x, int y, const float* positions,
+        Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
     }
 
     initRender(clip, bounds, functor);
-    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
+    mCurrentFont->render(paint, text, numGlyphs, x, y, positions);
 
     if (forceFinish) {
         finishRender();
@@ -653,15 +671,15 @@
 }
 
 bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
-        float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) {
+        int numGlyphs, const SkPath* path, float hOffset, float vOffset,
+        Rect* bounds, TextDrawFunctor* functor) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
     }
 
     initRender(clip, bounds, functor);
-    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+    mCurrentFont->render(paint, text, numGlyphs, path, hOffset, vOffset);
     finishRender();
 
     return mDrawn;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 8172312..87cfe7f 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -44,13 +44,28 @@
 namespace android {
 namespace uirenderer {
 
+#if HWUI_NEW_OPS
+class BakedOpState;
+class BakedOpRenderer;
+#else
 class OpenGLRenderer;
+#endif
 
 class TextDrawFunctor {
 public:
-    TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+    TextDrawFunctor(
+#if HWUI_NEW_OPS
+            BakedOpRenderer* renderer,
+            const BakedOpState* bakedState,
+#else
+            OpenGLRenderer* renderer,
+#endif
+            float x, float y, bool pureTranslate,
             int alpha, SkXfermode::Mode mode, const SkPaint* paint)
         : renderer(renderer)
+#if HWUI_NEW_OPS
+        , bakedState(bakedState)
+#endif
         , x(x)
         , y(y)
         , pureTranslate(pureTranslate)
@@ -61,7 +76,12 @@
 
     void draw(CacheTexture& texture, bool linearFiltering);
 
+#if HWUI_NEW_OPS
+    BakedOpRenderer* renderer;
+    const BakedOpState* bakedState;
+#else
     OpenGLRenderer* renderer;
+#endif
     float x;
     float y;
     bool pureTranslate;
@@ -83,15 +103,13 @@
     void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
     void endPrecaching();
 
-    // bounds is an out parameter
     bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
-            uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
-            Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true);
+            int numGlyphs, int x, int y, const float* positions,
+            Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true);
 
-    // bounds is an out parameter
     bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
-            uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
-            float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor);
+            int numGlyphs, const SkPath* path,
+            float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor);
 
     struct DropShadow {
         uint32_t width;
@@ -103,8 +121,8 @@
 
     // After renderDropShadow returns, the called owns the memory in DropShadow.image
     // and is responsible for releasing it when it's done with it
-    DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
-            uint32_t len, int numGlyphs, float radius, const float* positions);
+    DropShadow renderDropShadow(const SkPaint* paint, const char *text, int numGlyphs,
+            float radius, const float* positions);
 
     void setTextureFiltering(bool linearFiltering) {
         mLinearFiltering = linearFiltering;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 39cadd1..b117754 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-#include <GLES2/gl2.h>
+#include "LayerCache.h"
+
+#include "Caches.h"
+#include "Properties.h"
 
 #include <utils/Log.h>
 
-#include "Caches.h"
-#include "LayerCache.h"
-#include "Properties.h"
+#include <GLES2/gl2.h>
 
 namespace android {
 namespace uirenderer {
@@ -29,15 +30,9 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
-    char property[PROPERTY_VALUE_MAX];
-    if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) {
-        INIT_LOGD("  Setting layer cache size to %sMB", property);
-        setMaxSize(MB(atof(property)));
-    } else {
-        INIT_LOGD("  Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
-    }
-}
+LayerCache::LayerCache()
+        : mSize(0)
+        , mMaxSize(Properties::layerPoolSize) {}
 
 LayerCache::~LayerCache() {
     clear();
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 68f80ea..5e954ae 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,11 +18,13 @@
 
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "utils/FatVector.h"
 #include "utils/PaintUtils.h"
+#include "utils/TraceUtils.h"
 
 #include <SkCanvas.h>
-#include <utils/Trace.h>
+#include <SkPathOps.h>
 #include <utils/TypeHelpers.h>
 
 namespace android {
@@ -32,8 +34,8 @@
 
 public:
     BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
-        : mBatchId(batchId)
-        , mMerging(merging) {
+            : mBatchId(batchId)
+            , mMerging(merging) {
         mBounds = op->computedState.clippedBounds;
         mOps.push_back(op);
     }
@@ -206,9 +208,10 @@
 };
 
 OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
-        const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+        const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
         : width(width)
         , height(height)
+        , repaintRect(repaintRect)
         , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
         , beginLayerOp(beginLayerOp)
         , renderNode(renderNode) {}
@@ -308,15 +311,19 @@
 
 OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes)
+        const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
-        mLayerStack.push_back(0);
 
+    mLayerReorderers.reserve(layers.entries().size());
+    mLayerStack.reserve(layers.entries().size());
+
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
+    mLayerStack.push_back(0);
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
-            Vector3());
+            lightCenter);
 
     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
     // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
@@ -324,12 +331,15 @@
         RenderNode* layerNode = layers.entries()[i].renderNode;
         const Rect& layerDamage = layers.entries()[i].damage;
 
-        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
-        mCanvasState.writableSnapshot()->setClip(
-                layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
+        // map current light center into RenderNode's coordinate space
+        Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
+        layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter);
+
+        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
+                layerDamage, lightCenter, nullptr, layerNode);
 
         if (layerNode->getDisplayList()) {
-            deferImpl(*(layerNode->getDisplayList()));
+            deferDisplayList(*(layerNode->getDisplayList()));
         }
         restoreForLayer();
     }
@@ -344,16 +354,18 @@
     }
 }
 
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+        const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
+            Rect(viewportWidth, viewportHeight));
     mLayerStack.push_back(0);
-
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
-    deferImpl(displayList);
+            0, 0, viewportWidth, viewportHeight, lightCenter);
+
+    deferDisplayList(displayList);
 }
 
 void OpReorderer::onViewportInitialized() {}
@@ -361,18 +373,99 @@
 void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
 
 void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
-    if (node.applyViewProperties(mCanvasState, mAllocator)) {
-        // not rejected so render
+    const RenderProperties& properties = node.properties();
+    const Outline& outline = properties.getOutline();
+    if (properties.getAlpha() <= 0
+            || (outline.getShouldClip() && outline.isEmpty())
+            || properties.getScaleX() == 0
+            || properties.getScaleY() == 0) {
+        return; // rejected
+    }
+
+    if (properties.getLeft() != 0 || properties.getTop() != 0) {
+        mCanvasState.translate(properties.getLeft(), properties.getTop());
+    }
+    if (properties.getStaticMatrix()) {
+        mCanvasState.concatMatrix(*properties.getStaticMatrix());
+    } else if (properties.getAnimationMatrix()) {
+        mCanvasState.concatMatrix(*properties.getAnimationMatrix());
+    }
+    if (properties.hasTransformMatrix()) {
+        if (properties.isTransformTranslateOnly()) {
+            mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
+        } else {
+            mCanvasState.concatMatrix(*properties.getTransformMatrix());
+        }
+    }
+
+    const int width = properties.getWidth();
+    const int height = properties.getHeight();
+
+    Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
+    const bool isLayer = properties.effectiveLayerType() != LayerType::None;
+    int clipFlags = properties.getClippingFlags();
+    if (properties.getAlpha() < 1) {
+        if (isLayer) {
+            clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+        }
+        if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+            // simply scale rendering content's alpha
+            mCanvasState.scaleAlpha(properties.getAlpha());
+        } else {
+            // schedule saveLayer by initializing saveLayerBounds
+            saveLayerBounds.set(0, 0, width, height);
+            if (clipFlags) {
+                properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
+                clipFlags = 0; // all clipping done by savelayer
+            }
+        }
+
+        if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
+            // pretend alpha always causes savelayer to warn about
+            // performance problem affecting old versions
+            ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
+        }
+    }
+    if (clipFlags) {
+        Rect clipRect;
+        properties.getClippingRectForFlags(clipFlags, &clipRect);
+        mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+                SkRegion::kIntersect_Op);
+    }
+
+    if (properties.getRevealClip().willClip()) {
+        Rect bounds;
+        properties.getRevealClip().getBounds(&bounds);
+        mCanvasState.setClippingRoundRect(mAllocator,
+                bounds, properties.getRevealClip().getRadius());
+    } else if (properties.getOutline().willClip()) {
+        mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
+    }
+
+    if (!mCanvasState.quickRejectConservative(0, 0, width, height)) {
+        // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
         if (node.getLayer()) {
             // HW layer
             LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
             BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
             if (bakedOpState) {
-                // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+                // Node's layer already deferred, schedule it to render into parent layer
                 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
             }
+        } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
+            // draw DisplayList contents within temporary, since persisted layer could not be used.
+            // (temp layers are clipped to viewport, since they don't persist offscreen content)
+            SkPaint saveLayerPaint;
+            saveLayerPaint.setAlpha(properties.getAlpha());
+            onBeginLayerOp(*new (mAllocator) BeginLayerOp(
+                    saveLayerBounds,
+                    Matrix4::identity(),
+                    saveLayerBounds,
+                    &saveLayerPaint));
+            deferDisplayList(*(node.getDisplayList()));
+            onEndLayerOp(*new (mAllocator) EndLayerOp());
         } else {
-            deferImpl(*(node.getDisplayList()));
+            deferDisplayList(*(node.getDisplayList()));
         }
     }
 }
@@ -462,8 +555,61 @@
 }
 
 void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
-    // TODO
+    auto& node = *casterNodeOp.renderNode;
+    auto& properties = node.properties();
+
+    if (properties.getAlpha() <= 0.0f
+            || properties.getOutline().getAlpha() <= 0.0f
+            || !properties.getOutline().getPath()
+            || properties.getScaleX() == 0
+            || properties.getScaleY() == 0) {
+        // no shadow to draw
+        return;
+    }
+
+    const SkPath* casterOutlinePath = properties.getOutline().getPath();
+    const SkPath* revealClipPath = properties.getRevealClip().getPath();
+    if (revealClipPath && revealClipPath->isEmpty()) return;
+
+    float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
+
+    // holds temporary SkPath to store the result of intersections
+    SkPath* frameAllocatedPath = nullptr;
+    const SkPath* casterPath = casterOutlinePath;
+
+    // intersect the shadow-casting path with the reveal, if present
+    if (revealClipPath) {
+        frameAllocatedPath = createFrameAllocatedPath();
+
+        Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
+        casterPath = frameAllocatedPath;
+    }
+
+    // intersect the shadow-casting path with the clipBounds, if present
+    if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
+        if (!frameAllocatedPath) {
+            frameAllocatedPath = createFrameAllocatedPath();
+        }
+        Rect clipBounds;
+        properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+        SkPath clipBoundsPath;
+        clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
+                clipBounds.right, clipBounds.bottom);
+
+        Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
+        casterPath = frameAllocatedPath;
+    }
+
+    ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
+            mCanvasState.getLocalClipBounds(),
+            mCanvasState.currentSnapshot()->getRelativeLightCenter());
+    BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
+            mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
+    if (CC_LIKELY(bakedOpState)) {
+        currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
+    }
 }
+
 /**
  * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
  *
@@ -472,7 +618,7 @@
  */
 #define OP_RECEIVER(Type) \
         [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
-void OpReorderer::deferImpl(const DisplayList& displayList) {
+void OpReorderer::deferDisplayList(const DisplayList& displayList) {
     static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
         MAP_OPS(OP_RECEIVER)
     };
@@ -525,6 +671,13 @@
     currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
 }
 
+void OpReorderer::onLinesOp(const LinesOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+
+}
+
 void OpReorderer::onRectOp(const RectOp& op) {
     BakedOpState* bakedStateOp = tryBakeOpState(op);
     if (!bakedStateOp) return; // quick rejected
@@ -537,17 +690,34 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
-        const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
+void OpReorderer::onTextOp(const TextOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
 
+    // TODO: better handling of shader (since we won't care about color then)
+    batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
+            ? OpBatchType::Text : OpBatchType::ColorText;
+    mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+    currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
+}
+
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+        float contentTranslateX, float contentTranslateY,
+        const Rect& repaintRect,
+        const Vector3& lightCenter,
+        const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
-    mCanvasState.writableSnapshot()->transform->loadIdentity();
     mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
+    mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+    mCanvasState.writableSnapshot()->transform->loadTranslate(
+            contentTranslateX, contentTranslateY, 0);
+    mCanvasState.writableSnapshot()->setClip(
+            repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
 
-    // create a new layer, and push its index on the stack
+    // create a new layer repaint, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+    mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
 }
 
 void OpReorderer::restoreForLayer() {
@@ -558,9 +728,48 @@
 
 // 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();
-    saveForLayer(layerWidth, layerHeight, &op, nullptr);
+    uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+    uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
+    auto previous = mCanvasState.currentSnapshot();
+    Vector3 lightCenter = previous->getRelativeLightCenter();
+
+    // Combine all transforms used to present saveLayer content:
+    // parent content transform * canvas transform * bounds offset
+    Matrix4 contentTransform(*previous->transform);
+    contentTransform.multiply(op.localMatrix);
+    contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
+
+    Matrix4 inverseContentTransform;
+    inverseContentTransform.loadInverse(contentTransform);
+
+    // map the light center into layer-relative space
+    inverseContentTransform.mapPoint3d(lightCenter);
+
+    // Clip bounds of temporary layer to parent's clip rect, so:
+    Rect saveLayerBounds(layerWidth, layerHeight);
+    //     1) transform Rect(width, height) into parent's space
+    //        note: left/top offsets put in contentTransform above
+    contentTransform.mapRect(saveLayerBounds);
+    //     2) intersect with parent's clip
+    saveLayerBounds.doIntersect(previous->getRenderTargetClip());
+    //     3) and transform back
+    inverseContentTransform.mapRect(saveLayerBounds);
+    saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
+    saveLayerBounds.roundOut();
+
+    // if bounds are reduced, will clip the layer's area by reducing required bounds...
+    layerWidth = saveLayerBounds.getWidth();
+    layerHeight = saveLayerBounds.getHeight();
+    // ...and shifting drawing content to account for left/top side clipping
+    float contentTranslateX = -saveLayerBounds.left;
+    float contentTranslateY = -saveLayerBounds.top;
+
+    saveForLayer(layerWidth, layerHeight,
+            contentTranslateX, contentTranslateY,
+            Rect(layerWidth, layerHeight),
+            lightCenter,
+            &op, nullptr);
 }
 
 void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
@@ -593,5 +802,9 @@
     LOG_ALWAYS_FATAL("unsupported");
 }
 
+void OpReorderer::onShadowOp(const ShadowOp& op) {
+    LOG_ALWAYS_FATAL("unsupported");
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 936b6ed..976f413 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -51,6 +51,7 @@
         AlphaMaskTexture,
         Text,
         ColorText,
+        Shadow,
 
         Count // must be last
     };
@@ -66,13 +67,13 @@
     class LayerReorderer {
     public:
         // Create LayerReorderer for Fbo0
-        LayerReorderer(uint32_t width, uint32_t height)
-                : LayerReorderer(width, height, nullptr, nullptr) {};
+        LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+                : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
 
         // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
         // saveLayer, renderNode is present for a HW layer.
         LayerReorderer(uint32_t width, uint32_t height,
-                const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+                const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
 
         // 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
@@ -100,6 +101,7 @@
 
         const uint32_t width;
         const uint32_t height;
+        const Rect repaintRect;
         OffscreenBuffer* offscreenBuffer;
         const BeginLayerOp* beginLayerOp;
         const RenderNode* renderNode;
@@ -115,14 +117,15 @@
 
         // Maps batch ids to the most recent *non-merging* batch of that id
         OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
     };
+
 public:
     OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes);
+            const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
 
-    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+            const Vector3& lightCenter);
 
     virtual ~OpReorderer() {}
 
@@ -152,18 +155,18 @@
             LayerReorderer& layer = mLayerReorderers[i];
             if (layer.renderNode) {
                 // cached HW layer - can't skip layer if empty
-                renderer.startLayer(layer.offscreenBuffer);
+                renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             } else if (!layer.empty()) { // save layer - skip entire layer if empty
-                layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
+                layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             }
         }
 
         const LayerReorderer& fbo0 = mLayerReorderers[0];
-        renderer.startFrame(fbo0.width, fbo0.height);
+        renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
         fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
         renderer.endFrame();
     }
@@ -187,6 +190,9 @@
         Positive
     };
     void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+            float contentTranslateX, float contentTranslateY,
+            const Rect& repaintRect,
+            const Vector3& lightCenter,
             const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
     void restoreForLayer();
 
@@ -201,7 +207,7 @@
 
     void deferShadow(const RenderNodeOp& casterOp);
 
-    void deferImpl(const DisplayList& displayList);
+    void deferDisplayList(const DisplayList& displayList);
 
     template <typename V>
     void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
@@ -210,6 +216,10 @@
 
     void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
 
+    SkPath* createFrameAllocatedPath() {
+        mFrameAllocatedPaths.emplace_back(new SkPath);
+        return mFrameAllocatedPaths.back().get();
+    }
     /**
      * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
      *
@@ -220,6 +230,8 @@
     void on##Type(const Type& op);
     MAP_OPS(INTERNAL_OP_HANDLER)
 
+    std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths;
+
     // List of every deferred layer's render state. Replayed in reverse order to render a frame.
     std::vector<LayerReorderer> mLayerReorderers;
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 8c3603b..e386b1c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1950,7 +1950,7 @@
 }
 
 void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
-        int bytesCount, int count, const float* positions,
+        int count, const float* positions,
         FontRenderer& fontRenderer, int alpha, float x, float y) {
     mCaches.textureState().activateTexture(0);
 
@@ -1963,7 +1963,7 @@
     //       if shader-based correction is enabled
     mCaches.dropShadowCache.setFontRenderer(fontRenderer);
     ShadowTexture* texture = mCaches.dropShadowCache.get(
-            paint, text, bytesCount, count, textShadow.radius, positions);
+            paint, text, count, textShadow.radius, positions);
     // If the drop shadow exceeds the max texture size or couldn't be
     // allocated, skip drawing
     if (!texture) return;
@@ -1991,57 +1991,6 @@
             && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
 }
 
-void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
-        const float* positions, const SkPaint* paint) {
-    if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
-        return;
-    }
-
-    // NOTE: Skia does not support perspective transform on drawPosText yet
-    if (!currentTransform()->isSimple()) {
-        return;
-    }
-
-    mRenderState.scissor().setEnabled(true);
-
-    float x = 0.0f;
-    float y = 0.0f;
-    const bool pureTranslate = currentTransform()->isPureTranslate();
-    if (pureTranslate) {
-        x = floorf(x + currentTransform()->getTranslateX() + 0.5f);
-        y = floorf(y + currentTransform()->getTranslateY() + 0.5f);
-    }
-
-    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
-    fontRenderer.setFont(paint, SkMatrix::I());
-
-    int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
-    SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
-
-    if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
-        drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
-                alpha, 0.0f, 0.0f);
-    }
-
-    // Pick the appropriate texture filtering
-    bool linearFilter = currentTransform()->changesBounds();
-    if (pureTranslate && !linearFilter) {
-        linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
-    }
-    fontRenderer.setTextureFiltering(linearFilter);
-
-    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);
-    if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
-            positions, hasLayer() ? &bounds : nullptr, &functor)) {
-        dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
-        mDirty = true;
-    }
-
-}
-
 bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
     if (CC_LIKELY(transform.isPureTranslate())) {
         outMatrix->setIdentity();
@@ -2166,7 +2115,7 @@
 
     if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
         fontRenderer.setFont(paint, SkMatrix::I());
-        drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+        drawTextShadow(paint, text, count, positions, fontRenderer,
                 alpha, oldX, oldY);
     }
 
@@ -2195,17 +2144,22 @@
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+    TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+#else
     TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
+#endif
 
     // don't call issuedrawcommand, do it at end of batch
     bool forceFinish = (drawOpMode != DrawOpMode::kDefer);
     if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
         SkPaint paintCopy(*paint);
         paintCopy.setTextAlign(SkPaint::kLeft_Align);
-        status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
+        status = fontRenderer.renderPosText(&paintCopy, clip, text, count, x, y,
                 positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
     } else {
-        status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+        status = fontRenderer.renderPosText(paint, clip, text, count, x, y,
                 positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
     }
 
@@ -2216,8 +2170,6 @@
         dirtyLayerUnchecked(layerBounds, getRegion());
     }
 
-    drawTextDecorations(totalAdvance, oldX, oldY, paint);
-
     mDirty = true;
 }
 
@@ -2236,12 +2188,17 @@
 
     int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
     SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+    TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+#else
     TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
+#endif
 
     const Rect* clip = &writableSnapshot()->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
-    if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+    if (fontRenderer.renderTextOnPath(paint, clip, text, count, path,
             hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) {
         dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
         mDirty = true;
@@ -2255,12 +2212,15 @@
 
     PathTexture* texture = mCaches.pathCache.get(path, paint);
     if (!texture) return;
-    const AutoTexture autoCleanup(texture);
 
     const float x = texture->left - texture->offset;
     const float y = texture->top - texture->offset;
 
     drawPathTexture(texture, x, y, paint);
+
+    if (texture->cleanup) {
+        mCaches.pathCache.remove(path, paint);
+    }
     mDirty = true;
 }
 
@@ -2372,56 +2332,6 @@
     renderGlop(glop);
 }
 
-// Same values used by Skia
-#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
-#define kStdUnderline_Offset    (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
-        const SkPaint* paint) {
-    // Handle underline and strike-through
-    uint32_t flags = paint->getFlags();
-    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
-        SkPaint paintCopy(*paint);
-
-        if (CC_LIKELY(underlineWidth > 0.0f)) {
-            const float textSize = paintCopy.getTextSize();
-            const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f);
-
-            const float left = x;
-            float top = 0.0f;
-
-            int linesCount = 0;
-            if (flags & SkPaint::kUnderlineText_Flag) linesCount++;
-            if (flags & SkPaint::kStrikeThruText_Flag) linesCount++;
-
-            const int pointsCount = 4 * linesCount;
-            float points[pointsCount];
-            int currentPoint = 0;
-
-            if (flags & SkPaint::kUnderlineText_Flag) {
-                top = y + textSize * kStdUnderline_Offset;
-                points[currentPoint++] = left;
-                points[currentPoint++] = top;
-                points[currentPoint++] = left + underlineWidth;
-                points[currentPoint++] = top;
-            }
-
-            if (flags & SkPaint::kStrikeThruText_Flag) {
-                top = y + textSize * kStdStrikeThru_Offset;
-                points[currentPoint++] = left;
-                points[currentPoint++] = top;
-                points[currentPoint++] = left + underlineWidth;
-                points[currentPoint++] = top;
-            }
-
-            paintCopy.setStrokeWidth(strokeWidth);
-
-            drawLines(&points[0], pointsCount, &paintCopy);
-        }
-    }
-}
-
 void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
     if (mState.currentlyIgnored()) {
         return;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 400c225..84bc9b0 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -193,8 +193,6 @@
     void drawPoints(const float* points, int count, const SkPaint* paint);
     void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
             float hOffset, float vOffset, const SkPaint* paint);
-    void drawPosText(const char* text, int bytesCount, int count,
-            const float* positions, const SkPaint* paint);
     void drawText(const char* text, int bytesCount, int count, float x, float y,
             const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
             DrawOpMode drawOpMode = DrawOpMode::kImmediate);
@@ -637,24 +635,11 @@
      */
     void drawConvexPath(const SkPath& path, const SkPaint* paint);
 
-    /**
-     * Draws text underline and strike-through if needed.
-     *
-     * @param text The text to decor
-     * @param bytesCount The number of bytes in the text
-     * @param totalAdvance The total advance in pixels, defines underline/strikethrough length
-     * @param x The x coordinate where the text will be drawn
-     * @param y The y coordinate where the text will be drawn
-     * @param paint The paint to draw the text with
-     */
-    void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
-
    /**
      * Draws shadow layer on text (with optional positions).
      *
      * @param paint The paint to draw the shadow with
      * @param text The text to draw
-     * @param bytesCount The number of bytes in the text
      * @param count The number of glyphs in the text
      * @param positions The x, y positions of individual glyphs (or NULL)
      * @param fontRenderer The font renderer object
@@ -662,7 +647,7 @@
      * @param x The x coordinate where the shadow will be drawn
      * @param y The y coordinate where the shadow will be drawn
      */
-    void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+    void drawTextShadow(const SkPaint* paint, const char* text, int count,
             const float* positions, FontRenderer& fontRenderer, int alpha,
             float x, float y);
 
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index fd9ab18..06ea55a 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -396,6 +396,13 @@
     return texture;
 }
 
+void PathCache::remove(const SkPath* path, const SkPaint* paint)
+{
+    PathDescription entry(kShapePath, paint);
+    entry.shape.path.mGenerationID = path->getGenerationID();
+    mCache.remove(entry);
+}
+
 void PathCache::precache(const SkPath* path, const SkPaint* paint) {
     if (!Caches::getInstance().tasks.canRunTasks()) {
         return;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 31f8d35..302e9f8 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -202,6 +202,7 @@
     PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
             bool useCenter, const SkPaint* paint);
     PathTexture* get(const SkPath* path, const SkPaint* paint);
+    void         remove(const SkPath* path, const SkPaint* paint);
 
     /**
      * Removes the specified path. This is meant to be called from threads
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
new file mode 100644
index 0000000..4e9ac9c
--- /dev/null
+++ b/libs/hwui/PathParser.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "PathParser.h"
+
+#include "jni.h"
+
+#include <errno.h>
+#include <utils/Log.h>
+#include <sstream>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+static size_t nextStart(const char* s, size_t length, size_t startIndex) {
+    size_t index = startIndex;
+    while (index < length) {
+        char c = s[index];
+        // Note that 'e' or 'E' are not valid path commands, but could be
+        // used for floating point numbers' scientific notation.
+        // Therefore, when searching for next command, we should ignore 'e'
+        // and 'E'.
+        if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+                && c != 'e' && c != 'E') {
+            return index;
+        }
+        index++;
+    }
+    return index;
+}
+
+/**
+ * Calculate the position of the next comma or space or negative sign
+ * @param s the string to search
+ * @param start the position to start searching
+ * @param result the result of the extraction, including the position of the
+ * the starting position of next number, whether it is ending with a '-'.
+ */
+static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) {
+    // Now looking for ' ', ',', '.' or '-' from the start.
+    int currentIndex = start;
+    bool foundSeparator = false;
+    *outEndWithNegOrDot = false;
+    bool secondDot = false;
+    bool isExponential = false;
+    for (; currentIndex < end; currentIndex++) {
+        bool isPrevExponential = isExponential;
+        isExponential = false;
+        char currentChar = s[currentIndex];
+        switch (currentChar) {
+        case ' ':
+        case ',':
+            foundSeparator = true;
+            break;
+        case '-':
+            // The negative sign following a 'e' or 'E' is not a separator.
+            if (currentIndex != start && !isPrevExponential) {
+                foundSeparator = true;
+                *outEndWithNegOrDot = true;
+            }
+            break;
+        case '.':
+            if (!secondDot) {
+                secondDot = true;
+            } else {
+                // This is the second dot, and it is considered as a separator.
+                foundSeparator = true;
+                *outEndWithNegOrDot = true;
+            }
+            break;
+        case 'e':
+        case 'E':
+            isExponential = true;
+            break;
+        }
+        if (foundSeparator) {
+            break;
+        }
+    }
+    // In the case where nothing is found, we put the end position to the end of
+    // our extract range. Otherwise, end position will be where separator is found.
+    *outEndPosition = currentIndex;
+}
+
+static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) {
+    char* endPtr = NULL;
+    float currentValue = strtof(startPtr, &endPtr);
+    if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
+        result->failureOccurred = true;
+        result->failureMessage = "Float out of range:  ";
+        result->failureMessage.append(startPtr, expectedLength);
+    }
+    if (currentValue == 0 && endPtr == startPtr) {
+        // No conversion is done.
+        result->failureOccurred = true;
+        result->failureMessage = "Float format error when parsing: ";
+        result->failureMessage.append(startPtr, expectedLength);
+    }
+    return currentValue;
+}
+
+/**
+ * Parse the floats in the string.
+ *
+ * @param s the string containing a command and list of floats
+ * @return true on success
+ */
+static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
+        const char* pathStr, int start, int end) {
+
+    if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
+        return;
+    }
+    int startPosition = start + 1;
+    int endPosition = start;
+
+    // The startPosition should always be the first character of the
+    // current number, and endPosition is the character after the current
+    // number.
+    while (startPosition < end) {
+        bool endWithNegOrDot;
+        extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
+
+        if (startPosition < endPosition) {
+            float currentValue = parseFloat(result, &pathStr[startPosition],
+                    end - startPosition);
+            if (result->failureOccurred) {
+                return;
+            }
+            outPoints->push_back(currentValue);
+        }
+
+        if (endWithNegOrDot) {
+            // Keep the '-' or '.' sign with next number.
+            startPosition = endPosition;
+        } else {
+            startPosition = endPosition + 1;
+        }
+    }
+    return;
+}
+
+void PathParser::getPathDataFromString(PathData* data, ParseResult* result,
+        const char* pathStr, size_t strLen) {
+    if (pathStr == NULL) {
+        result->failureOccurred = true;
+        result->failureMessage = "Path string cannot be NULL.";
+        return;
+    }
+
+    size_t start = 0;
+    size_t end = 1;
+
+    while (end < strLen) {
+        end = nextStart(pathStr, strLen, end);
+        std::vector<float> points;
+        getFloats(&points, result, pathStr, start, end);
+        if (result->failureOccurred) {
+            return;
+        }
+        data->verbs.push_back(pathStr[start]);
+        data->verbSizes.push_back(points.size());
+        data->points.insert(data->points.end(), points.begin(), points.end());
+        start = end;
+        end++;
+    }
+
+    if ((end - start) == 1 && start < strLen) {
+        data->verbs.push_back(pathStr[start]);
+        data->verbSizes.push_back(0);
+    }
+    return;
+}
+
+void PathParser::dump(const PathData& data) {
+    // Print out the path data.
+    size_t start = 0;
+    for (size_t i = 0; i < data.verbs.size(); i++) {
+        std::ostringstream os;
+        os << data.verbs[i];
+        os << ", verb size: " << data.verbSizes[i];
+        for (size_t j = 0; j < data.verbSizes[i]; j++) {
+            os << " " << data.points[start + j];
+        }
+        start += data.verbSizes[i];
+        ALOGD("%s", os.str().c_str());
+    }
+
+    std::ostringstream os;
+    for (size_t i = 0; i < data.points.size(); i++) {
+        os << data.points[i] << ", ";
+    }
+    ALOGD("points are : %s", os.str().c_str());
+}
+
+void PathParser::parseStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) {
+    PathData pathData;
+    getPathDataFromString(&pathData, result, pathStr, strLen);
+    if (result->failureOccurred) {
+        return;
+    }
+    // Check if there is valid data coming out of parsing the string.
+    if (pathData.verbs.size() == 0) {
+        result->failureOccurred = true;
+        result->failureMessage = "No verbs found in the string for pathData";
+        return;
+    }
+    VectorDrawableUtils::verbsToPath(skPath, pathData);
+    return;
+}
+
+}; // namespace uirenderer
+}; //namespace android
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
new file mode 100644
index 0000000..4c87b18
--- /dev/null
+++ b/libs/hwui/PathParser.h
@@ -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.
+ */
+
+#ifndef ANDROID_HWUI_PATHPARSER_H
+#define ANDROID_HWUI_PATHPARSER_H
+
+#include "VectorDrawablePath.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <jni.h>
+#include <android/log.h>
+#include <cutils/compiler.h>
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+
+class PathParser {
+public:
+    struct ANDROID_API ParseResult {
+        bool failureOccurred = false;
+        std::string failureMessage;
+    };
+    /**
+     * Parse the string literal and create a Skia Path. Return true on success.
+     */
+    ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result,
+            const char* pathStr, size_t strLength);
+    ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result,
+            const char* pathStr, size_t strLength);
+    static void dump(const PathData& data);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+#endif //ANDROID_HWUI_PATHPARSER_H
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e818186..0669596 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -37,6 +37,7 @@
 bool Properties::enablePartialUpdates = true;
 
 float Properties::textGamma = DEFAULT_TEXT_GAMMA;
+int Properties::layerPoolSize = DEFAULT_LAYER_CACHE_SIZE;
 
 DebugLevel Properties::debugLevel = kDebugDisabled;
 OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -52,10 +53,19 @@
 ProfileType Properties::sProfileType = ProfileType::None;
 bool Properties::sDisableProfileBars = false;
 
+static int property_get_int(const char* key, int defaultValue) {
+    char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+    if (property_get(key, buf, "") > 0) {
+        return atoi(buf);
+    }
+    return defaultValue;
+}
+
 static float property_get_float(const char* key, float defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
 
-    if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+    if (property_get(key, buf, "") > 0) {
         return atof(buf);
     }
     return defaultValue;
@@ -114,16 +124,14 @@
 
     showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false);
 
-    debugLevel = kDebugDisabled;
-    if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) {
-        debugLevel = (DebugLevel) atoi(property);
-    }
+    debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled);
 
     skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
     useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
     enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
 
     textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA);
+    layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE));
 
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1293c78..1dde7e0 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -267,6 +267,8 @@
 
     static float textGamma;
 
+    static int layerPoolSize;
+
     static DebugLevel debugLevel;
     static OverdrawColorSet overdrawColorSet;
     static StencilClipDebug debugStencilClip;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 04af8e3..127dca5 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,10 +17,12 @@
 #ifndef ANDROID_HWUI_RECORDED_OP_H
 #define ANDROID_HWUI_RECORDED_OP_H
 
-#include "utils/LinearAllocator.h"
-#include "Rect.h"
+#include "font/FontUtil.h"
 #include "Matrix.h"
+#include "Rect.h"
 #include "RenderNode.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
 
 #include "SkXfermode.h"
 
@@ -41,9 +43,12 @@
  */
 #define MAP_OPS(OP_FN) \
         OP_FN(BitmapOp) \
+        OP_FN(LinesOp) \
         OP_FN(RectOp) \
         OP_FN(RenderNodeOp) \
+        OP_FN(ShadowOp) \
         OP_FN(SimpleRectsOp) \
+        OP_FN(TextOp) \
         OP_FN(BeginLayerOp) \
         OP_FN(EndLayerOp) \
         OP_FN(LayerOp)
@@ -96,6 +101,10 @@
     bool skipInOrderDraw = false;
 };
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Standard Ops
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
 struct BitmapOp : RecordedOp {
     BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
             : SUPER(BitmapOp)
@@ -104,11 +113,48 @@
     // TODO: asset atlas/texture id lookup?
 };
 
+struct LinesOp : RecordedOp {
+    LinesOp(BASE_PARAMS, const float* points, const int floatCount)
+            : SUPER(LinesOp)
+            , points(points)
+            , floatCount(floatCount) {}
+    const float* points;
+    const int floatCount;
+};
+
 struct RectOp : RecordedOp {
     RectOp(BASE_PARAMS)
             : SUPER(RectOp) {}
 };
 
+/**
+ * Real-time, dynamic-lit shadow.
+ *
+ * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
+ * and are resolved dynamically, and transform isn't needed.
+ *
+ * State construction handles these properties specially, ignoring matrix/bounds.
+ */
+struct ShadowOp : RecordedOp {
+    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
+            const Rect& clipRect, const Vector3& lightCenter)
+            : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
+            , shadowMatrixXY(casterOp.localMatrix)
+            , shadowMatrixZ(casterOp.localMatrix)
+            , casterAlpha(casterAlpha)
+            , casterPath(casterPath)
+            , lightCenter(lightCenter) {
+        const RenderNode& node = *casterOp.renderNode;
+        node.applyViewPropertyTransforms(shadowMatrixXY, false);
+        node.applyViewPropertyTransforms(shadowMatrixZ, true);
+    };
+    Matrix4 shadowMatrixXY;
+    Matrix4 shadowMatrixZ;
+    const float casterAlpha;
+    const SkPath* casterPath;
+    const Vector3 lightCenter;
+};
+
 struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
     SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
             : SUPER(SimpleRectsOp)
@@ -118,6 +164,27 @@
     const size_t vertexCount;
 };
 
+struct TextOp : RecordedOp {
+    TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount,
+            float x, float y)
+            : SUPER(TextOp)
+            , glyphs(glyphs)
+            , positions(positions)
+            , glyphCount(glyphCount)
+            , x(x)
+            , y(y) {}
+    const glyph_t* glyphs;
+    const float* positions;
+    const int glyphCount;
+    const float x;
+    const float y;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Layers
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
 /**
  * Stateful operation! denotes the creation of an off-screen layer,
  * and that commands following will render into it.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e988555..61fa384 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@
 
     snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
     snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
-    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+    snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
 
     Rect clip = layerBounds;
     clip.translate(-untransformedBounds.left, -untransformedBounds.top);
@@ -230,12 +230,9 @@
 
 void RecordingCanvas::drawPaint(const SkPaint& paint) {
     // TODO: more efficient recording?
-    Matrix4 identity;
-    identity.loadIdentity();
-
     addOp(new (alloc()) RectOp(
             mState.getRenderTargetClipBounds(),
-            identity,
+            Matrix4::identity(),
             mState.getRenderTargetClipBounds(),
             refPaint(&paint)));
 }
@@ -244,9 +241,30 @@
 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::drawLines(const float* points, int floatCount, const SkPaint& paint) {
+    if (floatCount < 4) return;
+    floatCount &= ~0x3; // round down to nearest four
+
+    Rect unmappedBounds(points[0], points[1], points[0], points[1]);
+    for (int i = 2; i < floatCount; i += 2) {
+        unmappedBounds.left = std::min(unmappedBounds.left, points[i]);
+        unmappedBounds.right = std::max(unmappedBounds.right, points[i]);
+        unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]);
+        unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]);
+    }
+
+    // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
+    // 1.0 stroke, treat 1.0 as minimum.
+    unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f);
+
+    addOp(new (alloc()) LinesOp(
+            unmappedBounds,
+            *mState.currentSnapshot()->transform,
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
 }
+
 void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
     addOp(new (alloc()) RectOp(
             Rect(left, top, right, bottom),
@@ -388,17 +406,24 @@
 }
 
 // Text
-void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
+void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) {
-    LOG_ALWAYS_FATAL("TODO!");
+    if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+    glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+    positions = refBuffer<float>(positions, glyphCount * 2);
+
+    addOp(new (alloc()) TextOp(
+            Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint), glyphs, positions, glyphCount, x, y));
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
-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) {
+    // NOTE: can't use refPaint() directly, since it forces left alignment
     LOG_ALWAYS_FATAL("TODO!");
 }
 
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index fc84c98..736cc9e 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -20,11 +20,12 @@
 #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 "utils/LinearAllocator.h"
+#include "utils/Macros.h"
+#include "utils/NinePatch.h"
 
 #include <SkDrawFilter.h>
 #include <SkPaint.h>
@@ -49,7 +50,7 @@
     virtual ~RecordingCanvas();
 
     void reset(int width, int height);
-    __attribute__((warn_unused_result)) DisplayList* finishRecording();
+    WARN_UNUSED_RESULT DisplayList* finishRecording();
 
 // ----------------------------------------------------------------------------
 // MISC HWUI OPERATIONS - TODO: CATEGORIZE
@@ -177,8 +178,6 @@
     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; }
@@ -220,6 +219,15 @@
         return cachedPath;
     }
 
+    /**
+     * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
+     * based on paint generation ID)
+     *
+     * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
+     * since alignment offsetting has been done at a higher level. This is done to essentially all
+     * copied paints, since the deduping can mean a paint is shared by drawText commands and other
+     * types (which wouldn't care about alignment).
+     */
     inline const SkPaint* refPaint(const SkPaint* paint) {
         if (!paint) return nullptr;
 
@@ -238,10 +246,11 @@
         // 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));
+            SkPaint* copy = new SkPaint(*paint);
+            copy->setTextAlign(SkPaint::kLeft_Align);
 
+            cachedPaint = copy;
+            mDisplayList->paints.emplace_back(copy);
             // replaceValueFor() performs an add if the entry doesn't exist
             mPaintMap.replaceValueFor(key, cachedPaint);
             refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 50199db..472aad7 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -260,13 +260,6 @@
         bottom = std::max(bottom, y);
     }
 
-    void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
-        left = std::min(left, otherLeft);
-        top = std::min(top, otherTop);
-        right = std::max(right, otherRight);
-        bottom = std::max(bottom, otherBottom);
-    }
-
     SkRect toSkRect() const {
         return SkRect::MakeLTRB(left, top, right, bottom);
     }
@@ -276,7 +269,7 @@
     }
 
     void dump(const char* label = nullptr) const {
-        ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
+        ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom);
     }
 }; // class Rect
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 15ca718..3f24f44 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -248,22 +248,31 @@
     }
 }
 
-layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
 #if HWUI_NEW_OPS
-    return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+    return renderState.layerPool().get(renderState, width, height);
 #else
     return LayerRenderer::createRenderLayer(renderState, width, height);
 #endif
 }
 
-void destroyLayer(layer_t* layer) {
+static void destroyLayer(layer_t* layer) {
 #if HWUI_NEW_OPS
-    BakedOpRenderer::destroyOffscreenBuffer(layer);
+    RenderState& renderState = layer->renderState;
+    renderState.layerPool().putOrDelete(layer);
 #else
     LayerRenderer::destroyLayer(layer);
 #endif
 }
 
+static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) {
+#if HWUI_NEW_OPS
+    return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
+#else
+    return layer->layer.getWidth() == width && layer->layer.getHeight() == height;
+#endif
+}
+
 void RenderNode::pushLayerUpdate(TreeInfo& info) {
     LayerType layerType = properties().effectiveLayerType();
     // If we are not a layer OR we cannot be rendered (eg, view was detached)
@@ -278,17 +287,19 @@
 
     bool transformUpdateNeeded = false;
     if (!mLayer) {
-            mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
-            damageSelf(info);
-            transformUpdateNeeded = true;
+        mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+#if !HWUI_NEW_OPS
+        applyLayerPropertiesToLayer(info);
+#endif
+        damageSelf(info);
+        transformUpdateNeeded = true;
+    } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
 #if HWUI_NEW_OPS
-    } else if (mLayer->viewportWidth != (uint32_t) getWidth()
-            || mLayer->viewportHeight != (uint32_t)getHeight()) {
-        // TODO: allow node's layer to grow larger
-        if ((uint32_t)getWidth() > mLayer->texture.width
-                || (uint32_t)getHeight() > mLayer->texture.height) {
+        RenderState& renderState = mLayer->renderState;
+        if (properties().fitsOnLayer()) {
+            mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
+        } else {
 #else
-    } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
         if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
 #endif
             destroyLayer(mLayer);
@@ -318,15 +329,11 @@
         return;
     }
 
-    if (transformUpdateNeeded) {
+    if (transformUpdateNeeded && mLayer) {
         // update the transform in window of the layer to reset its origin wrt light source position
         Matrix4 windowTransform;
         info.damageAccumulator->computeCurrentTransform(&windowTransform);
-#if HWUI_NEW_OPS
-        // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
-#else
         mLayer->setWindowTransform(windowTransform);
-#endif
     }
 
 #if HWUI_NEW_OPS
@@ -520,76 +527,6 @@
     }
 }
 
-bool RenderNode::applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) 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());
-        }
-    }
-
-    const bool isLayer = properties().effectiveLayerType() != LayerType::None;
-    int clipFlags = properties().getClippingFlags();
-    if (properties().getAlpha() < 1) {
-        if (isLayer) {
-            clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
-        }
-        if (CC_LIKELY(isLayer || !properties().getHasOverlappingRendering())) {
-            // simply scale rendering content's alpha
-            canvasState.scaleAlpha(properties().getAlpha());
-        } else {
-            // savelayer needed to create an offscreen buffer
-            Rect layerBounds(0, 0, getWidth(), getHeight());
-            if (clipFlags) {
-                properties().getClippingRectForFlags(clipFlags, &layerBounds);
-                clipFlags = 0; // all clipping done by savelayer
-            }
-            LOG_ALWAYS_FATAL("TODO: savelayer");
-        }
-
-        if (CC_UNLIKELY(ATRACE_ENABLED() && properties().promotedToLayer())) {
-            // pretend alpha always causes savelayer to warn about
-            // performance problem affecting old versions
-            ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", getName(), getWidth(), getHeight());
-        }
-    }
-    if (clipFlags) {
-        Rect clipRect;
-        properties().getClippingRectForFlags(clipFlags, &clipRect);
-        canvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
-                SkRegion::kIntersect_Op);
-    }
-
-    // TODO: support nesting round rect clips
-    if (mProperties.getRevealClip().willClip()) {
-        Rect bounds;
-        mProperties.getRevealClip().getBounds(&bounds);
-        canvasState.setClippingRoundRect(allocator,
-                bounds, mProperties.getRevealClip().getRadius());
-    } else if (mProperties.getOutline().willClip()) {
-        canvasState.setClippingOutline(allocator, &(mProperties.getOutline()));
-    }
-    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
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index bae5ebe..83d1b58 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -187,9 +187,6 @@
 
     AnimatorManager& animators() { return mAnimatorManager; }
 
-    // Returns false if the properties dictate the subtree contained in this RenderNode won't render
-    bool applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) const;
-
     void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
 
     bool nothingToDraw() const {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index ca7789e..3952798 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -608,12 +608,15 @@
                 && getOutline().getAlpha() != 0.0f;
     }
 
-    bool promotedToLayer() const {
+    bool fitsOnLayer() const {
         const DeviceInfo* deviceInfo = DeviceInfo::get();
-        LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized");
+        return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+                        && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+    }
+
+    bool promotedToLayer() const {
         return mLayerProperties.mType == LayerType::None
-                && mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
-                && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize()
+                && fitsOnLayer()
                 && (mComputedFields.mNeedLayerForFunctors
                         || (!MathUtils::isZero(mPrimitiveFields.mAlpha)
                                 && mPrimitiveFields.mAlpha < 1
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index a8f8134..96c1a7c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -131,8 +131,6 @@
             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;
 
@@ -152,7 +150,6 @@
 
     void drawPoints(const float* points, int count, const SkPaint& paint,
                     SkCanvas::PointMode mode);
-    void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
 
     SkAutoTUnref<SkCanvas> mCanvas;
     std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
@@ -240,11 +237,15 @@
     return count;
 }
 
+// The SkiaCanvas::restore operation layers on the capability to preserve
+// either (or both) the matrix and/or clip state after a SkCanvas::restore
+// operation. It does this by explicitly saving off the clip & matrix state
+// when requested and playing it back after the SkCanvas::restore.
 void SkiaCanvas::restore() {
     const SaveRec* rec = (NULL == mSaveStack.get())
             ? NULL
             : static_cast<SaveRec*>(mSaveStack->back());
-    int currentSaveCount = mCanvas->getSaveCount() - 1;
+    int currentSaveCount = mCanvas->getSaveCount();
     SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
 
     if (NULL == rec || rec->saveCount != currentSaveCount) {
@@ -262,8 +263,9 @@
     }
 
     SkTArray<SkClipStack::Element> savedClips;
+    int topClipStackFrame = mCanvas->getClipStack()->getSaveCount();
     if (preserveClip) {
-        saveClipsForFrame(savedClips, currentSaveCount);
+        saveClipsForFrame(savedClips, topClipStackFrame);
     }
 
     mCanvas->restore();
@@ -272,7 +274,11 @@
         mCanvas->setMatrix(savedMatrix);
     }
 
-    if (preserveClip && !savedClips.empty()) {
+    if (preserveClip && !savedClips.empty() &&
+        topClipStackFrame != mCanvas->getClipStack()->getSaveCount()) {
+        // Only reapply the saved clips if the top clip stack frame was actually
+        // popped by restore().  If it wasn't, it means it doesn't belong to the
+        // restored canvas frame (SkCanvas lazy save/restore kicked in).
         applyClips(savedClips);
     }
 
@@ -322,21 +328,23 @@
     }
 
     SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
-    // Store the save counter in the SkClipStack domain.
-    // (0-based, equal to the number of save ops on the stack).
-    rec->saveCount = mCanvas->getSaveCount() - 1;
+    rec->saveCount = mCanvas->getSaveCount();
     rec->saveFlags = flags;
 }
 
-void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount) {
+void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips,
+                                   int saveCountToBackup) {
+    // Each SkClipStack::Element stores the index of the canvas save
+    // with which it is associated. Backup only those Elements that
+    // are associated with 'saveCountToBackup'
     SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
                                    SkClipStack::Iter::kTop_IterStart);
-    while (const SkClipStack::Element* elem = clipIterator.next()) {
-        if (elem->getSaveCount() < frameSaveCount) {
-            // done with the current frame.
+    while (const SkClipStack::Element* elem = clipIterator.prev()) {
+        if (elem->getSaveCount() < saveCountToBackup) {
+            // done with the target save count.
             break;
         }
-        SkASSERT(elem->getSaveCount() == frameSaveCount);
+        SkASSERT(elem->getSaveCount() == saveCountToBackup);
         clips.push_back(*elem);
     }
 }
@@ -701,22 +709,7 @@
 
     static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
     mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
-}
-
-void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount,
-        const SkPaint& paint) {
-    SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
-    int indx;
-    for (indx = 0; indx < posCount; indx++) {
-        posPtr[indx].fX = positions[indx << 1];
-        posPtr[indx].fY = positions[(indx << 1) + 1];
-    }
-
-    SkPaint paintCopy(paint);
-    paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
-    mCanvas->drawPosText(text, count, posPtr, paintCopy);
-
-    delete[] posPtr;
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
 
 void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 0a58f4b..2f535bb 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
     // before resetting, map current light pos with inverse of current transform
     Vector3 center = mRelativeLightCenter;
     mat4 inverse;
@@ -139,6 +142,7 @@
 
     transform = &mTransformRoot;
     transform->loadTranslate(x, y, z);
+#endif
 }
 
 void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index bdce73c..759e39b 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -741,7 +741,7 @@
             // vertex's location.
             int newPenumbraNumber = indexDelta - 1;
 
-            float accumulatedDeltaLength[newPenumbraNumber];
+            float accumulatedDeltaLength[indexDelta];
             float totalDeltaLength = 0;
 
             // To save time, cache the previous umbra vertex info outside the loop
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index b7a76ba..996ac8e 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -30,8 +30,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 hash_t ShadowText::hash() const {
-    uint32_t charCount = len / sizeof(char16_t);
-    uint32_t hash = JenkinsHashMix(0, len);
+    uint32_t hash = JenkinsHashMix(0, glyphCount);
     hash = JenkinsHashMix(hash, android::hash_type(radius));
     hash = JenkinsHashMix(hash, android::hash_type(textSize));
     hash = JenkinsHashMix(hash, android::hash_type(typeface));
@@ -40,10 +39,10 @@
     hash = JenkinsHashMix(hash, android::hash_type(scaleX));
     if (text) {
         hash = JenkinsHashMixShorts(
-            hash, reinterpret_cast<const uint16_t*>(text), charCount);
+            hash, reinterpret_cast<const uint16_t*>(text), glyphCount);
     }
     if (positions) {
-        for (uint32_t i = 0; i < charCount * 2; i++) {
+        for (uint32_t i = 0; i < glyphCount * 2; i++) {
             hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
         }
     }
@@ -51,7 +50,7 @@
 }
 
 int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
-    int deltaInt = int(lhs.len) - int(rhs.len);
+    int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
     if (deltaInt != 0) return deltaInt;
 
     deltaInt = lhs.flags - rhs.flags;
@@ -76,7 +75,7 @@
         if (!lhs.text) return -1;
         if (!rhs.text) return +1;
 
-        deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+        deltaInt = memcmp(lhs.text, rhs.text, lhs.glyphCount * sizeof(glyph_t));
         if (deltaInt != 0) return deltaInt;
     }
 
@@ -84,7 +83,7 @@
         if (!lhs.positions) return -1;
         if (!rhs.positions) return +1;
 
-        return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+        return memcmp(lhs.positions, rhs.positions, lhs.glyphCount << 1);
     }
 
     return 0;
@@ -168,16 +167,16 @@
     mCache.clear();
 }
 
-ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
-        int numGlyphs, float radius, const float* positions) {
-    ShadowText entry(paint, radius, len, text, positions);
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* glyphs, int numGlyphs,
+        float radius, const float* positions) {
+    ShadowText entry(paint, radius, numGlyphs * 2, glyphs, positions);
     ShadowTexture* texture = mCache.get(entry);
 
     if (!texture) {
         SkPaint paintCopy(*paint);
         paintCopy.setTextAlign(SkPaint::kLeft_Align);
-        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0,
-                len, numGlyphs, radius, positions);
+        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
+                radius, positions);
 
         if (!shadow.image) {
             return nullptr;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index caf089f..c4f3c5d 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -34,14 +34,14 @@
 class FontRenderer;
 
 struct ShadowText {
-    ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
+    ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
             flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) {
     }
 
     // len is the number of bytes in text
-    ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
+    ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText,
             const float* positions):
-            len(len), radius(radius), positions(positions) {
+            glyphCount(glyphCount), radius(radius), positions(positions) {
         // TODO: Propagate this through the API, we should not cast here
         text = (const char16_t*) srcText;
 
@@ -73,17 +73,16 @@
     }
 
     void copyTextLocally() {
-        uint32_t charCount = len / sizeof(char16_t);
-        str.setTo((const char16_t*) text, charCount);
+        str.setTo((const char16_t*) text, glyphCount);
         text = str.string();
         if (positions != nullptr) {
             positionsCopy.clear();
-            positionsCopy.appendArray(positions, charCount * 2);
+            positionsCopy.appendArray(positions, glyphCount * 2);
             positions = positionsCopy.array();
         }
     }
 
-    uint32_t len;
+    uint32_t glyphCount;
     float radius;
     float textSize;
     SkTypeface* typeface;
@@ -136,7 +135,7 @@
      */
     void operator()(ShadowText& text, ShadowTexture*& texture) override;
 
-    ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
+    ShadowTexture* get(const SkPaint* paint, const char* text,
             int numGlyphs, float radius, const float* positions);
 
     /**
diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp
new file mode 100644
index 0000000..c9a54ca
--- /dev/null
+++ b/libs/hwui/VectorDrawablePath.cpp
@@ -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.
+ */
+
+#include "VectorDrawablePath.h"
+
+#include "PathParser.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <math.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+
+VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) {
+    PathParser::ParseResult result;
+    PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
+    if (!result.failureOccurred) {
+        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+    }
+}
+
+VectorDrawablePath::VectorDrawablePath(const PathData& data) {
+    mData = data;
+    // Now we need to construct a path
+    VectorDrawableUtils::verbsToPath(&mSkPath, data);
+}
+
+VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) {
+    mData = path.mData;
+    VectorDrawableUtils::verbsToPath(&mSkPath, mData);
+}
+
+
+bool VectorDrawablePath::canMorph(const PathData& morphTo) {
+    return VectorDrawableUtils::canMorph(mData, morphTo);
+}
+
+bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) {
+    return canMorph(path.mData);
+}
+
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/VectorDrawablePath.h b/libs/hwui/VectorDrawablePath.h
new file mode 100644
index 0000000..2e56349
--- /dev/null
+++ b/libs/hwui/VectorDrawablePath.h
@@ -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.
+ */
+
+#ifndef ANDROID_HWUI_VPATH_H
+#define ANDROID_HWUI_VPATH_H
+
+#include <cutils/compiler.h>
+#include "SkPath.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+struct ANDROID_API PathData {
+    // TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance
+    // difference.
+    std::vector<char> verbs;
+    std::vector<size_t> verbSizes;
+    std::vector<float> points;
+    bool operator== (const PathData& data) const {
+        return verbs == data.verbs && verbSizes == data.verbSizes && points == data.points;
+    }
+
+};
+
+class VectorDrawablePath {
+public:
+    VectorDrawablePath(const PathData& nodes);
+    VectorDrawablePath(const VectorDrawablePath& path);
+    VectorDrawablePath(const char* path, size_t strLength);
+    bool canMorph(const PathData& path);
+    bool canMorph(const VectorDrawablePath& path);
+
+private:
+    PathData mData;
+    SkPath mSkPath;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // ANDROID_HWUI_VPATH_H
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index d680f99..dc82041 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -291,20 +291,18 @@
     return cachedGlyph;
 }
 
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char *text,
             int numGlyphs, int x, int y, const float* positions) {
-    render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr,
+    render(paint, text, numGlyphs, x, y, FRAMEBUFFER, nullptr,
             0, 0, nullptr, positions);
 }
 
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
-        int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
-    if (numGlyphs == 0 || text == nullptr || len == 0) {
+void Font::render(const SkPaint* paint, const char *text, int numGlyphs,
+        const SkPath* path, float hOffset, float vOffset) {
+    if (numGlyphs == 0 || text == nullptr) {
         return;
     }
 
-    text += start;
-
     int glyphsCount = 0;
     SkFixed prevRsbDelta = 0;
 
@@ -317,7 +315,7 @@
     float pathLength = SkScalarToFloat(measure.getLength());
 
     if (paint->getTextAlign() != SkPaint::kLeft_Align) {
-        float textWidth = SkScalarToFloat(paint->measureText(text, len));
+        float textWidth = SkScalarToFloat(paint->measureText(text, numGlyphs * 2));
         float pathOffset = pathLength;
         if (paint->getTextAlign() == SkPaint::kCenter_Align) {
             textWidth *= 0.5f;
@@ -347,14 +345,14 @@
     }
 }
 
-void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const char* text,
         int numGlyphs, Rect *bounds, const float* positions) {
     if (bounds == nullptr) {
         ALOGE("No return rectangle provided to measure text");
         return;
     }
     bounds->set(1e6, -1e6, -1e6, 1e6);
-    render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
+    render(paint, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
 }
 
 void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
@@ -378,10 +376,10 @@
     }
 }
 
-void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char* text,
         int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
         uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
-    if (numGlyphs == 0 || text == nullptr || len == 0) {
+    if (numGlyphs == 0 || text == nullptr) {
         return;
     }
 
@@ -395,7 +393,6 @@
     };
     RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
 
-    text += start;
     int glyphsCount = 0;
 
     while (glyphsCount < numGlyphs) {
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 3119d73..59518a1 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -82,10 +82,10 @@
 
     ~Font();
 
-    void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char* text,
             int numGlyphs, int x, int y, const float* positions);
 
-    void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char* text,
             int numGlyphs, const SkPath* path, float hOffset, float vOffset);
 
     const Font::FontDescription& getDescription() const {
@@ -113,11 +113,11 @@
 
     void precache(const SkPaint* paint, const char* text, int numGlyphs);
 
-    void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char *text,
             int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
             uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
 
-    void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void measure(const SkPaint* paint, const char* text,
             int numGlyphs, Rect *bounds, const float* positions);
 
     void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index 7a62037..4be1f99 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -23,7 +23,7 @@
 #include "DisplayListCanvas.h"
 #endif
 #include "microbench/MicroBench.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 7b8d0e5..eea0c7f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -21,7 +21,7 @@
 #include "OpReorderer.h"
 #include "RecordedOp.h"
 #include "RecordingCanvas.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 #include "microbench/MicroBench.h"
 
 #include <vector>
@@ -48,7 +48,7 @@
 void BM_OpReorderer_defer::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; i++) {
-        OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+        OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
         MicroBench::DoNotOptimize(&reorderer);
     }
     StopBenchmarkTiming();
@@ -59,11 +59,13 @@
     TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
+        BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
+
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
-            OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+            OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
 
-            BakedOpRenderer renderer(caches, renderState, true);
+            BakedOpRenderer renderer(caches, renderState, true, lightInfo);
             reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
             MicroBench::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp
new file mode 100644
index 0000000..3d9fafa
--- /dev/null
+++ b/libs/hwui/microbench/PathParserBench.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 <benchmark/Benchmark.h>
+
+#include "PathParser.h"
+#include "VectorDrawablePath.h"
+
+#include <SkPath.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath);
+void BM_PathParser_parseStringPathForSkPath::Run(int iter) {
+    SkPath skPath;
+    size_t length = strlen(sPathString);
+    PathParser::ParseResult result;
+    StartBenchmarkTiming();
+    for (int i = 0; i < iter; i++) {
+        PathParser::parseStringForSkPath(&skPath, &result, sPathString, length);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData);
+void BM_PathParser_parseStringPathForPathData::Run(int iter) {
+    size_t length = strlen(sPathString);
+    PathData outData;
+    PathParser::ParseResult result;
+    StartBenchmarkTiming();
+    for (int i = 0; i < iter; i++) {
+        PathParser::getPathDataFromString(&outData, &result, sPathString, length);
+    }
+    StopBenchmarkTiming();
+}
diff --git a/libs/hwui/microbench/how_to_run.txt b/libs/hwui/microbench/how_to_run.txt
new file mode 100755
index 0000000..e6f80b2
--- /dev/null
+++ b/libs/hwui/microbench/how_to_run.txt
@@ -0,0 +1,4 @@
+mmm -j8 frameworks/base/libs/hwui &&
+adb push $ANDROID_PRODUCT_OUT/data/local/tmp/hwuimicro \
+    /data/local/tmp/hwuimicro &&
+    adb shell /data/local/tmp/hwuimicro
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
new file mode 100644
index 0000000..6b44557
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "OffscreenBufferPool.h"
+
+#include "Caches.h"
+#include "Properties.h"
+#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
+
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+        uint32_t viewportWidth, uint32_t viewportHeight)
+        : renderState(renderState)
+        , viewportWidth(viewportWidth)
+        , viewportHeight(viewportHeight)
+        , texture(caches) {
+    texture.width = computeIdealDimension(viewportWidth);
+    texture.height = computeIdealDimension(viewportHeight);
+    texture.blend = true;
+
+    caches.textureState().activateTexture(0);
+    glGenTextures(1, &texture.id);
+    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+    // not setting filter on texture, since it's set when drawing, based on transform
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+}
+
+void OffscreenBuffer::updateMeshFromRegion() {
+    // avoid T-junctions as they cause artifacts in between the resultant
+    // geometry when complex transforms occur.
+    // TODO: generate the safeRegion only if necessary based on drawing transform
+    Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+    size_t count;
+    const android::Rect* rects = safeRegion.getArray(&count);
+
+    const float texX = 1.0f / float(texture.width);
+    const float texY = 1.0f / float(texture.height);
+
+    FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+    TextureVertex* mesh = &meshVector[0];
+    for (size_t i = 0; i < count; i++) {
+        const android::Rect* r = &rects[i];
+
+        const float u1 = r->left * texX;
+        const float v1 = (viewportHeight - r->top) * texY;
+        const float u2 = r->right * texX;
+        const float v2 = (viewportHeight - r->bottom) * texY;
+
+        TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+        TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+        TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+        TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+    }
+    elementCount = count * 6;
+    renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+            sizeof(TextureVertex) * count * 4,
+            &meshVector[0],
+            GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) {
+    return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+    texture.deleteTexture();
+    renderState.meshState().deleteMeshBuffer(vbo);
+    elementCount = 0;
+    vbo = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OffscreenBufferPool
+///////////////////////////////////////////////////////////////////////////////
+
+OffscreenBufferPool::OffscreenBufferPool()
+    : mMaxSize(Properties::layerPoolSize) {
+}
+
+OffscreenBufferPool::~OffscreenBufferPool() {
+    clear(); // TODO: unique_ptr?
+}
+
+int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
+    int deltaInt = int(lhs.width) - int(rhs.width);
+    if (deltaInt != 0) return deltaInt;
+
+    return int(lhs.height) - int(rhs.height);
+}
+
+void OffscreenBufferPool::clear() {
+    for (auto entry : mPool) {
+        delete entry.layer;
+    }
+    mPool.clear();
+    mSize = 0;
+}
+
+OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
+        const uint32_t width, const uint32_t height) {
+    OffscreenBuffer* layer = nullptr;
+
+    Entry entry(width, height);
+    auto iter = mPool.find(entry);
+
+    if (iter != mPool.end()) {
+        entry = *iter;
+        mPool.erase(iter);
+
+        layer = entry.layer;
+        layer->viewportWidth = width;
+        layer->viewportHeight = height;
+        mSize -= layer->getSizeInBytes();
+    } else {
+        layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+    }
+
+    return layer;
+}
+
+OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
+        const uint32_t width, const uint32_t height) {
+    RenderState& renderState = layer->renderState;
+    if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
+            && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+        // resize in place
+        layer->viewportWidth = width;
+        layer->viewportHeight = height;
+        return layer;
+    }
+    putOrDelete(layer);
+    return get(renderState, width, height);
+}
+
+void OffscreenBufferPool::dump() {
+    for (auto entry : mPool) {
+        ALOGD("  Layer size %dx%d", entry.width, entry.height);
+    }
+}
+
+void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) {
+    const uint32_t size = layer->getSizeInBytes();
+    // Don't even try to cache a layer that's bigger than the cache
+    if (size < mMaxSize) {
+        // TODO: Use an LRU
+        while (mSize + size > mMaxSize) {
+            OffscreenBuffer* victim = mPool.begin()->layer;
+            mSize -= victim->getSizeInBytes();
+            delete victim;
+            mPool.erase(mPool.begin());
+        }
+
+        // clear region, since it's no longer valid
+        layer->region.clear();
+
+        Entry entry(layer);
+
+        mPool.insert(entry);
+        mSize += size;
+    } else {
+        delete layer;
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
new file mode 100644
index 0000000..fac6c35
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_OFFSCREEN_BUFFER_POOL_H
+#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/Macros.h"
+
+#include <ui/Region.h>
+
+#include <set>
+
+namespace android {
+namespace uirenderer {
+
+class RenderState;
+
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
+ */
+class OffscreenBuffer {
+public:
+    OffscreenBuffer(RenderState& renderState, Caches& caches,
+            uint32_t viewportWidth, uint32_t viewportHeight);
+    ~OffscreenBuffer();
+
+    // must be called prior to rendering, to construct/update vertex buffer
+    void updateMeshFromRegion();
+
+    // Set by RenderNode for HW layers, TODO for clipped saveLayers
+    void setWindowTransform(const Matrix4& transform) {
+        inverseTransformInWindow.loadInverse(transform);
+    }
+
+    static uint32_t computeIdealDimension(uint32_t dimension);
+
+    uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+
+    RenderState& renderState;
+
+    uint32_t viewportWidth;
+    uint32_t viewportHeight;
+    Texture texture;
+
+    // Portion of layer that has been drawn to. Used to minimize drawing area when
+    // drawing back to screen / parent FBO.
+    Region region;
+
+    Matrix4 inverseTransformInWindow;
+
+    // vbo / size of mesh
+    GLsizei elementCount = 0;
+    GLuint vbo = 0;
+};
+
+/**
+ * Pool of OffscreenBuffers allocated, but not currently in use.
+ */
+class OffscreenBufferPool {
+public:
+    OffscreenBufferPool();
+    ~OffscreenBufferPool();
+
+    WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
+            const uint32_t width, const uint32_t height);
+
+    WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
+            const uint32_t width, const uint32_t height);
+
+    void putOrDelete(OffscreenBuffer* layer);
+
+    /**
+     * Clears the pool. This causes all layers to be deleted.
+     */
+    void clear();
+
+    /**
+     * Returns the maximum size of the pool in bytes.
+     */
+    uint32_t getMaxSize() { return mMaxSize; }
+
+    /**
+     * Returns the current size of the pool in bytes.
+     */
+    uint32_t getSize() { return mSize; }
+
+    size_t getCount() { return mPool.size(); }
+
+    /**
+     * Prints out the content of the pool.
+     */
+    void dump();
+private:
+    struct Entry {
+        Entry() {}
+
+        Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+                : width(OffscreenBuffer::computeIdealDimension(layerWidth))
+                , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+
+        Entry(OffscreenBuffer* layer)
+                : layer(layer)
+                , width(layer->texture.width)
+                , height(layer->texture.height) {
+        }
+
+        static int compare(const Entry& lhs, const Entry& rhs);
+
+        bool operator==(const Entry& other) const {
+            return compare(*this, other) == 0;
+        }
+
+        bool operator!=(const Entry& other) const {
+            return compare(*this, other) != 0;
+        }
+
+        bool operator<(const Entry& other) const {
+            return Entry::compare(*this, other) < 0;
+        }
+
+        OffscreenBuffer* layer = nullptr;
+        uint32_t width = 0;
+        uint32_t height = 0;
+    }; // struct Entry
+
+    std::multiset<Entry> mPool;
+
+    uint32_t mSize = 0;
+    uint32_t mMaxSize;
+}; // class OffscreenBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 9637117..4fa8200 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -90,6 +90,8 @@
     }
 */
 
+    mLayerPool.clear();
+
     // TODO: reset all cached state in state objects
     std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
     mAssetAtlas.terminate();
@@ -106,6 +108,19 @@
     mStencil = nullptr;
 }
 
+void RenderState::flush(Caches::FlushMode mode) {
+    switch (mode) {
+        case Caches::FlushMode::Full:
+            // fall through
+        case Caches::FlushMode::Moderate:
+            // fall through
+        case Caches::FlushMode::Layers:
+            mLayerPool.clear();
+            break;
+    }
+    mCaches->flush(mode);
+}
+
 void RenderState::setViewport(GLsizei width, GLsizei height) {
     mViewportWidth = width;
     mViewportHeight = height;
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 3cda170..dcd5ea6 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -21,6 +21,7 @@
 #include "Glop.h"
 #include "renderstate/Blend.h"
 #include "renderstate/MeshState.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/PixelBufferState.h"
 #include "renderstate/Scissor.h"
 #include "renderstate/Stencil.h"
@@ -56,6 +57,8 @@
     void onGLContextCreated();
     void onGLContextDestroyed();
 
+    void flush(Caches::FlushMode flushMode);
+
     void setViewport(GLsizei width, GLsizei height);
     void getViewport(GLsizei* outWidth, GLsizei* outHeight);
 
@@ -97,6 +100,8 @@
     Scissor& scissor() { return *mScissor; }
     Stencil& stencil() { return *mStencil; }
 
+    OffscreenBufferPool& layerPool() { return mLayerPool; }
+
     void dump();
 
 private:
@@ -116,6 +121,8 @@
     Scissor* mScissor = nullptr;
     Stencil* mStencil = nullptr;
 
+    OffscreenBufferPool mLayerPool;
+
     AssetAtlas mAssetAtlas;
     std::set<Layer*> mActiveLayers;
     std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b89960..89cadea 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
 #include "utils/TimeUtils.h"
 
 #if HWUI_NEW_OPS
-#include "BakedOpRenderer.h"
 #include "OpReorderer.h"
 #endif
 
@@ -150,13 +149,23 @@
 // TODO: don't pass viewport size, it's automatic via EGL
 void CanvasContext::setup(int width, int height, float lightRadius,
         uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+    mLightInfo.lightRadius = lightRadius;
+    mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+    mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
     if (!mCanvas) return;
     mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
 }
 
 void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+    mLightCenter = lightCenter;
+#else
     if (!mCanvas) return;
     mCanvas->setLightCenter(lightCenter);
+#endif
 }
 
 void CanvasContext::setOpaque(bool opaque) {
@@ -340,9 +349,11 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+            mRenderNodes, mLightCenter);
     mLayerUpdateQueue.clear();
-    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
+    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
+            mOpaque, mLightInfo);
     // TODO: profiler().draw(mCanvas);
     reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
 
@@ -576,8 +587,12 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+#else
     mCanvas->markLayersAsBuildLayers();
     mCanvas->flushLayerUpdates();
+#endif
 
     node->incStrong(nullptr);
     mPrefetechedLayers.insert(node);
@@ -599,7 +614,7 @@
         // Make sure to release all the textures we were owning as there won't
         // be another draw
         caches.textureCache.resetMarkInUse(this);
-        caches.flush(Caches::FlushMode::Layers);
+        mRenderThread.renderState().flush(Caches::FlushMode::Layers);
     }
 }
 
@@ -609,10 +624,10 @@
 
     ATRACE_CALL();
     if (level >= TRIM_MEMORY_COMPLETE) {
-        Caches::getInstance().flush(Caches::FlushMode::Full);
+        thread.renderState().flush(Caches::FlushMode::Full);
         thread.eglManager().destroy();
     } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
-        Caches::getInstance().flush(Caches::FlushMode::Moderate);
+        thread.renderState().flush(Caches::FlushMode::Moderate);
     }
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d656014..c3cfc94 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -27,6 +27,10 @@
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#endif
+
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
 #include <SkBitmap.h>
@@ -165,6 +169,11 @@
 
     bool mOpaque;
     OpenGLRenderer* mCanvas = nullptr;
+#if HWUI_NEW_OPS
+    BakedOpRenderer::LightInfo mLightInfo;
+    Vector3 mLightCenter = { 0, 0, 0 };
+#endif
+
     bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
     LayerUpdateQueue mLayerUpdateQueue;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a1107f0..15ccd6a 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -563,7 +563,10 @@
 void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
     void* retval;
     task->setReturnPtr(&retval);
-    mRenderThread.queueAndWait(task);
+    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+    AutoMutex _lock(mSyncMutex);
+    mRenderThread.queue(&syncTask);
+    mSyncCondition.wait(mSyncMutex);
     return retval;
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index d0e601e..338fab6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -117,6 +117,9 @@
 
     DrawFrameTask mDrawFrameTask;
 
+    Mutex mSyncMutex;
+    Condition mSyncCondition;
+
     void destroyContext();
 
     void post(RenderTask* task);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 526a848..9fb30c9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -25,7 +25,9 @@
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <sys/resource.h>
+#include <utils/Condition.h>
 #include <utils/Log.h>
+#include <utils/Mutex.h>
 
 namespace android {
 namespace uirenderer {
@@ -325,10 +327,16 @@
 }
 
 void RenderThread::queueAndWait(RenderTask* task) {
-    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
-    AutoMutex _lock(mSyncMutex);
+    // These need to be local to the thread to avoid the Condition
+    // signaling the wrong thread. The easiest way to achieve that is to just
+    // make this on the stack, although that has a slight cost to it
+    Mutex mutex;
+    Condition condition;
+    SignalingRenderTask syncTask(task, &mutex, &condition);
+
+    AutoMutex _lock(mutex);
     queue(&syncTask);
-    mSyncCondition.wait(mSyncMutex);
+    condition.wait(mutex);
 }
 
 void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d8c7e61..076e3d4 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -27,9 +27,7 @@
 #include <utils/Looper.h>
 #include <utils/Thread.h>
 
-#include <condition_variable>
 #include <memory>
-#include <mutex>
 #include <set>
 
 namespace android {
@@ -127,8 +125,6 @@
 
     nsecs_t mNextWakeup;
     TaskQueue mQueue;
-    Mutex mSyncMutex;
-    Condition mSyncCondition;
 
     DisplayInfo mDisplayInfo;
 
diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h
index e16310e..3f87d7f 100644
--- a/libs/hwui/tests/Benchmark.h
+++ b/libs/hwui/tests/Benchmark.h
@@ -16,6 +16,8 @@
 #ifndef TESTS_BENCHMARK_H
 #define TESTS_BENCHMARK_H
 
+#include "TestScene.h"
+
 #include <string>
 #include <vector>
 
@@ -26,12 +28,17 @@
     int count;
 };
 
-typedef void (*BenchmarkFunctor)(const BenchmarkOptions&);
+typedef test::TestScene* (*CreateScene)(const BenchmarkOptions&);
+
+template <class T>
+test::TestScene* simpleCreateScene(const BenchmarkOptions&) {
+    return new T();
+}
 
 struct BenchmarkInfo {
     std::string name;
     std::string description;
-    BenchmarkFunctor functor;
+    CreateScene createScene;
 };
 
 class Benchmark {
diff --git a/libs/hwui/tests/TestScene.h b/libs/hwui/tests/TestScene.h
new file mode 100644
index 0000000..b5d8954
--- /dev/null
+++ b/libs/hwui/tests/TestScene.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_TESTSCENE_H
+#define TESTS_TESTSCENE_H
+
+namespace android {
+namespace uirenderer {
+class RenderNode;
+
+#if HWUI_NEW_OPS
+class RecordingCanvas;
+typedef RecordingCanvas TestCanvas;
+#else
+class DisplayListCanvas;
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+namespace test {
+
+class TestScene {
+public:
+    virtual ~TestScene() {}
+    virtual void createContent(int width, int height, TestCanvas& renderer) = 0;
+    virtual void doFrame(int frameNr) = 0;
+};
+
+} // namespace test
+} // namespace uirenderer
+} // namespace android
+
+#endif /* TESTS_TESTSCENE_H */
diff --git a/libs/hwui/tests/TestSceneRunner.cpp b/libs/hwui/tests/TestSceneRunner.cpp
new file mode 100644
index 0000000..0376e10
--- /dev/null
+++ b/libs/hwui/tests/TestSceneRunner.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 "AnimationContext.h"
+#include "Benchmark.h"
+#include "RenderNode.h"
+#include "TestContext.h"
+#include "scenes/TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "renderthread/RenderTask.h"
+
+#include <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+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);
+    }
+};
+
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts) {
+    // Switch to the real display
+    gDisplay = getBuiltInDisplay();
+
+    std::unique_ptr<TestScene> scene(info.createScene(opts));
+
+    TestContext testContext;
+
+    // create the native surface
+    const int width = gDisplay.w;
+    const int height = gDisplay.h;
+    sp<Surface> surface = testContext.surface();
+
+    sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height,
+            [&scene, width, height](RenderProperties& props, TestCanvas& canvas) {
+        props.setClipToBounds(false);
+        scene->createContent(width, height, canvas);
+    });
+
+    ContextFactory factory;
+    std::unique_ptr<RenderProxy> proxy(new RenderProxy(false,
+            rootNode.get(), &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)});
+
+    // 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();
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        proxy->syncAndDrawFrame();
+    }
+    proxy->resetProfileInfo();
+
+    for (int i = 0; i < opts.count; i++) {
+        testContext.waitForVsync();
+
+        ATRACE_NAME("UI-Draw Frame");
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        scene->doFrame(i);
+        proxy->syncAndDrawFrame();
+    }
+
+    proxy->dumpProfileInfo(STDOUT_FILENO, 0);
+}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
deleted file mode 100644
index 29d9803..0000000
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ /dev/null
@@ -1,428 +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 <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 <unit_tests/TestUtils.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>
-});
-
-
-class HwLayerAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
-        canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
-    }, true);
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-        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 _HwLayer(BenchmarkInfo{
-    "hwlayer",
-    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
-    "Tests the hardware layer codepath.",
-    TreeContentAnimation::run<HwLayerAnimation>
-});
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index aee84de..48566e8 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -43,6 +43,8 @@
 static int gRepeatCount = 1;
 static std::vector<BenchmarkInfo> gRunTests;
 
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts);
+
 static void printHelp() {
     printf("\
 USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
@@ -186,7 +188,7 @@
     opts.count = gFrameCount;
     for (int i = 0; i < gRepeatCount; i++) {
         for (auto&& test : gRunTests) {
-            test.functor(opts);
+            run(test, opts);
         }
     }
     printf("Success!\n");
diff --git a/libs/hwui/tests/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
new file mode 100644
index 0000000..e316eca
--- /dev/null
+++ b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
@@ -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.
+ */
+
+#include "TestSceneBase.h"
+
+class HwLayerAnimation;
+
+static Benchmark _HwLayer(BenchmarkInfo{
+    "hwlayer",
+    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+    "Tests the hardware layer codepath.",
+    simpleCreateScene<HwLayerAnimation>
+});
+
+class HwLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](RenderProperties& props, TestCanvas& canvas) {
+            props.mutateLayerProperties().setType(LayerType::RenderLayer);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+        });
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+        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);
+    }
+};
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
new file mode 100644
index 0000000..919a53d
--- /dev/null
+++ b/libs/hwui/tests/scenes/OvalAnimation.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 "TestSceneBase.h"
+
+class OvalAnimation;
+
+static Benchmark _Oval(BenchmarkInfo{
+    "oval",
+    "Draws 1 oval.",
+    simpleCreateScene<OvalAnimation>
+});
+
+class OvalAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(0, 0, 200, 200, [](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);
+    }
+};
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
new file mode 100644
index 0000000..0fba4eb
--- /dev/null
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -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.
+ */
+
+#include "TestSceneBase.h"
+
+class PartialDamageAnimation;
+
+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.",
+    simpleCreateScene<PartialDamageAnimation>
+});
+
+class PartialDamageAnimation : public TestScene {
+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)) {
+                SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
+                sp<RenderNode> card = TestUtils::createNode(x, y,
+                        x + dp(100), y + dp(100),
+                        [color](TestCanvas& canvas) {
+                    canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+                });
+                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);
+
+        TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+            SkColor color = TestUtils::interpolateColor(
+                    curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
+            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/RecentsAnimation.cpp b/libs/hwui/tests/scenes/RecentsAnimation.cpp
new file mode 100644
index 0000000..1e38d84
--- /dev/null
+++ b/libs/hwui/tests/scenes/RecentsAnimation.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 "TestSceneBase.h"
+
+class RecentsAnimation;
+
+static Benchmark _Recents(BenchmarkInfo{
+    "recents",
+    "A recents-like scrolling list of textures. "
+    "Consists of updating a texture every frame",
+    simpleCreateScene<RecentsAnimation>
+});
+
+class RecentsAnimation : public TestScene {
+public:
+    void createContent(int width, int height, TestCanvas& renderer) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        thumbnailSize = std::min(std::min(width, height) / 2, 720);
+        int cardsize = std::min(width, height) - dp(64);
+
+        renderer.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        renderer.insertReorderBarrier(true);
+
+        int x = dp(32);
+        for (int i = 0; i < 4; i++) {
+            int y = (height / 4) * i;
+            SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
+            thumb.eraseColor(COLORS[i]);
+            sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+            card->mutateStagingProperties().setElevation(i * dp(8));
+            renderer.drawRenderNode(card.get());
+            mThumbnail = thumb;
+            mCards.push_back(card);
+        }
+
+        renderer.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < mCards.size(); ci++) {
+            mCards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            mCards[ci]->setPropertyFieldsDirty(RenderNode::Y);
+        }
+        mThumbnail.eraseColor(TestUtils::interpolateColor(
+                curFrame / 150.0f, 0xFF4CAF50, 0xFFFF5722));
+    }
+
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height,
+            const SkBitmap& thumb) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+            props.mutableOutline().setShouldClip(true);
+
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+            canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
+                    0, 0, width, height, nullptr);
+        });
+    }
+
+    SkBitmap mThumbnail;
+    std::vector< sp<RenderNode> > mCards;
+    int thumbnailSize;
+};
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
new file mode 100644
index 0000000..254f828
--- /dev/null
+++ b/libs/hwui/tests/scenes/RectGridAnimation.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 "TestSceneBase.h"
+
+class RectGridAnimation;
+
+static Benchmark _RectGrid(BenchmarkInfo{
+    "rectgrid",
+    "A dense grid of 1x1 rects that should visually look like a single rect. "
+    "Low CPU/GPU load.",
+    simpleCreateScene<RectGridAnimation>
+});
+
+class RectGridAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(50, 50, 250, 250,
+                [](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);
+    }
+};
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
new file mode 100644
index 0000000..c62dd19
--- /dev/null
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -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.
+ */
+
+#include "TestSceneBase.h"
+
+class SaveLayerAnimation;
+
+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.",
+    simpleCreateScene<SaveLayerAnimation>
+});
+
+class SaveLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](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);
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
new file mode 100644
index 0000000..26c86aa
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "TestSceneBase.h"
+
+class ShadowGrid2Animation;
+
+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.",
+    simpleCreateScene<ShadowGrid2Animation>
+});
+
+class ShadowGrid2Animation : public TestScene {
+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) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
new file mode 100644
index 0000000..ee3c590
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "TestSceneBase.h"
+
+class ShadowGridAnimation;
+
+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.",
+    simpleCreateScene<ShadowGridAnimation>
+});
+
+class ShadowGridAnimation : public TestScene {
+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) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/TestSceneBase.h b/libs/hwui/tests/scenes/TestSceneBase.h
new file mode 100644
index 0000000..a208509
--- /dev/null
+++ b/libs/hwui/tests/scenes/TestSceneBase.h
@@ -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.
+ */
+#ifndef TESTS_SCENES_TESTSCENEBASE_H
+#define TESTS_SCENES_TESTSCENEBASE_H
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "tests/Benchmark.h"
+#include "tests/TestContext.h"
+#include "tests/TestScene.h"
+#include "utils/TestUtils.h"
+
+#include <functional>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index bc1b69f..7ad2f9b 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -18,7 +18,7 @@
 
 #include <BakedOpState.h>
 #include <RecordedOp.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
@@ -60,24 +60,21 @@
 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));
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::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));
+        RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(0, 0, 100, 200), &paint);
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::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
@@ -85,5 +82,24 @@
     }
 }
 
+TEST(BakedOpState, oplessConstructAndReject) {
+    LinearAllocator allocator;
+    {
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 0, 0)); // empty
+        BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+        EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
+        EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
+    }
+    {
+        auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200));
+        BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+        EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
+        EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
+        EXPECT_EQ((ShadowOp*)0x1234, bakedOp->op);
+    }
+}
+
 }
 }
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index 3ef329a..c6ccf4d 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/FatVector.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index ef205ec..05fd08a 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -19,7 +19,7 @@
 #include <LayerUpdateQueue.h>
 #include <RenderNode.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 0f6b249..0591db6 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/LinearAllocator.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
new file mode 100644
index 0000000..de86aed
--- /dev/null
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <renderstate/OffscreenBufferPool.h>
+
+#include <utils/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OffscreenBuffer, computeIdealDimension) {
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
+    EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
+    EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
+}
+
+TEST(OffscreenBuffer, construct) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBuffer layer(thread.renderState(), Caches::getInstance(), 49u, 149u);
+        EXPECT_EQ(49u, layer.viewportWidth);
+        EXPECT_EQ(149u, layer.viewportHeight);
+
+        EXPECT_EQ(64u, layer.texture.width);
+        EXPECT_EQ(192u, layer.texture.height);
+
+        EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
+    });
+}
+
+TEST(OffscreenBufferPool, construct) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+        EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
+        EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
+        EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
+                << "pool must read size from Properties";
+    });
+
+}
+
+TEST(OffscreenBufferPool, getPutClear) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+
+        auto layer = pool.get(thread.renderState(), 100u, 200u);
+        EXPECT_EQ(100u, layer->viewportWidth);
+        EXPECT_EQ(200u, layer->viewportHeight);
+
+        ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+        pool.putOrDelete(layer);
+        ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+        auto layer2 = pool.get(thread.renderState(), 102u, 202u);
+        EXPECT_EQ(layer, layer2) << "layer should be recycled";
+        ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+        pool.putOrDelete(layer);
+        EXPECT_EQ(1u, pool.getCount());
+        pool.clear();
+        EXPECT_EQ(0u, pool.getSize());
+        EXPECT_EQ(0u, pool.getCount());
+    });
+}
+
+TEST(OffscreenBufferPool, resize) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+
+        auto layer = pool.get(thread.renderState(), 64u, 64u);
+
+        // resize in place
+        ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+        EXPECT_EQ(60u, layer->viewportWidth);
+        EXPECT_EQ(55u, layer->viewportHeight);
+        EXPECT_EQ(64u, layer->texture.width);
+        EXPECT_EQ(64u, layer->texture.height);
+
+        // resized to use different object in pool
+        auto layer2 = pool.get(thread.renderState(), 128u, 128u);
+        pool.putOrDelete(layer2);
+        ASSERT_EQ(1u, pool.getCount());
+        ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+        EXPECT_EQ(120u, layer2->viewportWidth);
+        EXPECT_EQ(125u, layer2->viewportHeight);
+        EXPECT_EQ(128u, layer2->texture.width);
+        EXPECT_EQ(128u, layer2->texture.height);
+
+        // original allocation now only thing in pool
+        EXPECT_EQ(1u, pool.getCount());
+        EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
+    });
+}
+
+TEST(OffscreenBufferPool, putAndDestroy) {
+    TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+        OffscreenBufferPool pool;
+        // layer too big to return to the pool
+        // Note: this relies on the fact that the pool won't reject based on max texture size
+        auto hugeLayer = pool.get(thread.renderState(), pool.getMaxSize() / 64, 64);
+        EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
+        pool.putOrDelete(hugeLayer);
+        EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
+    });
+}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index f67c24a..d76086c 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -17,18 +17,26 @@
 #include <gtest/gtest.h>
 
 #include <BakedOpState.h>
+#include <LayerUpdateQueue.h>
 #include <OpReorderer.h>
 #include <RecordedOp.h>
 #include <RecordingCanvas.h>
-#include <renderthread/CanvasContext.h> // todo: remove
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 #include <unordered_map>
 
 namespace android {
 namespace uirenderer {
 
-LayerUpdateQueue sEmptyLayerUpdateQueue;
+const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const Vector3 sLightCenter = {100, 100, 100};
+
+static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    std::vector<sp<RenderNode>> vec;
+    vec.emplace_back(node);
+    return vec;
+}
 
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
@@ -38,23 +46,23 @@
  * and allows Renderer vs Dispatching behavior to be merged.
  *
  * onXXXOp methods fail by default - tests should override ops they expect
- * startLayer fails by default - tests should override if expected
+ * startRepaintLayer fails by default - tests should override if expected
  * startFrame/endFrame do nothing by default - tests should override to intercept
  */
 class TestRendererBase {
 public:
     virtual ~TestRendererBase() {}
-    virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+    virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
         ADD_FAILURE() << "Layer creation not expected in this test";
         return nullptr;
     }
-    virtual void startLayer(OffscreenBuffer*) {
+    virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
         ADD_FAILURE() << "Layer repaint not expected in this test";
     }
     virtual void endLayer() {
         ADD_FAILURE() << "Layer updates not expected in this test";
     }
-    virtual void startFrame(uint32_t width, uint32_t height) {}
+    virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
     virtual void endFrame() {}
 
     // define virtual defaults for direct
@@ -71,7 +79,7 @@
 
 /**
  * Dispatches all static methods to similar formed methods on renderer, which fail by default but
- * are overriden by subclasses per test.
+ * are overridden by subclasses per test.
  */
 class TestDispatcher {
 public:
@@ -82,40 +90,38 @@
     MAP_OPS(DISPATCHER_METHOD);
 };
 
-
 class FailRenderer : public TestRendererBase {};
 
-class SimpleTestRenderer : public TestRendererBase {
-public:
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(100u, width);
-        EXPECT_EQ(200u, height);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
-    }
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(3, mIndex++);
-    }
-};
 TEST(OpReorderer, simple) {
+    class SimpleTestRenderer : public TestRendererBase {
+    public:
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(100u, width);
+            EXPECT_EQ(200u, height);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+    };
+
     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);
-
+    OpReorderer reorderer(100, 200, *dl, sLightCenter);
     SimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
 }
 
-
 TEST(OpReorderer, simpleRejection) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -123,31 +129,31 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-
-static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingTestRenderer : public TestRendererBase {
-public:
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
-    }
-};
 TEST(OpReorderer, simpleBatching) {
+    const int LOOPS = 5;
+    class SimpleBatchingTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
+        }
+    };
+
     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++) {
+        for (int i = 0; i < LOOPS; i++) {
             canvas.translate(0, 10);
             canvas.drawRect(0, 0, 10, 10, SkPaint());
             canvas.drawBitmap(bitmap, 5, 0, nullptr);
@@ -155,39 +161,67 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
-
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
     SimpleBatchingTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
-    EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
+    EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+            << "Expect number of ops = 2 * loop count"; // TODO: force no merging
 }
 
-class RenderNodeTestRenderer : public TestRendererBase {
-public:
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        switch(mIndex++) {
-        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, textStrikethroughBatching) {
+    const int LOOPS = 5;
+    class TextStrikethroughTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
         }
-    }
-};
+        void onTextOp(const TextOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
+        }
+    };
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+        SkPaint textPaint;
+        textPaint.setAntiAlias(true);
+        textPaint.setTextSize(20);
+        textPaint.setStrikeThruText(true);
+        for (int i = 0; i < LOOPS; i++) {
+            TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+        }
+    });
+    OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+    TextStrikethroughTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+            << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+}
+
 TEST(OpReorderer, renderNode) {
-    sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+    class RenderNodeTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            switch(mIndex++) {
+            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();
+            }
+        }
+    };
+
+    sp<RenderNode> child = TestUtils::createNode(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) {
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorDKGRAY);
         canvas.drawRect(0, 0, 200, 200, paint);
@@ -198,138 +232,128 @@
         canvas.restore();
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
     RenderNodeTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-class ClippedTestRenderer : public TestRendererBase {
-public:
-    void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(0, mIndex++);
-        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) {
+    class ClippedTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            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());
+        }
+    };
+
+    sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
 
     OpReorderer reorderer(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, nodes);
-
+            200, 200, createSyncedNodeList(node), sLightCenter);
     ClippedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-
-class SaveLayerSimpleTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(180u, width);
-        EXPECT_EQ(180u, height);
-        return nullptr;
-    }
-    void endLayer() override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
-        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(const LayerOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(3, mIndex++);
-        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) {
+    class SaveLayerSimpleTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(180u, width);
+            EXPECT_EQ(180u, height);
+            return nullptr;
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            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(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(3, mIndex++);
+            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());
+        }
+    };
+
     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);
-
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
     SaveLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-
-/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - createLayer2, rect2 endLayer2
- * - createLayer1, rect1, drawLayer2, endLayer1
- * - startFrame, layerOp1, endFrame
- */
-class SaveLayerNestedTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
-        const int index = mIndex++;
-        if (index == 0) {
-            EXPECT_EQ(400u, width);
-            EXPECT_EQ(400u, height);
-            return (OffscreenBuffer*) 0x400;
-        } else if (index == 3) {
-            EXPECT_EQ(800u, width);
-            EXPECT_EQ(800u, height);
-            return (OffscreenBuffer*) 0x800;
-        } else { ADD_FAILURE(); }
-        return (OffscreenBuffer*) nullptr;
-    }
-    void endLayer() override {
-        int index = mIndex++;
-        EXPECT_TRUE(index == 2 || index == 6);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(7, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(9, mIndex++);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        const int index = mIndex++;
-        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(const LayerOp& op, const BakedOpState& state) override {
-        const int index = mIndex++;
-        if (index == 5) {
-            EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
-            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
-        } else if (index == 8) {
-            EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
-            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
-        } else { ADD_FAILURE(); }
-    }
-};
 TEST(OpReorderer, saveLayerNested) {
+    /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
+     * - startTemporaryLayer2, rect2 endLayer2
+     * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
+     * - startFrame, layerOp1, endFrame
+     */
+    class SaveLayerNestedTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            const int index = mIndex++;
+            if (index == 0) {
+                EXPECT_EQ(400u, width);
+                EXPECT_EQ(400u, height);
+                return (OffscreenBuffer*) 0x400;
+            } else if (index == 3) {
+                EXPECT_EQ(800u, width);
+                EXPECT_EQ(800u, height);
+                return (OffscreenBuffer*) 0x800;
+            } else { ADD_FAILURE(); }
+            return (OffscreenBuffer*) nullptr;
+        }
+        void endLayer() override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 6);
+        }
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            EXPECT_EQ(7, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(9, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            const int index = mIndex++;
+            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(const LayerOp& op, const BakedOpState& state) override {
+            const int index = mIndex++;
+            if (index == 5) {
+                EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+                EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+            } else if (index == 8) {
+                EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
+                EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+            } else { ADD_FAILURE(); }
+        }
+    };
+
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
         canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
         {
@@ -343,8 +367,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(800, 800, *dl);
-
+    OpReorderer reorderer(800, 800, *dl, sLightCenter);
     SaveLayerNestedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
@@ -362,139 +385,148 @@
         canvas.restore();
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-class HwLayerSimpleTestRenderer : public TestRendererBase {
-public:
-    void startLayer(OffscreenBuffer* offscreenBuffer) override {
-        EXPECT_EQ(0, mIndex++);
-        EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(1, mIndex++);
+RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
+    class HwLayerSimpleTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+            EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
 
-        EXPECT_TRUE(state.computedState.transform.isIdentity())
-                << "Transform should be reset within layer";
+            EXPECT_TRUE(state.computedState.transform.isIdentity())
+                    << "Transform should be reset within layer";
 
-        EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
-                << "Damage rect should be used to clip layer content";
-    }
-    void endLayer() override {
-        EXPECT_EQ(2, mIndex++);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(3, mIndex++);
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(4, mIndex++);
-    }
-    void endFrame() override {
-        EXPECT_EQ(5, mIndex++);
-    }
-};
-TEST(OpReorderer, hwLayerSimple) {
-    sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+            EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+                    << "Damage rect should be used to clip layer content";
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+        void endFrame() override {
+            EXPECT_EQ(5, mIndex++);
+        }
+    };
+
+    sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     });
-    node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    node->setPropertyFieldsDirty(RenderNode::GENERIC);
-    OffscreenBuffer** bufferHandle = node->getLayerHandle();
-    *bufferHandle = (OffscreenBuffer*) 0x0124;
+    OffscreenBuffer** layerHandle = node->getLayerHandle();
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    // create RenderNode's layer here in same way prepareTree would
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *layerHandle = &layer;
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
+    auto syncedNodeList = createSyncedNodeList(node);
 
     // only enqueue partial damage
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedNodeList, sLightCenter);
     HwLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
 
     // clean up layer pointer, so we can safely destruct RenderNode
-    *bufferHandle = nullptr;
+    *layerHandle = nullptr;
 }
 
+RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
+    /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+     * - startRepaintLayer(child), rect(grey), endLayer
+     * - startTemporaryLayer, drawLayer(child), endLayer
+     * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+     * - startFrame, drawLayer(parent), endLayerb
+     */
+    class HwLayerComplexTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+            EXPECT_EQ(3, mIndex++); // savelayer first
+            return (OffscreenBuffer*)0xabcd;
+        }
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            int index = mIndex++;
+            if (index == 0) {
+                // starting inner layer
+                EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            } else if (index == 6) {
+                // starting outer layer
+                EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
+            } else { ADD_FAILURE(); }
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            if (index == 1) {
+                // inner layer's rect (white)
+                EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+            } else if (index == 7) {
+                // outer layer's rect (grey)
+                EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+            } else { ADD_FAILURE(); }
+        }
+        void endLayer() override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+        }
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            EXPECT_EQ(10, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            OffscreenBuffer* layer = *op.layerHandle;
+            int index = mIndex++;
+            if (index == 4) {
+                EXPECT_EQ(100u, layer->viewportWidth);
+                EXPECT_EQ(100u, layer->viewportHeight);
+            } else if (index == 8) {
+                EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+            } else if (index == 11) {
+                EXPECT_EQ(200u, layer->viewportWidth);
+                EXPECT_EQ(200u, layer->viewportHeight);
+            } else { ADD_FAILURE(); }
+        }
+        void endFrame() override {
+            EXPECT_EQ(12, mIndex++);
+        }
+    };
 
-/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
- * - startLayer(child), rect(grey), endLayer
- * - createLayer, drawLayer(child), endLayer
- * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
- * - startFrame, drawLayer(parent), endLayerb
- */
-class HwLayerComplexTestRenderer : public TestRendererBase {
-public:
-    OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
-        EXPECT_EQ(3, mIndex++); // savelayer first
-        return (OffscreenBuffer*)0xabcd;
-    }
-    void startLayer(OffscreenBuffer* offscreenBuffer) override {
-        int index = mIndex++;
-        if (index == 0) {
-            // starting inner layer
-            EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
-        } else if (index == 6) {
-            // starting outer layer
-            EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
-        } else { ADD_FAILURE(); }
-    }
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        int index = mIndex++;
-        if (index == 1) {
-            // inner layer's rect (white)
-            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
-        } else if (index == 7) {
-            // outer layer's rect (grey)
-            EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
-        } else { ADD_FAILURE(); }
-    }
-    void endLayer() override {
-        int index = mIndex++;
-        EXPECT_TRUE(index == 2 || index == 5 || index == 9);
-    }
-    void startFrame(uint32_t width, uint32_t height) override {
-        EXPECT_EQ(10, mIndex++);
-    }
-    void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
-        int index = mIndex++;
-        if (index == 4) {
-            EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
-        } else if (index == 8) {
-            EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
-        } else if (index == 11) {
-            EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
-        } else { ADD_FAILURE(); }
-    }
-    void endFrame() override {
-        EXPECT_EQ(12, mIndex++);
-    }
-};
-TEST(OpReorderer, hwLayerComplex) {
-    auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
-            [](RecordingCanvas& canvas) {
+    auto child = TestUtils::createNode(50, 50, 150, 150,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     });
-    child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    child->setPropertyFieldsDirty(RenderNode::GENERIC);
-    *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+    OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *(child->getLayerHandle()) = &childLayer;
 
     RenderNode* childPtr = child.get();
-    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
-            [childPtr](RecordingCanvas& canvas) {
+    auto parent = TestUtils::createNode(0, 0, 200, 200,
+            [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorDKGRAY);
         canvas.drawRect(0, 0, 200, 200, paint);
@@ -503,21 +535,17 @@
         canvas.drawRenderNode(childPtr);
         canvas.restore();
     });
-    parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    parent->setPropertyFieldsDirty(RenderNode::GENERIC);
-    *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+    OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+    *(parent->getLayerHandle()) = &parentLayer;
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+    auto syncedList = createSyncedNodeList(parent);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, sLightCenter);
     HwLayerComplexTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(13, renderer.getIndex());
@@ -527,21 +555,13 @@
     *(parent->getLayerHandle()) = nullptr;
 }
 
-
-class ZReorderTestRenderer : public TestRendererBase {
-public:
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
-        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
-    }
-};
 static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
     canvas->drawRect(0, 0, 100, 100, paint);
 }
 static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
-    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+    auto node = TestUtils::createNode(0, 0, 100, 100,
             [expectedDrawOrder](RecordingCanvas& canvas) {
         drawOrderedRect(&canvas, expectedDrawOrder);
     });
@@ -550,7 +570,15 @@
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
 TEST(OpReorderer, zReorder) {
-    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+    class ZReorderTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+        }
+    };
+
+    auto parent = TestUtils::createNode(0, 0, 100, 100,
             [](RecordingCanvas& canvas) {
         drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
         drawOrderedRect(&canvas, 1);
@@ -565,46 +593,199 @@
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
-
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            createSyncedNodeList(parent), sLightCenter);
     ZReorderTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 };
 
-
-class PropertyTestRenderer : public TestRendererBase {
-public:
-    PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
-            : mCallback(callback) {}
-    void onRectOp(const RectOp& op, const BakedOpState& state) override {
-        EXPECT_EQ(mIndex++, 0);
-        mCallback(op, state);
-    }
-    std::function<void(const RectOp&, const BakedOpState&)> mCallback;
-};
-
-static void testProperty(
-        std::function<int(RenderProperties&)> propSetupCallback,
-        std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
-    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+    return TestUtils::createNode(0, 0, 100, 100,
+            [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setTranslationZ(translationZ);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     });
-    node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+}
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
+TEST(OpReorderer, shadow) {
+    class ShadowTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+            EXPECT_TRUE(op.casterPath->isRect(nullptr));
+            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY);
 
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(100, 100), 200, 200, nodes);
+            Matrix4 expectedZ;
+            expectedZ.loadTranslate(0, 0, 5);
+            EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+    };
 
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
+    ShadowTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(OpReorderer, shadowSaveLayer) {
+    class ShadowSaveLayerTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            return nullptr;
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        // save/restore outside of reorderBarrier, so they don't get moved out of place
+        canvas.translate(20, 10);
+        int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.insertReorderBarrier(false);
+        canvas.restoreToCount(count);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+    ShadowSaveLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+    class ShadowHwLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
+        canvas.insertReorderBarrier(true);
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(20, 10);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.restore();
+    });
+    OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+    layer.setWindowTransform(windowTransform);
+    *layerHandle = &layer;
+
+    auto syncedList = createSyncedNodeList(parent);
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, (Vector3) { 100, 100, 100 });
+    ShadowHwLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
+TEST(OpReorderer, shadowLayering) {
+    class ShadowLayeringTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 0 || index == 1);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 3);
+        }
+    };
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
+    ShadowLayeringTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
+        std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
+    class PropertyTestRenderer : public TestRendererBase {
+    public:
+        PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
+                : mCallback(callback) {}
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(mIndex++, 0);
+            mCallback(op, state);
+        }
+        std::function<void(const RectOp&, const BakedOpState&)> mCallback;
+    };
+
+    auto node = TestUtils::createNode(0, 0, 100, 100,
+            [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) {
+        propSetupCallback(props);
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+            createSyncedNodeList(node), sLightCenter);
     PropertyTestRenderer renderer(opValidateCallback);
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
@@ -614,7 +795,6 @@
     testProperty([](RenderProperties& properties) {
         properties.setAlpha(0.5f);
         properties.setHasOverlappingRendering(false);
-        return RenderNode::ALPHA | RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
     });
@@ -624,7 +804,6 @@
     testProperty([](RenderProperties& properties) {
         properties.setClipToBounds(true);
         properties.setClipBounds(Rect(10, 20, 300, 400));
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
                 << "Clip rect should be intersection of node bounds and clip bounds";
@@ -634,7 +813,6 @@
 TEST(OpReorderer, renderPropRevealClip) {
     testProperty([](RenderProperties& properties) {
         properties.mutableRevealClip().set(true, 50, 50, 25);
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         ASSERT_NE(nullptr, state.roundRectClipState);
         EXPECT_TRUE(state.roundRectClipState->highPriority);
@@ -647,7 +825,6 @@
     testProperty([](RenderProperties& properties) {
         properties.mutableOutline().setShouldClip(true);
         properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         ASSERT_NE(nullptr, state.roundRectClipState);
         EXPECT_FALSE(state.roundRectClipState->highPriority);
@@ -671,9 +848,6 @@
         properties.setTranslationY(20);
         properties.setScaleX(0.5f);
         properties.setScaleY(0.7f);
-        return RenderNode::GENERIC
-                | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
-                | RenderNode::SCALE_X | RenderNode::SCALE_Y;
     }, [](const RectOp& op, const BakedOpState& state) {
         Matrix4 matrix;
         matrix.loadTranslate(10, 10, 0); // left, top
@@ -692,5 +866,122 @@
     });
 }
 
+struct SaveLayerAlphaData {
+    uint32_t layerWidth = 0;
+    uint32_t layerHeight = 0;
+    Rect rectClippedBounds;
+    Matrix4 rectMatrix;
+};
+/**
+ * Constructs a view to hit the temporary layer alpha property implementation:
+ *     a) 0 < alpha < 1
+ *     b) too big for layer (larger than maxTextureSize)
+ *     c) overlapping rendering content
+ * returning observed data about layer size and content clip/transform.
+ *
+ * Used to validate clipping behavior of temporary layer, where requested layer size is reduced
+ * (for efficiency, and to fit in layer size constraints) based on parent clip.
+ */
+void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
+        std::function<void(RenderProperties&)> propSetupCallback) {
+    class SaveLayerAlphaClipTestRenderer : public TestRendererBase {
+    public:
+        SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData)
+                : mOutData(outData) {}
+
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            mOutData->layerWidth = width;
+            mOutData->layerHeight = height;
+            return nullptr;
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+
+            mOutData->rectClippedBounds = state.computedState.clippedBounds;
+            mOutData->rectMatrix = state.computedState.transform;
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(3, mIndex++);
+        }
+    private:
+        SaveLayerAlphaData* mOutData;
+    };
+
+    ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize())
+            << "Node must be bigger than max texture size to exercise saveLayer codepath";
+    auto node = TestUtils::createNode(0, 0, 10000, 10000,
+            [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setHasOverlappingRendering(true);
+        properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
+        // apply other properties
+        propSetupCallback(properties);
+
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 10000, 10000, paint);
+    });
+    auto nodes = createSyncedNodeList(node); // sync before querying height
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter);
+    SaveLayerAlphaClipTestRenderer renderer(outObservedData);
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+
+    // assert, since output won't be valid if we haven't seen a save layer triggered
+    ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
+}
+
+TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) {
+    SaveLayerAlphaData observedData;
+    testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+        properties.setTranslationX(10); // offset rendering content
+        properties.setTranslationY(-2000); // offset rendering content
+    });
+    EXPECT_EQ(190u, observedData.layerWidth);
+    EXPECT_EQ(200u, observedData.layerHeight);
+    EXPECT_EQ(Rect(0, 0, 190, 200), observedData.rectClippedBounds)
+            << "expect content to be clipped to screen area";
+    Matrix4 expected;
+    expected.loadTranslate(0, -2000, 0);
+    EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix)
+            << "expect content to be translated as part of being clipped";
+}
+
+TEST(OpReorderer, renderPropSaveLayerAlphaRotate) {
+    SaveLayerAlphaData observedData;
+    testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+        // Translate and rotate the view so that the only visible part is the top left corner of
+        // the view. It will form an isoceles right triangle with a long side length of 200 at the
+        // bottom of the viewport.
+        properties.setTranslationX(100);
+        properties.setTranslationY(100);
+        properties.setPivotX(0);
+        properties.setPivotY(0);
+        properties.setRotation(45);
+    });
+    // ceil(sqrt(2) / 2 * 200) = 142
+    EXPECT_EQ(142u, observedData.layerWidth);
+    EXPECT_EQ(142u, observedData.layerHeight);
+    EXPECT_EQ(Rect(0, 0, 142, 142), observedData.rectClippedBounds);
+    EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
+}
+
+TEST(OpReorderer, renderPropSaveLayerAlphaScale) {
+    SaveLayerAlphaData observedData;
+    testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+        properties.setPivotX(0);
+        properties.setPivotY(0);
+        properties.setScaleX(2);
+        properties.setScaleY(0.5f);
+    });
+    EXPECT_EQ(100u, observedData.layerWidth);
+    EXPECT_EQ(400u, observedData.layerHeight);
+    EXPECT_EQ(Rect(0, 0, 100, 400), observedData.rectClippedBounds);
+    EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 83b37ab..c23d47e 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -18,7 +18,7 @@
 
 #include <RecordedOp.h>
 #include <RecordingCanvas.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
@@ -41,21 +41,110 @@
     playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
 }
 
-TEST(RecordingCanvas, testSimpleRectRecord) {
+TEST(RecordingCanvas, drawLines) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setStrokeWidth(20);
+        float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
+        canvas.drawLines(&points[0], 7, paint);
+    });
+
+    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+    auto op = dl->getOps()[0];
+    ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
+    EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
+            << "float count must be rounded down to closest multiple of 4";
+    EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds)
+            << "unmapped bounds must be size of line, outset by 1/2 stroke width";
+}
+
+TEST(RecordingCanvas, drawRect) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(10, 20, 90, 180, SkPaint());
     });
 
+    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+    auto op = *(dl->getOps()[0]);
+    ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+    EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+    EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawText) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+    });
+
     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(RecordedOpId::TextOp, op.opId);
+        EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+        EXPECT_TRUE(op.localMatrix.isIdentity());
+        EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
+                << "Op expected to be 25+ pixels wide, 10+ pixels tall";
     });
     ASSERT_EQ(1, count);
 }
 
+TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        for (int i = 0; i < 2; i++) {
+            for (int j = 0; j < 2; j++) {
+                paint.setUnderlineText(i != 0);
+                paint.setStrikeThruText(j != 0);
+                TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+            }
+        }
+    });
+
+    auto ops = dl->getOps();
+    ASSERT_EQ(8u, ops.size());
+
+    int index = 0;
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
+}
+
+TEST(RecordingCanvas, drawText_forceAlignLeft) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        paint.setTextAlign(SkPaint::kLeft_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+        paint.setTextAlign(SkPaint::kCenter_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+        paint.setTextAlign(SkPaint::kRight_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+    });
+
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        count++;
+        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
+                << "recorded drawText commands must force kLeft_Align on their paint";
+        EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+    });
+    ASSERT_EQ(3, count);
+}
+
 TEST(RecordingCanvas, backgroundAndImage) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap;
@@ -109,7 +198,7 @@
     ASSERT_EQ(2, count);
 }
 
-TEST(RecordingCanvas, saveLayerSimple) {
+TEST(RecordingCanvas, saveLayer_simple) {
     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());
@@ -143,7 +232,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerViewportCrop) {
+TEST(RecordingCanvas, saveLayer_viewportCrop) {
     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);
@@ -167,7 +256,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.translate(100, 100);
@@ -193,7 +282,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerRotateClipped) {
+TEST(RecordingCanvas, saveLayer_rotateClipped) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.translate(100, 100);
@@ -224,7 +313,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, testReorderBarrier) {
+TEST(RecordingCanvas, insertReorderBarrier) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.insertReorderBarrier(true);
diff --git a/libs/hwui/unit_tests/VectorDrawableTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp
new file mode 100644
index 0000000..77dd73a
--- /dev/null
+++ b/libs/hwui/unit_tests/VectorDrawableTests.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "PathParser.h"
+#include "utils/MathUtils.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <functional>
+
+namespace android {
+namespace uirenderer {
+
+struct TestData {
+    const char* pathString;
+    const PathData pathData;
+    const std::function<void(SkPath*)> skPathLamda;
+};
+
+const static TestData sTestDataSet[] = {
+    // TestData with scientific notation -2e3 etc.
+    {
+        // Path
+        "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+        {
+            // Verbs
+            {'M', 'l', 'z'},
+            // Verb sizes
+            {2, 4, 0},
+            // Points
+            {2, 22, 20, 0,  1, -2000},
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(2, 22);
+            outPath->rLineTo(20, 0);
+            outPath->rLineTo(1, -2000);
+            outPath->close();
+            outPath->moveTo(2, 22);
+        }
+    },
+
+    // Comprehensive data, containing all the verbs possible.
+    {
+        // Path
+        "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
+        {
+            // Verbs
+            {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
+            // VerbSizes
+            {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
+            // Points
+            {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(1.0, 1.0);
+            outPath->rMoveTo(2.0, 2.0);
+            outPath->rLineTo(3.0, 3.0);
+            outPath->lineTo(3.0, 3.0);
+            outPath->lineTo(4.0, 3.0);
+            outPath->rLineTo(4.0, 0);
+            outPath->lineTo(8.0, 5.0);
+            outPath->rLineTo(0, 5.0);
+            outPath->quadTo(6.0, 6.0, 6.0, 6.0);
+            outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
+            outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
+            outPath->quadTo(26.0, 26.0, 7.0, 7.0);
+            outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+            outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+            outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
+            outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
+            outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
+            outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
+            outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
+            outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
+            outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
+            outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
+            outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
+            outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
+            outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
+            outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
+            outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
+            outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
+            outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
+        }
+    },
+
+    // Random long data
+    {
+        // Path
+        "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
+        {
+            // Verbs
+            {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
+            // Verb sizes
+            {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
+            // Points
+            {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
+        },
+        [](SkPath* outPath) {
+            outPath->moveTo(5.3, 13.2);
+            outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
+            outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
+            outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
+            outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
+            outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
+            outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
+            outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
+            outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
+            outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
+            outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
+            outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
+            outPath->close();
+            outPath->moveTo(5.3, 13.2);
+        }
+    },
+
+    // Extreme case with numbers and decimal points crunched together
+    {
+        // Path
+        "l0.0.0.5.0.0.5-0.5.0.0-.5z",
+        {
+            // Verbs
+            {'l', 'z'},
+            // Verb sizes
+            {10, 0},
+            // Points
+            {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
+        },
+        [](SkPath* outPath) {
+            outPath->rLineTo(0.0, 0.0);
+            outPath->rLineTo(0.5, 0.0);
+            outPath->rLineTo(0.0, 0.5);
+            outPath->rLineTo(-0.5, 0.0);
+            outPath->rLineTo(0.0, -0.5);
+            outPath->close();
+            outPath->moveTo(0.0, 0.0);
+        }
+    },
+
+    // Empty test data
+    {
+        "",
+        {
+                // Verbs
+                {},
+                {},
+                {},
+        },
+        [](SkPath* outPath) {}
+    }
+
+};
+
+struct StringPath {
+    const char* stringPath;
+    bool isValid;
+};
+
+const StringPath sStringPaths[] = {
+    {"3e...3", false},
+    {"L.M.F.A.O", false},
+    {"m 1 1", true},
+    {"z", true},
+    {"1-2e34567", false}
+};
+
+static bool hasSameVerbs(const PathData& from, const PathData& to) {
+    return from.verbs == to.verbs && from.verbSizes == to.verbSizes;
+}
+
+TEST(PathParser, parseStringForData) {
+    for (TestData testData: sTestDataSet) {
+        PathParser::ParseResult result;
+        // Test generated path data against the given data.
+        PathData pathData;
+        size_t length = strlen(testData.pathString);
+        PathParser::getPathDataFromString(&pathData, &result, testData.pathString, length);
+        EXPECT_EQ(testData.pathData, pathData);
+    }
+
+    for (StringPath stringPath : sStringPaths) {
+        PathParser::ParseResult result;
+        PathData pathData;
+        SkPath skPath;
+        PathParser::getPathDataFromString(&pathData, &result,
+                stringPath.stringPath, strlen(stringPath.stringPath));
+        EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+    }
+}
+
+TEST(VectorDrawableUtils, createSkPathFromPathData) {
+    for (TestData testData: sTestDataSet) {
+        SkPath expectedPath;
+        testData.skPathLamda(&expectedPath);
+        SkPath actualPath;
+        VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData);
+        EXPECT_EQ(expectedPath, actualPath);
+    }
+}
+
+TEST(PathParser, parseStringForSkPath) {
+    for (TestData testData: sTestDataSet) {
+        PathParser::ParseResult result;
+        size_t length = strlen(testData.pathString);
+        // Check the return value as well as the SkPath generated.
+        SkPath actualPath;
+        PathParser::parseStringForSkPath(&actualPath, &result, testData.pathString, length);
+        bool hasValidData = !result.failureOccurred;
+        EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0);
+        SkPath expectedPath;
+        testData.skPathLamda(&expectedPath);
+        EXPECT_EQ(expectedPath, actualPath);
+    }
+
+    for (StringPath stringPath : sStringPaths) {
+        PathParser::ParseResult result;
+        SkPath skPath;
+        PathParser::parseStringForSkPath(&skPath, &result, stringPath.stringPath,
+                strlen(stringPath.stringPath));
+        EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+    }
+}
+
+TEST(VectorDrawableUtils, morphPathData) {
+    for (TestData fromData: sTestDataSet) {
+        for (TestData toData: sTestDataSet) {
+            bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
+            if (fromData.pathData == toData.pathData) {
+                EXPECT_TRUE(canMorph);
+            } else {
+                bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+                EXPECT_EQ(expectedToMorph, canMorph);
+            }
+        }
+    }
+}
+
+TEST(VectorDrawableUtils, interpolatePathData) {
+    // Interpolate path data with itself and every other path data
+    for (TestData fromData: sTestDataSet) {
+        for (TestData toData: sTestDataSet) {
+            PathData outData;
+            bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
+                    toData.pathData, 0.5);
+            bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+            EXPECT_EQ(expectedToMorph, success);
+        }
+    }
+
+    float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
+    // Now try to interpolate with a slightly modified version of self and expect success
+    for (TestData fromData : sTestDataSet) {
+        PathData toPathData = fromData.pathData;
+        for (size_t i = 0; i < toPathData.points.size(); i++) {
+            toPathData.points[i]++;
+        }
+        const PathData& fromPathData = fromData.pathData;
+        PathData outData;
+        // Interpolate the two path data with different fractions
+        for (float fraction : fractions) {
+            bool success = VectorDrawableUtils::interpolatePathData(
+                    &outData, fromPathData, toPathData, fraction);
+            EXPECT_TRUE(success);
+            for (size_t i = 0; i < outData.points.size(); i++) {
+                float expectedResult = fromPathData.points[i] * (1.0 - fraction) +
+                        toPathData.points[i] * fraction;
+                EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i]));
+            }
+        }
+    }
+}
+
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
index 315c249..93d37c2 100644
--- a/libs/hwui/utils/FatVector.h
+++ b/libs/hwui/utils/FatVector.h
@@ -29,6 +29,7 @@
 #include "utils/Macros.h"
 
 #include <stddef.h>
+#include <stdlib.h>
 #include <type_traits>
 #include <utils/Log.h>
 
@@ -95,6 +96,7 @@
     FatVector(size_t capacity) : FatVector() {
         this->resize(capacity);
     }
+
 private:
     typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
 };
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083..ccf2287 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -35,4 +35,7 @@
         static_assert(std::is_standard_layout<Type>::value, \
         #Type " must have standard layout")
 
+#define WARN_UNUSED_RESULT \
+    __attribute__((warn_unused_result))
+
 #endif /* MACROS_H */
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
new file mode 100644
index 0000000..dd6fc36
--- /dev/null
+++ b/libs/hwui/utils/TestUtils.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 "TestUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+SkColor TestUtils::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))));
+}
+
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+        const SkPaint& inPaint, float x, float y) {
+   // copy to force TextEncoding (which JNI layer would have done)
+   SkPaint paint(inPaint);
+   paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+   SkMatrix identity;
+   identity.setIdentity();
+   SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+   SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+
+   float totalAdvance = 0;
+   std::vector<glyph_t> glyphs;
+   std::vector<float> positions;
+   Rect bounds;
+   while (*text != '\0') {
+       SkUnichar unichar = SkUTF8_NextUnichar(&text);
+       glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+       autoCache.getCache()->unicharToGlyph(unichar);
+
+       // push glyph and its relative position
+       glyphs.push_back(glyph);
+       positions.push_back(totalAdvance);
+       positions.push_back(0);
+
+       // compute bounds
+       SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+       Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+       glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+       bounds.unionWith(glyphBounds);
+
+       // advance next character
+       SkScalar skWidth;
+       paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+       totalAdvance += skWidth;
+   }
+   bounds.translate(x, y);
+   canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
+           bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/utils/TestUtils.h
similarity index 66%
rename from libs/hwui/unit_tests/TestUtils.h
rename to libs/hwui/utils/TestUtils.h
index 28e0fd8..f9fa242 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -27,8 +27,10 @@
 
 #if HWUI_NEW_OPS
 #include <RecordedOp.h>
+#include <RecordingCanvas.h>
 #else
 #include <DisplayListOp.h>
+#include <DisplayListCanvas.h>
 #endif
 
 #include <memory>
@@ -36,6 +38,12 @@
 namespace android {
 namespace uirenderer {
 
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
 #define EXPECT_MATRIX_APPROX_EQ(a, b) \
     EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
 
@@ -45,6 +53,20 @@
             && MathUtils::areEqual(a.right, b.right) \
             && MathUtils::areEqual(a.bottom, b.bottom));
 
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+    class test_case_name##_##test_name##_RenderThreadTest { \
+    public: \
+        static void doTheThing(renderthread::RenderThread& renderThread); \
+    }; \
+    TEST(test_case_name, test_name) { \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }; \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
 class TestUtils {
 public:
     class SignalingDtor {
@@ -83,7 +105,8 @@
 
     static SkBitmap createSkBitmap(int width, int height) {
         SkBitmap bitmap;
-        SkImageInfo info = SkImageInfo::MakeUnknown(width, height);
+        SkImageInfo info = SkImageInfo::Make(width, height,
+                kN32_SkColorType, kPremul_SkAlphaType);
         bitmap.setInfo(info);
         bitmap.allocPixels(info);
         return bitmap;
@@ -97,31 +120,48 @@
         return std::unique_ptr<DisplayList>(canvas.finishRecording());
     }
 
-    static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
+#if HWUI_NULL_GPU
         // if RenderNodes are being sync'd/used, device info will be needed, since
         // DeviceInfo::maxTextureSize() affects layer property
         DeviceInfo::initialize();
+#endif
 
         sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        if (onLayer) {
-            node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-            node->setPropertyFieldsDirty(RenderNode::GENERIC);
+        RenderProperties& props = node->mutateStagingProperties();
+        props.setLeftTopRightBottom(left, top, right, bottom);
+        if (setup) {
+            TestCanvas canvas(props.getWidth(), props.getHeight());
+            setup(props, canvas);
+            node->setStagingDisplayList(canvas.finishRecording());
         }
+        node->setPropertyFieldsDirty(0xFFFFFFFF);
         return node;
     }
 
-    template<class CanvasType>
     static sp<RenderNode> createNode(int left, int top, int right, int bottom,
-            std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
-        sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
+            std::function<void(RenderProperties& props)> setup) {
+        return createNode(left, top, right, bottom,
+                [&setup](RenderProperties& props, TestCanvas& canvas) {
+            setup(props);
+        });
+    }
 
-        auto&& props = node->stagingProperties(); // staging, since not sync'd yet
-        CanvasType canvas(props.getWidth(), props.getHeight());
-        canvasCallback(canvas);
-        node->setStagingDisplayList(canvas.finishRecording());
-        return node;
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            std::function<void(TestCanvas& canvas)> setup) {
+        return createNode(left, top, right, bottom,
+                [&setup](RenderProperties& props, TestCanvas& canvas) {
+            setup(canvas);
+        });
+    }
+
+    static void recordNode(RenderNode& node,
+            std::function<void(TestCanvas&)> contentCallback) {
+       TestCanvas canvas(node.stagingProperties().getWidth(),
+               node.stagingProperties().getHeight());
+       contentCallback(canvas);
+       node.setStagingDisplayList(canvas.finishRecording());
     }
 
     static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
@@ -153,6 +193,12 @@
         TestTask task(rtCallback);
         renderthread::RenderThread::getInstance().queueAndWait(&task);
     }
+
+    static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+
+    static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+            const SkPaint& inPaint, float x, float y);
+
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
         node->syncProperties();
diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp
new file mode 100644
index 0000000..ca75c59
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.cpp
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "VectorDrawableUtils.h"
+
+#include "PathParser.h"
+
+#include <math.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+class PathResolver {
+public:
+    float currentX = 0;
+    float currentY = 0;
+    float ctrlPointX = 0;
+    float ctrlPointY = 0;
+    float currentSegmentStartX = 0;
+    float currentSegmentStartY = 0;
+    void addCommand(SkPath* outPath, char previousCmd,
+            char cmd, const std::vector<float>* points, size_t start, size_t end);
+};
+
+bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) {
+    if (morphFrom.verbs.size() != morphTo.verbs.size()) {
+        return false;
+    }
+
+    for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) {
+        if (morphFrom.verbs[i] != morphTo.verbs[i]
+                ||  morphFrom.verbSizes[i] != morphTo.verbSizes[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom,
+        const PathData& morphTo, float fraction) {
+    if (!canMorph(morphFrom, morphTo)) {
+        return false;
+    }
+    interpolatePaths(outData, morphFrom, morphTo, fraction);
+    return true;
+}
+
+ /**
+ * Convert an array of PathVerb to Path.
+ */
+void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) {
+    PathResolver resolver;
+    char previousCommand = 'm';
+    size_t start = 0;
+    outPath->reset();
+    for (unsigned int i = 0; i < data.verbs.size(); i++) {
+        size_t verbSize = data.verbSizes[i];
+        resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start,
+                start + verbSize);
+        previousCommand = data.verbs[i];
+        start += verbSize;
+    }
+}
+
+/**
+ * The current PathVerb will be interpolated between the
+ * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+ * <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathVerb.
+ * @param nodeTo The end value as a PathVerb
+ * @param fraction The fraction to interpolate.
+ */
+void VectorDrawableUtils::interpolatePaths(PathData* outData,
+        const PathData& from, const PathData& to, float fraction) {
+    outData->points.resize(from.points.size());
+    outData->verbSizes = from.verbSizes;
+    outData->verbs = from.verbs;
+
+    for (size_t i = 0; i < from.points.size(); i++) {
+        outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction;
+    }
+}
+
+/**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+static void arcToBezier(SkPath* p,
+        double cx,
+        double cy,
+        double a,
+        double b,
+        double e1x,
+        double e1y,
+        double theta,
+        double start,
+        double sweep) {
+    // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+    // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+    // Maximum of 45 degrees per cubic Bezier segment
+    int numSegments = ceil(fabs(sweep * 4 / M_PI));
+
+    double eta1 = start;
+    double cosTheta = cos(theta);
+    double sinTheta = sin(theta);
+    double cosEta1 = cos(eta1);
+    double sinEta1 = sin(eta1);
+    double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+    double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+    double anglePerSegment = sweep / numSegments;
+    for (int i = 0; i < numSegments; i++) {
+        double eta2 = eta1 + anglePerSegment;
+        double sinEta2 = sin(eta2);
+        double cosEta2 = cos(eta2);
+        double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+        double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+        double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+        double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+        double tanDiff2 = tan((eta2 - eta1) / 2);
+        double alpha =
+                sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+        double q1x = e1x + alpha * ep1x;
+        double q1y = e1y + alpha * ep1y;
+        double q2x = e2x - alpha * ep2x;
+        double q2y = e2y - alpha * ep2y;
+
+        p->cubicTo((float) q1x,
+                (float) q1y,
+                (float) q2x,
+                (float) q2y,
+                (float) e2x,
+                (float) e2y);
+        eta1 = eta2;
+        e1x = e2x;
+        e1y = e2y;
+        ep1x = ep2x;
+        ep1y = ep2y;
+    }
+}
+
+inline double toRadians(float theta) { return theta * M_PI / 180;}
+
+static void drawArc(SkPath* p,
+        float x0,
+        float y0,
+        float x1,
+        float y1,
+        float a,
+        float b,
+        float theta,
+        bool isMoreThanHalf,
+        bool isPositiveArc) {
+
+    /* Convert rotation angle from degrees to radians */
+    double thetaD = toRadians(theta);
+    /* Pre-compute rotation matrix entries */
+    double cosTheta = cos(thetaD);
+    double sinTheta = sin(thetaD);
+    /* Transform (x0, y0) and (x1, y1) into unit space */
+    /* using (inverse) rotation, followed by (inverse) scale */
+    double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+    double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+    double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+    double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+    /* Compute differences and averages */
+    double dx = x0p - x1p;
+    double dy = y0p - y1p;
+    double xm = (x0p + x1p) / 2;
+    double ym = (y0p + y1p) / 2;
+    /* Solve for intersecting unit circles */
+    double dsq = dx * dx + dy * dy;
+    if (dsq == 0.0) {
+        ALOGW("Points are coincident");
+        return; /* Points are coincident */
+    }
+    double disc = 1.0 / dsq - 1.0 / 4.0;
+    if (disc < 0.0) {
+        ALOGW("Points are too far apart %f", dsq);
+        float adjust = (float) (sqrt(dsq) / 1.99999);
+        drawArc(p, x0, y0, x1, y1, a * adjust,
+                b * adjust, theta, isMoreThanHalf, isPositiveArc);
+        return; /* Points are too far apart */
+    }
+    double s = sqrt(disc);
+    double sdx = s * dx;
+    double sdy = s * dy;
+    double cx;
+    double cy;
+    if (isMoreThanHalf == isPositiveArc) {
+        cx = xm - sdy;
+        cy = ym + sdx;
+    } else {
+        cx = xm + sdy;
+        cy = ym - sdx;
+    }
+
+    double eta0 = atan2((y0p - cy), (x0p - cx));
+
+    double eta1 = atan2((y1p - cy), (x1p - cx));
+
+    double sweep = (eta1 - eta0);
+    if (isPositiveArc != (sweep >= 0)) {
+        if (sweep > 0) {
+            sweep -= 2 * M_PI;
+        } else {
+            sweep += 2 * M_PI;
+        }
+    }
+
+    cx *= a;
+    cy *= b;
+    double tcx = cx;
+    cx = cx * cosTheta - cy * sinTheta;
+    cy = tcx * sinTheta + cy * cosTheta;
+
+    arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+}
+
+
+
+// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
+void PathResolver::addCommand(SkPath* outPath, char previousCmd,
+        char cmd, const std::vector<float>* points, size_t start, size_t end) {
+
+    int incr = 2;
+    float reflectiveCtrlPointX;
+    float reflectiveCtrlPointY;
+
+    switch (cmd) {
+    case 'z':
+    case 'Z':
+        outPath->close();
+        // Path is closed here, but we need to move the pen to the
+        // closed position. So we cache the segment's starting position,
+        // and restore it here.
+        currentX = currentSegmentStartX;
+        currentY = currentSegmentStartY;
+        ctrlPointX = currentSegmentStartX;
+        ctrlPointY = currentSegmentStartY;
+        outPath->moveTo(currentX, currentY);
+        break;
+    case 'm':
+    case 'M':
+    case 'l':
+    case 'L':
+    case 't':
+    case 'T':
+        incr = 2;
+        break;
+    case 'h':
+    case 'H':
+    case 'v':
+    case 'V':
+        incr = 1;
+        break;
+    case 'c':
+    case 'C':
+        incr = 6;
+        break;
+    case 's':
+    case 'S':
+    case 'q':
+    case 'Q':
+        incr = 4;
+        break;
+    case 'a':
+    case 'A':
+        incr = 7;
+        break;
+    }
+
+    for (unsigned int k = start; k < end; k += incr) {
+        switch (cmd) {
+        case 'm': // moveto - Start a new sub-path (relative)
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            if (k > start) {
+                // According to the spec, if a moveto is followed by multiple
+                // pairs of coordinates, the subsequent pairs are treated as
+                // implicit lineto commands.
+                outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+            } else {
+                outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
+                currentSegmentStartX = currentX;
+                currentSegmentStartY = currentY;
+            }
+            break;
+        case 'M': // moveto - Start a new sub-path
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            if (k > start) {
+                // According to the spec, if a moveto is followed by multiple
+                // pairs of coordinates, the subsequent pairs are treated as
+                // implicit lineto commands.
+                outPath->lineTo(points->at(k + 0), points->at(k + 1));
+            } else {
+                outPath->moveTo(points->at(k + 0), points->at(k + 1));
+                currentSegmentStartX = currentX;
+                currentSegmentStartY = currentY;
+            }
+            break;
+        case 'l': // lineto - Draw a line from the current point (relative)
+            outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            break;
+        case 'L': // lineto - Draw a line from the current point
+            outPath->lineTo(points->at(k + 0), points->at(k + 1));
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            break;
+        case 'h': // horizontal lineto - Draws a horizontal line (relative)
+            outPath->rLineTo(points->at(k + 0), 0);
+            currentX += points->at(k + 0);
+            break;
+        case 'H': // horizontal lineto - Draws a horizontal line
+            outPath->lineTo(points->at(k + 0), currentY);
+            currentX = points->at(k + 0);
+            break;
+        case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+            outPath->rLineTo(0, points->at(k + 0));
+            currentY += points->at(k + 0);
+            break;
+        case 'V': // vertical lineto - Draws a vertical line from the current point
+            outPath->lineTo(currentX, points->at(k + 0));
+            currentY = points->at(k + 0);
+            break;
+        case 'c': // curveto - Draws a cubic Bézier curve (relative)
+            outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+                    points->at(k + 4), points->at(k + 5));
+
+            ctrlPointX = currentX + points->at(k + 2);
+            ctrlPointY = currentY + points->at(k + 3);
+            currentX += points->at(k + 4);
+            currentY += points->at(k + 5);
+
+            break;
+        case 'C': // curveto - Draws a cubic Bézier curve
+            outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+                    points->at(k + 4), points->at(k + 5));
+            currentX = points->at(k + 4);
+            currentY = points->at(k + 5);
+            ctrlPointX = points->at(k + 2);
+            ctrlPointY = points->at(k + 3);
+            break;
+        case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+            reflectiveCtrlPointX = 0;
+            reflectiveCtrlPointY = 0;
+            if (previousCmd == 'c' || previousCmd == 's'
+                    || previousCmd == 'C' || previousCmd == 'S') {
+                reflectiveCtrlPointX = currentX - ctrlPointX;
+                reflectiveCtrlPointY = currentY - ctrlPointY;
+            }
+            outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1),
+                    points->at(k + 2), points->at(k + 3));
+            ctrlPointX = currentX + points->at(k + 0);
+            ctrlPointY = currentY + points->at(k + 1);
+            currentX += points->at(k + 2);
+            currentY += points->at(k + 3);
+            break;
+        case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+            reflectiveCtrlPointX = currentX;
+            reflectiveCtrlPointY = currentY;
+            if (previousCmd == 'c' || previousCmd == 's'
+                    || previousCmd == 'C' || previousCmd == 'S') {
+                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+            }
+            outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = points->at(k + 0);
+            ctrlPointY = points->at(k + 1);
+            currentX = points->at(k + 2);
+            currentY = points->at(k + 3);
+            break;
+        case 'q': // Draws a quadratic Bézier (relative)
+            outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = currentX + points->at(k + 0);
+            ctrlPointY = currentY + points->at(k + 1);
+            currentX += points->at(k + 2);
+            currentY += points->at(k + 3);
+            break;
+        case 'Q': // Draws a quadratic Bézier
+            outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = points->at(k + 0);
+            ctrlPointY = points->at(k + 1);
+            currentX = points->at(k + 2);
+            currentY = points->at(k + 3);
+            break;
+        case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+            reflectiveCtrlPointX = 0;
+            reflectiveCtrlPointY = 0;
+            if (previousCmd == 'q' || previousCmd == 't'
+                    || previousCmd == 'Q' || previousCmd == 'T') {
+                reflectiveCtrlPointX = currentX - ctrlPointX;
+                reflectiveCtrlPointY = currentY - ctrlPointY;
+            }
+            outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1));
+            ctrlPointX = currentX + reflectiveCtrlPointX;
+            ctrlPointY = currentY + reflectiveCtrlPointY;
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            break;
+        case 'T': // Draws a quadratic Bézier curve (reflective control point)
+            reflectiveCtrlPointX = currentX;
+            reflectiveCtrlPointY = currentY;
+            if (previousCmd == 'q' || previousCmd == 't'
+                    || previousCmd == 'Q' || previousCmd == 'T') {
+                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+            }
+            outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1));
+            ctrlPointX = reflectiveCtrlPointX;
+            ctrlPointY = reflectiveCtrlPointY;
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            break;
+        case 'a': // Draws an elliptical arc
+            // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+            drawArc(outPath,
+                    currentX,
+                    currentY,
+                    points->at(k + 5) + currentX,
+                    points->at(k + 6) + currentY,
+                    points->at(k + 0),
+                    points->at(k + 1),
+                    points->at(k + 2),
+                    points->at(k + 3) != 0,
+                    points->at(k + 4) != 0);
+            currentX += points->at(k + 5);
+            currentY += points->at(k + 6);
+            ctrlPointX = currentX;
+            ctrlPointY = currentY;
+            break;
+        case 'A': // Draws an elliptical arc
+            drawArc(outPath,
+                    currentX,
+                    currentY,
+                    points->at(k + 5),
+                    points->at(k + 6),
+                    points->at(k + 0),
+                    points->at(k + 1),
+                    points->at(k + 2),
+                    points->at(k + 3) != 0,
+                    points->at(k + 4) != 0);
+            currentX = points->at(k + 5);
+            currentY = points->at(k + 6);
+            ctrlPointX = currentX;
+            ctrlPointY = currentY;
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Unsupported command: %c", cmd);
+            break;
+        }
+        previousCmd = cmd;
+    }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h
new file mode 100644
index 0000000..21c1cdc
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_VECTORDRAWABLE_UTILS_H
+#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+
+#include "VectorDrawablePath.h"
+
+#include <cutils/compiler.h>
+#include "SkPath.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class VectorDrawableUtils {
+public:
+    ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo);
+    ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom,
+            const PathData& morphTo, float fraction);
+    ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data);
+    static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to,
+            float fraction);
+};
+} // namespace uirenderer
+} // namespace android
+#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index c9586e4..bd6af78 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -86,6 +86,9 @@
     mLocked.pointerIconChanged = false;
     mLocked.requestedPointerShape = mPolicy->getDefaultPointerIconId();
 
+    mLocked.animationFrameIndex = 0;
+    mLocked.lastFrameUpdatedTime = 0;
+
     mLocked.buttonState = 0;
 
     loadResources();
@@ -239,7 +242,8 @@
     AutoMutex _l(mLock);
 
     if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) {
-        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources);
+        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                              &mLocked.animationResources);
     }
 
     if (mLocked.presentation != presentation) {
@@ -402,7 +406,7 @@
     updatePointerLocked();
 }
 
-void PointerController::updatePointerShape(int iconId) {
+void PointerController::updatePointerShape(int32_t iconId) {
     AutoMutex _l(mLock);
     if (mLocked.requestedPointerShape != iconId) {
         mLocked.requestedPointerShape = iconId;
@@ -462,8 +466,17 @@
 void PointerController::doAnimate(nsecs_t timestamp) {
     AutoMutex _l(mLock);
 
-    bool keepAnimating = false;
     mLocked.animationPending = false;
+
+    bool keepFading = doFadingAnimationLocked(timestamp);
+    bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp);
+    if (keepFading || keepBitmapFlipping) {
+        startAnimationLocked();
+    }
+}
+
+bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) {
+    bool keepAnimating = false;
     nsecs_t frameDelay = timestamp - mLocked.animationTime;
 
     // Animate pointer fade.
@@ -501,10 +514,32 @@
             }
         }
     }
+    return keepAnimating;
+}
 
-    if (keepAnimating) {
-        startAnimationLocked();
+bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) {
+    std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(
+            mLocked.requestedPointerShape);
+    if (iter == mLocked.animationResources.end()) {
+        return false;
     }
+
+    if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
+        mSpriteController->openTransaction();
+
+        int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
+        mLocked.animationFrameIndex += incr;
+        mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
+        while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
+            mLocked.animationFrameIndex -= iter->second.animationFrames.size();
+        }
+        mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
+
+        mSpriteController->closeTransaction();
+    }
+
+    // Keep animating.
+    return true;
 }
 
 void PointerController::doInactivityTimeout() {
@@ -549,9 +584,16 @@
             if (mLocked.requestedPointerShape == mPolicy->getDefaultPointerIconId()) {
                 mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
             } else {
-                std::map<int, SpriteIcon>::const_iterator iter =
+                std::map<int32_t, SpriteIcon>::const_iterator iter =
                     mLocked.additionalMouseResources.find(mLocked.requestedPointerShape);
                 if (iter != mLocked.additionalMouseResources.end()) {
+                    std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
+                            mLocked.animationResources.find(mLocked.requestedPointerShape);
+                    if (anim_iter != mLocked.animationResources.end()) {
+                        mLocked.animationFrameIndex = 0;
+                        mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
+                        startAnimationLocked();
+                    }
                     mLocked.pointerSprite->setIcon(iter->second);
                 } else {
                     ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerShape);
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6d840db..b6c01d2 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -20,6 +20,7 @@
 #include "SpriteController.h"
 
 #include <map>
+#include <vector>
 
 #include <ui/DisplayInfo.h>
 #include <input/Input.h>
@@ -30,8 +31,6 @@
 #include <utils/String8.h>
 #include <gui/DisplayEventReceiver.h>
 
-#include <SkBitmap.h>
-
 namespace android {
 
 /*
@@ -43,6 +42,11 @@
     SpriteIcon spotAnchor;
 };
 
+struct PointerAnimation {
+    std::vector<SpriteIcon> animationFrames;
+    nsecs_t durationPerFrame;
+};
+
 /*
  * Pointer controller policy interface.
  *
@@ -59,7 +63,8 @@
 
 public:
     virtual void loadPointerResources(PointerResources* outResources) = 0;
-    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources) = 0;
+    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
+            std::map<int32_t, PointerAnimation>* outAnimationResources) = 0;
     virtual int32_t getDefaultPointerIconId() = 0;
 };
 
@@ -98,7 +103,7 @@
             const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
     virtual void clearSpots();
 
-    void updatePointerShape(int iconId);
+    void updatePointerShape(int32_t iconId);
     void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
     void setPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
@@ -145,6 +150,9 @@
         bool animationPending;
         nsecs_t animationTime;
 
+        size_t animationFrameIndex;
+        nsecs_t lastFrameUpdatedTime;
+
         int32_t displayWidth;
         int32_t displayHeight;
         int32_t displayOrientation;
@@ -162,7 +170,8 @@
         SpriteIcon pointerIcon;
         bool pointerIconChanged;
 
-        std::map<int, SpriteIcon> additionalMouseResources;
+        std::map<int32_t, SpriteIcon> additionalMouseResources;
+        std::map<int32_t, PointerAnimation> animationResources;
 
         int32_t requestedPointerShape;
 
@@ -178,6 +187,8 @@
     void handleMessage(const Message& message);
     int handleEvent(int fd, int events, void* data);
     void doAnimate(nsecs_t timestamp);
+    bool doFadingAnimationLocked(nsecs_t timestamp);
+    bool doBitmapAnimationLocked(nsecs_t timestamp);
     void doInactivityTimeout();
 
     void startAnimationLocked();
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 6bf5721..445ee6f 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -17,6 +17,8 @@
 package android.media;
 
 import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -128,6 +130,9 @@
     // use sLock to serialize the accesses.
     private static final Object sLock = new Object();
 
+    // Pattern to check non zero timestamp
+    private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
+
     /**
      * Reads Exif tags from the specified JPEG file.
      */
@@ -367,7 +372,8 @@
      */
     public long getDateTime() {
         String dateTimeString = mAttributes.get(TAG_DATETIME);
-        if (dateTimeString == null) return -1;
+        if (dateTimeString == null
+                || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
 
         ParsePosition pos = new ParsePosition(0);
         try {
@@ -402,7 +408,9 @@
     public long getGpsDateTime() {
         String date = mAttributes.get(TAG_GPS_DATESTAMP);
         String time = mAttributes.get(TAG_GPS_TIMESTAMP);
-        if (date == null || time == null) return -1;
+        if (date == null || time == null
+                || (!sNonZeroTimePattern.matcher(date).matches()
+                && !sNonZeroTimePattern.matcher(time).matches())) return -1;
 
         String dateTimeString = date + ' ' + time;
 
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 6c224e5..478fd99 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2179,6 +2179,7 @@
     // in media/hardware/CryptoAPI.h !
     public static final int CRYPTO_MODE_UNENCRYPTED = 0;
     public static final int CRYPTO_MODE_AES_CTR     = 1;
+    public static final int CRYPTO_MODE_AES_CBC     = 2;
 
     /**
      * Metadata describing the structure of a (at least partially) encrypted
@@ -2186,27 +2187,14 @@
      * A buffer's data is considered to be partitioned into "subSamples",
      * each subSample starts with a (potentially empty) run of plain,
      * unencrypted bytes followed by a (also potentially empty) run of
-     * encrypted bytes.
-     * numBytesOfClearData can be null to indicate that all data is encrypted.
-     * This information encapsulates per-sample metadata as outlined in
-     * ISO/IEC FDIS 23001-7:2011 "Common encryption in ISO base media file format files".
+     * encrypted bytes. If pattern encryption applies, each of the latter runs
+     * is encrypted only partly, according to a repeating pattern of "encrypt"
+     * and "skip" blocks. numBytesOfClearData can be null to indicate that all
+     * data is encrypted. This information encapsulates per-sample metadata as
+     * outlined in ISO/IEC FDIS 23001-7:2011 "Common encryption in ISO base
+     * media file format files".
      */
     public final static class CryptoInfo {
-        public void set(
-                int newNumSubSamples,
-                @NonNull int[] newNumBytesOfClearData,
-                @NonNull int[] newNumBytesOfEncryptedData,
-                @NonNull byte[] newKey,
-                @NonNull byte[] newIV,
-                int newMode) {
-            numSubSamples = newNumSubSamples;
-            numBytesOfClearData = newNumBytesOfClearData;
-            numBytesOfEncryptedData = newNumBytesOfEncryptedData;
-            key = newKey;
-            iv = newIV;
-            mode = newMode;
-        }
-
         /**
          * The number of subSamples that make up the buffer's contents.
          */
@@ -2220,7 +2208,7 @@
          */
         public int[] numBytesOfEncryptedData;
         /**
-         * A 16-byte opaque key
+         * A 16-byte key id
          */
         public byte[] key;
         /**
@@ -2229,10 +2217,84 @@
         public byte[] iv;
         /**
          * The type of encryption that has been applied,
-         * see {@link #CRYPTO_MODE_UNENCRYPTED} and {@link #CRYPTO_MODE_AES_CTR}.
+         * see {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR}
+         * and {@link #CRYPTO_MODE_AES_CBC}
          */
         public int mode;
 
+        /**
+         * Metadata describing encryption pattern for the protected bytes in a subsample.
+         */
+        public final static class Pattern {
+            /**
+             * Number of blocks to be encrypted in the pattern. If zero, pattern
+             * encryption is inoperative.
+             */
+            private int mEncryptBlocks;
+
+            /**
+             * Number of blocks to be skipped (left clear) in the pattern. If zero,
+             * pattern encryption is inoperative.
+             */
+            private int mSkipBlocks;
+
+            /**
+             * Construct a sample encryption pattern given the number of blocks to
+             * encrypt and skip in the pattern.
+             */
+            public Pattern(int blocksToEncrypt, int blocksToSkip) {
+                set(blocksToEncrypt, blocksToSkip);
+            }
+
+            /**
+             * Set the number of blocks to encrypt and skip in a sample encryption
+             * pattern.
+             */
+            public void set(int blocksToEncrypt, int blocksToSkip) {
+                mEncryptBlocks = blocksToEncrypt;
+                mSkipBlocks = blocksToSkip;
+            }
+
+            /**
+             * Return the number of blocks to skip in a sample encryption pattern.
+             */
+            public int getSkipBlocks() {
+                return mSkipBlocks;
+            }
+
+            /**
+             * Return the number of blocks to encrypt in a sample encryption pattern.
+             */
+            public int getEncryptBlocks() {
+                return mEncryptBlocks;
+            }
+        };
+
+        /**
+         * The pattern applicable to the protected data in each subsample.
+         */
+        private Pattern pattern;
+
+        public void set(
+                int newNumSubSamples,
+                @NonNull int[] newNumBytesOfClearData,
+                @NonNull int[] newNumBytesOfEncryptedData,
+                @NonNull byte[] newKey,
+                @NonNull byte[] newIV,
+                int newMode) {
+            numSubSamples = newNumSubSamples;
+            numBytesOfClearData = newNumBytesOfClearData;
+            numBytesOfEncryptedData = newNumBytesOfEncryptedData;
+            key = newKey;
+            iv = newIV;
+            mode = newMode;
+            pattern = new Pattern(0, 0);
+        }
+
+        public void setPattern(Pattern newPattern) {
+            pattern = newPattern;
+        }
+
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 1355635..3164930 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -166,7 +166,7 @@
         updateAppOpsPlayAudio();
         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
         mAppOpsCallback = new IAppOpsCallback.Stub() {
-            public void opChanged(int op, String packageName) {
+            public void opChanged(int op, int uid, String packageName) {
                 synchronized (mLock) {
                     if (op == AppOpsManager.OP_PLAY_AUDIO) {
                         updateAppOpsPlayAudio();
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index e0a8026..41b8ab2 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -44,7 +44,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -519,13 +518,10 @@
                     return;
                 }
 
-                List<MediaItem> data = list.getList();
+                List<MediaItem> data = list == null ? null : list.getList();
                 if (DBG) {
                     Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
                 }
-                if (data == null) {
-                    data = Collections.emptyList();
-                }
 
                 // Check that the subscription is still subscribed.
                 final Subscription subscription = mSubscriptions.get(parentId);
@@ -730,10 +726,9 @@
          * Called when the list of children is loaded or updated.
          *
          * @param parentId The media id of the parent media item.
-         * @param children The children which were loaded.
+         * @param children The children which were loaded, or null if the id is invalid.
          */
-        public void onChildrenLoaded(@NonNull String parentId,
-                                     @NonNull List<MediaItem> children) {
+        public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children) {
         }
 
         /**
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 043b80e..6197c70 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -931,9 +931,7 @@
      *
      * @param rating The {@link TvContentRating} to check.
      * @return {@code true} if this object contains {@code rating}, {@code false} otherwise.
-     * @hide
      */
-    @SystemApi
     public final boolean contains(@NonNull TvContentRating rating) {
         Preconditions.checkNotNull(rating);
         if (!rating.getMainRating().equals(mRating)) {
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 1bb99ff..f2c6a6c 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -469,10 +469,6 @@
                 = new Result<List<MediaBrowser.MediaItem>>(parentId) {
             @Override
             void onResultSent(List<MediaBrowser.MediaItem> list) {
-                if (list == null) {
-                    throw new IllegalStateException("onLoadChildren sent null list for id "
-                            + parentId);
-                }
                 if (mConnections.get(connection.callbacks.asBinder()) != connection) {
                     if (DBG) {
                         Log.d(TAG, "Not sending onLoadChildren result for connection that has"
@@ -481,7 +477,8 @@
                     return;
                 }
 
-                final ParceledListSlice<MediaBrowser.MediaItem> pls = new ParceledListSlice(list);
+                final ParceledListSlice<MediaBrowser.MediaItem> pls =
+                        list == null ? null : new ParceledListSlice(list);
                 try {
                     connection.callbacks.onLoadChildren(parentId, pls);
                 } catch (RemoteException ex) {
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index e8f680f..49b579c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -834,29 +834,36 @@
         env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
     CHECK(constructID != NULL);
 
-    jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error");
+    const char *defaultMsg = "Unknown Error";
 
     /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
     switch (err) {
         case ERROR_DRM_NO_LICENSE:
             err = gCryptoErrorCodes.cryptoErrorNoKey;
+            defaultMsg = "Crypto key not available";
             break;
         case ERROR_DRM_LICENSE_EXPIRED:
             err = gCryptoErrorCodes.cryptoErrorKeyExpired;
+            defaultMsg = "License expired";
             break;
         case ERROR_DRM_RESOURCE_BUSY:
             err = gCryptoErrorCodes.cryptoErrorResourceBusy;
+            defaultMsg = "Resource busy or unavailable";
             break;
         case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
             err = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
+            defaultMsg = "Required output protections are not active";
             break;
         case ERROR_DRM_SESSION_NOT_OPENED:
             err = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
+            defaultMsg = "Attempted to use a closed session";
             break;
         default:  /* Other negative DRM error codes go out as is. */
             break;
     }
 
+    jstring msgObj = env->NewStringUTF(msg != NULL ? msg : defaultMsg);
+
     jthrowable exception =
         (jthrowable)env->NewObject(clazz.get(), constructID, err, msgObj);
 
diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml
index e67c554..ccf1501 100644
--- a/packages/DefaultContainerService/AndroidManifest.xml
+++ b/packages/DefaultContainerService/AndroidManifest.xml
@@ -12,7 +12,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
 
     <application android:label="@string/service_name"
-                 android:allowBackup="false">
+                 android:allowBackup="false"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
 
         <service android:name=".DefaultContainerService"
                  android:enabled="true"
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index d45345e..595928a 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -71,7 +71,7 @@
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
             <intent-filter>
-                <action android:name="android.provider.action.BROWSE_DOCUMENT_ROOT" />
+                <action android:name="android.provider.action.BROWSE" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.document/root" />
             </intent-filter>
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/packages/DocumentsUI/res/color/item_doc_grid_border.xml
similarity index 78%
copy from core/res/res/drawable/non_client_decor_title.xml
copy to packages/DocumentsUI/res/color/item_doc_grid_border.xml
index e50daea..e144af8 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/packages/DocumentsUI/res/color/item_doc_grid_border.xml
@@ -15,7 +15,9 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_window_focused="true"
-          android:drawable="@drawable/non_client_decor_title_focused" />
-    <item android:drawable="@drawable/non_client_decor_title_unfocused" />
+    <item
+        android:state_focused="true"
+        android:color="?android:attr/colorAccent"/>
+    <item
+        android:color="@android:color/transparent" />
 </selector>
diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
deleted file mode 100644
index bf19d4e..0000000
--- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
+++ /dev/null
@@ -1,28 +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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:state_activated="true"
-        android:color="?android:attr/colorAccent"
-        android:alpha="0.1" />
-    <item
-        android:state_enabled="false"
-        android:color="?android:attr/colorBackground"
-        android:alpha="0.5" />
-    <item
-        android:color="@android:color/transparent" />
-</selector>
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png
deleted file mode 100644
index f3007c2..0000000
--- a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png
deleted file mode 100644
index 16f2ab9..0000000
--- a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png
deleted file mode 100644
index 0885320..0000000
--- a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png
deleted file mode 100644
index 083bbcc..0000000
--- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png
deleted file mode 100644
index 74b1ca5..0000000
--- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png
+++ /dev/null
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_home.xml
new file mode 100644
index 0000000..0a258ac
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20 6h-8l-2-2H4c-1.1 0-1.99 .9 -1.99 2L2 18c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4
+8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/drawable/item_doc_grid_border.xml
similarity index 78%
rename from packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml
rename to packages/DocumentsUI/res/drawable/item_doc_grid_border.xml
index 3f50fb9..db66094 100644
--- a/packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml
+++ b/packages/DocumentsUI/res/drawable/item_doc_grid_border.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<?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.
@@ -16,5 +16,7 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/item_doc_grid_overlay" />
+    <stroke
+        android:width="2dp"
+        android:color="@color/item_doc_grid_border"/>
 </shape>
diff --git a/packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml b/packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml
deleted file mode 100644
index d40de1e..0000000
--- a/packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
-    <item android:state_focused="true" android:state_activated="true" android:drawable="@drawable/ic_grid_selection_check" />
-    <item android:state_focused="false" android:state_activated="true" android:drawable="@drawable/ic_grid_selection_check" />
-    <item android:drawable="@android:color/transparent" />
-</selector>
diff --git a/packages/DocumentsUI/res/drawable/item_root_background.xml b/packages/DocumentsUI/res/drawable/item_root_background.xml
deleted file mode 100644
index c403159..0000000
--- a/packages/DocumentsUI/res/drawable/item_root_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:state_activated="true">
-        <color android:color="@color/material_grey_300" />
-    </item>
-    <item android:state_focused="false" android:state_activated="true">
-        <color android:color="@color/material_grey_300" />
-    </item>
-</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 231e110..fe06eaf 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -17,8 +17,9 @@
 <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:orientation="horizontal">
+    android:background="@color/item_doc_background"
+    android:orientation="horizontal"
+    android:focusable="true">
 
     <View
         android:id="@+id/focus_indicator"
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index 0dac0d5..0146f14 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -61,7 +61,7 @@
             android:layout_gravity="start"
             android:orientation="vertical"
             android:elevation="16dp"
-            android:background="@*android:color/white">
+            android:background="@color/window_background">
 
             <Toolbar
                 android:id="@+id/roots_toolbar"
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 403c667..3135977 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -50,9 +50,7 @@
             android:layout_height="0dp"
             android:layout_weight="1"
             android:orientation="horizontal"
-            android:baselineAligned="false"
-            android:divider="?android:attr/dividerVertical"
-            android:showDividers="middle">
+            android:baselineAligned="false">
 
             <FrameLayout
                 android:id="@+id/container_roots"
@@ -62,8 +60,7 @@
             <include layout="@layout/directory_cluster"
                 android:layout_width="0dp"
                 android:layout_weight="1"
-                android:elevation="8dp"
-                android:background="@color/material_grey_50" />
+                android:elevation="8dp" />
 
         </LinearLayout>
 
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index ada7f49..f9bbccb 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -17,7 +17,6 @@
 <com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/material_grey_50"
     android:orientation="vertical"
     android:animateLayoutChanges="true">
 
@@ -78,8 +77,7 @@
             android:paddingBottom="0dp"
             android:clipToPadding="false"
             android:scrollbarStyle="outsideOverlay"
-            android:drawSelectorOnTop="true"
-            android:background="@color/directory_background" />
+            android:drawSelectorOnTop="true" />
 
     </FrameLayout>
 
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index 71e618b..dcd5cfd 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -14,130 +14,110 @@
      limitations under the License.
 -->
 
-<com.android.documentsui.GridItem xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/grid_item_height"
+    android:layout_height="wrap_content"
     android:layout_margin="@dimen/grid_item_margin"
-    android:background="@color/item_doc_grid_background">
+    android:background="@color/item_doc_background"
+    android:focusable="true">
 
-    <ImageView
-        android:id="@+id/icon_thumb"
+    <!-- Main item thumbnail.  Comprised of two overlapping images, the
+         visibility of which is controlled by code in
+         DirectoryFragment.java. -->
+
+    <FrameLayout
+        android:id="@+id/thumbnail"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:scaleType="centerCrop"
-        android:contentDescription="@null" />
+        android:layout_height="wrap_content">
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
+        <com.android.documentsui.GridItemThumbnail
+            android:id="@+id/icon_thumb"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scaleType="centerCrop"
+            android:contentDescription="@null" />
 
-        <ImageView
+        <com.android.documentsui.GridItemThumbnail
             android:id="@+id/icon_mime"
             android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
+            android:layout_height="wrap_content"
             android:scaleType="centerInside"
             android:contentDescription="@null" />
 
-        <LinearLayout
-            android:layout_width="match_parent"
+    </FrameLayout>
+
+    <!-- Item nameplate.  Has a mime-type icon and some text fields (title,
+         size, mod-time, etc). -->
+
+    <RelativeLayout
+        android:id="@+id/nameplate"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/thumbnail"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:paddingLeft="12dp"
+        android:paddingRight="12dp">
+
+        <ImageView
+            android:id="@android:id/icon1"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="8dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:scaleType="centerInside"
+            android:contentDescription="@null"/>
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="@color/item_doc_grid_protect_background"
-            android:orientation="vertical"
-            android:paddingStart="16dp"
-            android:paddingEnd="12dp"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp">
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@android:id/icon1"
+            android:singleLine="true"
+            android:ellipsize="middle"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+            android:textColor="@*android:color/primary_text_default_material_light" />
 
-            <LinearLayout
-                android:id="@+id/line1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="horizontal"
-                android:baselineAligned="false">
+        <TextView
+            android:id="@+id/size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toEndOf="@android:id/icon1"
+            android:layout_below="@android:id/title"
+            android:layout_marginEnd="4dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption"
+            android:textColor="@*android:color/primary_text_default_material_light" />
 
-                <TextView
-                    android:id="@android:id/title"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:singleLine="true"
-                    android:ellipsize="middle"
-                    android:textAlignment="viewStart"
-                    android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-                    android:textColor="@*android:color/primary_text_default_material_dark" />
+        <TextView
+            android:id="@+id/date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_toEndOf="@id/size"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption"
+            android:textColor="@*android:color/primary_text_default_material_light" />
 
-                <ImageView
-                    android:id="@android:id/icon1"
-                    android:layout_width="@dimen/root_icon_size"
-                    android:layout_height="@dimen/root_icon_size"
-                    android:layout_marginStart="8dp"
-                    android:scaleType="centerInside"
-                    android:contentDescription="@null" />
+    </RelativeLayout>
 
-            </LinearLayout>
-
-            <LinearLayout
-                android:id="@+id/line2"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical"
-                android:orientation="horizontal"
-                android:baselineAligned="false">
-
-                <TextView
-                    android:id="@+id/date"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="0.5"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:textAlignment="viewStart"
-                    android:textAppearance="@android:style/TextAppearance.Material.Caption"
-                    android:textColor="@*android:color/primary_text_default_material_dark" />
-
-                <TextView
-                    android:id="@+id/size"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="0.5"
-                    android:layout_marginStart="8dp"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:textAlignment="viewStart"
-                    android:textAppearance="@android:style/TextAppearance.Material.Caption"
-                    android:textColor="@*android:color/primary_text_default_material_dark" />
-
-                <ImageView
-                    android:id="@android:id/icon2"
-                    android:layout_width="@dimen/root_icon_size"
-                    android:layout_height="@dimen/root_icon_size"
-                    android:layout_marginStart="8dp"
-                    android:scaleType="centerInside"
-                    android:contentDescription="@null"
-                    android:visibility="gone" />
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:src="@drawable/item_doc_grid_overlay"
+    <!-- An overlay that draws the item border when it is focused. -->
+    <View
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@id/nameplate"
+        android:layout_alignTop="@id/thumbnail"
+        android:layout_alignLeft="@id/thumbnail"
+        android:layout_alignRight="@id/thumbnail"
         android:contentDescription="@null"
+        android:background="@drawable/item_doc_grid_border"
         android:duplicateParentState="true" />
 
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:src="@drawable/item_doc_grid_overlay_icon"
-        android:scaleType="center"
-        android:contentDescription="@null"
-        android:duplicateParentState="true" />
-
-</com.android.documentsui.GridItem>
+</RelativeLayout>
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index eba00a6..e068423 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -17,9 +17,10 @@
 <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:orientation="horizontal">
-  
+    android:background="@color/item_doc_background"
+    android:orientation="horizontal"
+    android:focusable="true">
+
     <View
         android:id="@+id/focus_indicator"
         android:layout_width="4dp"
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 90b1ff6..ff80d07 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -22,8 +22,7 @@
     android:paddingEnd="@dimen/list_item_padding"
     android:gravity="center_vertical"
     android:orientation="horizontal"
-    android:baselineAligned="false"
-    android:background="@drawable/item_root_background">
+    android:baselineAligned="false">
 
     <FrameLayout
         android:layout_width="@dimen/icon_size"
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index a376418..153c673 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -17,14 +17,20 @@
 <resources>
     <color name="material_grey_400">#ffbdbdbd</color>
 
+    <!-- This is the window background, but also the background for anything
+         else that needs to manually declare a background matching the "default"
+         app background (e.g. the drawer overlay). -->
+    <color name="window_background">#fff1f1f1</color>
+
     <color name="primary_dark">@*android:color/primary_dark_material_dark</color>
     <color name="primary">@*android:color/material_blue_grey_900</color>
     <color name="accent">@*android:color/accent_material_light</color>
     <color name="action_mode">@color/material_grey_400</color>
-    
-    <color name="directory_background">@*android:color/material_grey_300</color>
-    <color name="item_doc_grid_background">@android:color/white</color>
-    <color name="item_doc_grid_protect_background">#88000000</color>
+
     <color name="band_select_background">#88ffffff</color>
     <color name="band_select_border">#44000000</color>
+
+    <color name="item_doc_background">#fffafafa</color>
+    <color name="item_doc_background_selected">#ffe0f2f1</color>
+
 </resources>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index 15d17cc..6712e2d 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -25,6 +25,7 @@
         <item name="actionBarTheme">@style/ActionBarTheme</item>
         <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
 
+        <item name="android:windowBackground">@color/window_background</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorAccent">@color/accent</item>
@@ -44,6 +45,7 @@
         <item name="actionBarTheme">@style/ActionBarTheme</item>
         <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
 
+        <item name="android:windowBackground">@color/window_background</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorAccent">@color/accent</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0ee970d..91ac033 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -358,18 +358,6 @@
         return mState;
     }
 
-    public static abstract class DocumentsIntent {
-        /** Intent action name to open copy destination. */
-        public static String ACTION_OPEN_COPY_DESTINATION =
-                "com.android.documentsui.OPEN_COPY_DESTINATION";
-
-        /**
-         * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
-         * specifies if the destination directory needs to create new directory or not.
-         */
-        public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
-    }
-
     void setDisplayAdvancedDevices(boolean display) {
         LocalPreferences.setDisplayAdvancedDevices(this, display);
         mState.showAdvanced = mState.forceAdvanced | display;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55a123f..55e2f44 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -179,8 +179,7 @@
             if (mFailedFiles.size() > 0) {
                 Log.e(TAG, mFailedFiles.size() + " files failed to copy");
                 final Context context = getApplicationContext();
-                final Intent navigateIntent = new Intent(context, FilesActivity.class);
-                navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+                final Intent navigateIntent = buildNavigateIntent(context, stack);
                 navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
                 navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
                 navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -228,8 +227,7 @@
         mIsCancelled = false;
 
         final Context context = getApplicationContext();
-        final Intent navigateIntent = new Intent(context, FilesActivity.class);
-        navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+        final Intent navigateIntent = buildNavigateIntent(context, stack);
 
         final String contentTitle = getString(copying ? R.string.copy_notification_title
                 : R.string.move_notification_title);
@@ -592,4 +590,14 @@
             }
         }
     }
+
+    /**
+     * Creates an intent for navigating back to the destination directory.
+     */
+    private Intent buildNavigateIntent(Context context, DocumentStack stack) {
+        Intent intent = new Intent(context, FilesActivity.class);
+        intent.setAction(DocumentsContract.ACTION_BROWSE);
+        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+        return intent;
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 13c481c..4f4649c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -19,7 +19,7 @@
 import static com.android.documentsui.State.ACTION_CREATE;
 import static com.android.documentsui.State.ACTION_GET_CONTENT;
 import static com.android.documentsui.State.ACTION_OPEN;
-import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
 import static com.android.documentsui.State.ACTION_OPEN_TREE;
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 
@@ -123,7 +123,7 @@
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
         } else if (mState.action == ACTION_OPEN_TREE ||
-                   mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                   mState.action == ACTION_PICK_COPY_DESTINATION) {
             PickFragment.show(getFragmentManager());
         }
 
@@ -135,7 +135,7 @@
         } else if (mState.action == ACTION_OPEN ||
                    mState.action == ACTION_CREATE ||
                    mState.action == ACTION_OPEN_TREE ||
-                   mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                   mState.action == ACTION_PICK_COPY_DESTINATION) {
             RootsFragment.show(getFragmentManager(), null);
         }
 
@@ -163,8 +163,8 @@
             state.action = ACTION_GET_CONTENT;
         } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
             state.action = ACTION_OPEN_TREE;
-        } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
-            state.action = ACTION_OPEN_COPY_DESTINATION;
+        } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
+            state.action = ACTION_PICK_COPY_DESTINATION;
         }
 
         if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
@@ -172,9 +172,14 @@
                     Intent.EXTRA_ALLOW_MULTIPLE, false);
         }
 
-        if (state.action == ACTION_OPEN_COPY_DESTINATION) {
+        if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
+                || state.action == ACTION_CREATE) {
+            state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
+        }
+
+        if (state.action == ACTION_PICK_COPY_DESTINATION) {
             state.directoryCopy = intent.getBooleanExtra(
-                    BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
+                    Shared.EXTRA_DIRECTORY_COPY, false);
             state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
                     CopyService.TRANSFER_MODE_COPY);
         }
@@ -257,7 +262,7 @@
                     mState.action == ACTION_OPEN_TREE) {
                     mRootsToolbar.setTitle(R.string.title_open);
                 } else if (mState.action == ACTION_CREATE ||
-                           mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                           mState.action == ACTION_PICK_COPY_DESTINATION) {
                     mRootsToolbar.setTitle(R.string.title_save);
                 }
             }
@@ -324,7 +329,7 @@
         boolean recents = cwd == null;
         boolean picking = mState.action == ACTION_CREATE
                 || mState.action == ACTION_OPEN_TREE
-                || mState.action == ACTION_OPEN_COPY_DESTINATION;
+                || mState.action == ACTION_PICK_COPY_DESTINATION;
 
         createDir.setVisible(picking && !recents && cwd.isCreateSupported());
         mSearchManager.showMenu(!picking);
@@ -361,7 +366,7 @@
             // No directory means recents
             if (mState.action == ACTION_CREATE ||
                 mState.action == ACTION_OPEN_TREE ||
-                mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                mState.action == ACTION_PICK_COPY_DESTINATION) {
                 RecentsCreateFragment.show(fm);
             } else {
                 DirectoryFragment.showRecentsOpen(fm, anim);
@@ -391,7 +396,7 @@
         }
 
         if (mState.action == ACTION_OPEN_TREE ||
-            mState.action == ACTION_OPEN_COPY_DESTINATION) {
+            mState.action == ACTION_PICK_COPY_DESTINATION) {
             final PickFragment pick = PickFragment.get(fm);
             if (pick != null) {
                 pick.setPickTarget(mState.action, mState.transferMode, cwd);
@@ -444,7 +449,7 @@
         if (mState.action == ACTION_OPEN_TREE) {
             result = DocumentsContract.buildTreeDocumentUri(
                     pickTarget.authority, pickTarget.documentId);
-        } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+        } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
             result = pickTarget.derivedUri;
         } else {
             // Should not be reached.
@@ -461,7 +466,7 @@
         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
         if (mState.action == ACTION_CREATE ||
             mState.action == ACTION_OPEN_TREE ||
-            mState.action == ACTION_OPEN_COPY_DESTINATION) {
+            mState.action == ACTION_PICK_COPY_DESTINATION) {
             // Remember stack for last create
             values.clear();
             values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -500,7 +505,7 @@
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-        } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+        } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
             intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 49dae3d..1b5b60de 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -117,6 +117,8 @@
         public MotionInputEvent(MotionEvent event, RecyclerView view) {
             mEvent = event;
             mView = view;
+
+            // Consider determining position lazily as an optimization.
             View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY());
             mPosition = (child != null)
                     ? mView.getChildAdapterPosition(child)
diff --git a/packages/DocumentsUI/src/com/android/documentsui/GridItem.java b/packages/DocumentsUI/src/com/android/documentsui/GridItemThumbnail.java
similarity index 80%
rename from packages/DocumentsUI/src/com/android/documentsui/GridItem.java
rename to packages/DocumentsUI/src/com/android/documentsui/GridItemThumbnail.java
index 990dca7..38d6d72 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/GridItem.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/GridItemThumbnail.java
@@ -18,22 +18,21 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 /**
- * A FrameLayout subclass used by DirectoryFragment. Ensures that the resulting grid item is always
- * square.
+ * Ensures that grid thumbnails are always square.
  */
-public class GridItem extends FrameLayout {
-    public GridItem(Context context) {
+public class GridItemThumbnail extends ImageView {
+    public GridItemThumbnail(Context context) {
         super(context);
     }
 
-    public GridItem(Context context, AttributeSet attrs) {
+    public GridItemThumbnail(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public GridItem(Context context, AttributeSet attrs, int defStyle) {
+    public GridItemThumbnail(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 48e28dc..bbf4682 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -110,7 +110,7 @@
                 mPick.setText(R.string.button_select);
                 mCancel.setVisibility(View.GONE);
                 break;
-            case State.ACTION_OPEN_COPY_DESTINATION:
+            case State.ACTION_PICK_COPY_DESTINATION:
                 mPick.setText(R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 4fc3788..72ee6cbab 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -360,7 +360,7 @@
 
             // Exclude read-only devices when creating
             if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
-            if (state.action == State.ACTION_OPEN_COPY_DESTINATION && !supportsCreate) continue;
+            if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
             // Exclude roots that don't support directory picking
             if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
             // Exclude advanced devices when not requested
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index beff196..4c844c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
 
             for (final RootInfo root : roots) {
                 final RootItem item = new RootItem(root);
-                if (root.isLibrary()) {
+                if (root.isLibrary() || root.isHome()) {
                     libraries.add(item);
                 } else {
                     others.add(item);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index a4d6dc5..570c9bf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -20,6 +20,16 @@
 
 /** @hide */
 public final class Shared {
+    /** Intent action name to pick a copy destination. */
+    public static final String ACTION_PICK_COPY_DESTINATION =
+            "com.android.documentsui.PICK_COPY_DESTINATION";
+
+    /**
+     * Extra boolean flag for {@link ACTION_PICK_COPY_DESTINATION}, which
+     * specifies if the destination directory needs to create new directory or not.
+     */
+    public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
+
     public static final boolean DEBUG = true;
     public static final String TAG = "Documents";
     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 4306a0e..c81d4fb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -52,6 +52,7 @@
     public boolean stackTouched;
     public boolean restored;
     public boolean directoryCopy;
+    public boolean openableOnly;
     /** Transfer mode for file copy/move operations. */
     public int transferMode;
 
@@ -75,7 +76,7 @@
     public static final int ACTION_OPEN_TREE = 4;
     public static final int ACTION_MANAGE = 5;
     public static final int ACTION_BROWSE = 6;
-    public static final int ACTION_OPEN_COPY_DESTINATION = 8;
+    public static final int ACTION_PICK_COPY_DESTINATION = 8;
 
     public static final int MODE_UNKNOWN = 0;
     public static final int MODE_LIST = 1;
@@ -119,6 +120,7 @@
         out.writeMap(dirState);
         out.writeList(selectedDocumentsForCopy);
         out.writeList(excludedAuthorities);
+        out.writeInt(openableOnly ? 1 : 0);
     }
 
     public static final Creator<State> CREATOR = new Creator<State>() {
@@ -142,6 +144,7 @@
             in.readMap(state.dirState, null);
             in.readList(state.selectedDocumentsForCopy, null);
             in.readList(state.excludedAuthorities, null);
+            state.openableOnly = in.readInt() != 0;
             return state;
         }
 
@@ -150,4 +153,4 @@
             return new State[size];
         }
     };
-}
\ No newline at end of file
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 108d5e8..b0421b0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -111,18 +111,8 @@
 import com.android.documentsui.State;
 import com.android.documentsui.ThumbnailCache;
 import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.BaseActivity.DocumentsIntent;
 import com.android.documentsui.ProviderExecutor.Preemptable;
-import com.android.documentsui.R.animator;
-import com.android.documentsui.R.attr;
-import com.android.documentsui.R.bool;
-import com.android.documentsui.R.dimen;
-import com.android.documentsui.R.drawable;
-import com.android.documentsui.R.id;
-import com.android.documentsui.R.layout;
-import com.android.documentsui.R.menu;
-import com.android.documentsui.R.plurals;
-import com.android.documentsui.R.string;
+import com.android.documentsui.Shared;
 import com.android.documentsui.RecentsProvider.StateColumns;
 import com.android.documentsui.dirlist.MultiSelectManager.Callback;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
@@ -184,7 +174,7 @@
     private Point mThumbSize;
     private DocumentsAdapter mAdapter;
     private LoaderCallbacks<DirectoryResult> mCallbacks;
-    private FragmentTuner mFragmentTuner;
+    private FragmentTuner mTuner;
     private DocumentClipper mClipper;
     // These are lazily initialized.
     private LinearLayoutManager mListLayout;
@@ -319,7 +309,7 @@
         super.onActivityCreated(savedInstanceState);
 
         final Context context = getActivity();
-        final State state = getDisplayState(DirectoryFragment.this);
+        final State state = getDisplayState();
 
         final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
         final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
@@ -381,7 +371,7 @@
         mType = getArguments().getInt(EXTRA_TYPE);
         mStateKey = buildStateKey(root, doc);
 
-        mFragmentTuner = FragmentTuner.pick(state);
+        mTuner = FragmentTuner.pick(state);
         mClipper = new DocumentClipper(context);
 
         if (mType == TYPE_RECENT_OPEN) {
@@ -485,7 +475,7 @@
             return;
         }
 
-        CopyService.start(getActivity(), getDisplayState(this).selectedDocumentsForCopy,
+        CopyService.start(getActivity(), getDisplayState().selectedDocumentsForCopy,
                 (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
                 data.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_COPY));
     }
@@ -524,7 +514,7 @@
         checkNotNull(cursor, "Cursor cannot be null.");
         final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
         final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-        if (isDocumentEnabled(docMimeType, docFlags)) {
+        if (mTuner.isDocumentEnabled(docMimeType, docFlags)) {
             final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
             ((BaseActivity) getActivity()).onDocumentPicked(doc, mModel);
             mSelectionManager.clearSelection();
@@ -540,7 +530,7 @@
         // Remember last scroll location
         final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
         getView().saveHierarchyState(container);
-        final State state = getDisplayState(this);
+        final State state = getDisplayState();
         state.dirState.put(mStateKey, container);
     }
 
@@ -562,7 +552,7 @@
 
     public void onUserModeChanged() {
         final ContentResolver resolver = getActivity().getContentResolver();
-        final State state = getDisplayState(this);
+        final State state = getDisplayState();
 
         final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
         final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
@@ -591,7 +581,7 @@
     }
 
     private void updateDisplayState() {
-        final State state = getDisplayState(this);
+        final State state = getDisplayState();
 
         if (mLastMode == state.derivedMode && mLastShowSize == state.showSize) return;
         mLastMode = state.derivedMode;
@@ -665,13 +655,12 @@
 
         @Override
         public boolean onBeforeItemStateChange(int position, boolean selected) {
-            // Directories cannot be checked
             if (selected) {
                 final Cursor cursor = mModel.getItem(position);
                 checkNotNull(cursor, "Cursor cannot be null.");
                 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-                return isDocumentEnabled(docMimeType, docFlags);
+                return mTuner.canSelectType(docMimeType, docFlags);
             }
             return true;
         }
@@ -743,7 +732,7 @@
         private void updateActionMenu() {
             checkNotNull(mMenu);
             // Delegate update logic to our owning action, since specialized logic is desired.
-            mFragmentTuner.updateActionMenu(mMenu, mType, mNoDeleteCount == 0);
+            mTuner.updateActionMenu(mMenu, mType, mNoDeleteCount == 0);
             Menus.disableHiddenItems(mMenu);
         }
 
@@ -907,7 +896,7 @@
         // Pop up a dialog to pick a destination.  This is inadequate but works for now.
         // TODO: Implement a picker that is to spec.
         final Intent intent = new Intent(
-                BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
+                Shared.ACTION_PICK_COPY_DESTINATION,
                 Uri.EMPTY,
                 getActivity(),
                 DocumentsActivity.class);
@@ -915,7 +904,7 @@
         new GetDocumentsTask() {
             @Override
             void onDocumentsReady(List<DocumentInfo> docs) {
-                getDisplayState(DirectoryFragment.this).selectedDocumentsForCopy = docs;
+                getDisplayState().selectedDocumentsForCopy = docs;
 
                 boolean directoryCopy = false;
                 for (DocumentInfo info : docs) {
@@ -924,15 +913,15 @@
                         break;
                     }
                 }
-                intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
+                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
                 intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
                 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
             }
         }.execute(selected);
     }
 
-    private static State getDisplayState(Fragment fragment) {
-        return ((BaseActivity) fragment.getActivity()).getDisplayState();
+    private State getDisplayState() {
+        return ((BaseActivity) getActivity()).getDisplayState();
     }
 
     // Provide a reference to the views for each data item
@@ -948,16 +937,11 @@
 
         public DocumentHolder(View view) {
             super(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.
-            view.setFocusable(true);
             view.setOnKeyListener(this);
         }
 
         public void setSelected(boolean selected) {
             itemView.setActivated(selected);
-            itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
         }
 
         @Override
@@ -1027,7 +1011,7 @@
 
         @Override
         public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            final State state = getDisplayState(DirectoryFragment.this);
+            final State state = getDisplayState();
             final LayoutInflater inflater = LayoutInflater.from(getContext());
             View item = null;
             switch (state.derivedMode) {
@@ -1070,8 +1054,7 @@
         public void onBindViewHolder(DocumentHolder holder, int position) {
 
             final Context context = getContext();
-            final State state = getDisplayState(DirectoryFragment.this);
-            final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
+            final State state = getDisplayState();
             final RootsCache roots = DocumentsApplication.getRootsCache(context);
             final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                     context, mThumbSize);
@@ -1095,14 +1078,10 @@
 
             holder.setSelected(isSelected(position));
 
-            final View line1 = itemView.findViewById(R.id.line1);
-            final View line2 = itemView.findViewById(R.id.line2);
-
             final ImageView iconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
             final ImageView iconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
             final TextView title = (TextView) itemView.findViewById(android.R.id.title);
             final ImageView icon1 = (ImageView) itemView.findViewById(android.R.id.icon1);
-            final ImageView icon2 = (ImageView) itemView.findViewById(android.R.id.icon2);
             final TextView summary = (TextView) itemView.findViewById(android.R.id.summary);
             final TextView date = (TextView) itemView.findViewById(R.id.date);
             final TextView size = (TextView) itemView.findViewById(R.id.size);
@@ -1121,7 +1100,7 @@
                     || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
             final boolean showThumbnail = supportsThumbnail && allowThumbnail && !mSvelteRecents;
 
-            final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
+            final boolean enabled = mTuner.isDocumentEnabled(docMimeType, docFlags);
             final float iconAlpha = (state.derivedMode == MODE_LIST && !enabled) ? 0.5f : 1f;
 
             boolean cacheHit = false;
@@ -1155,13 +1134,11 @@
                         getDocumentIcon(mContext, docAuthority, docId, docMimeType, docIcon, state));
             }
 
-            boolean hasLine1 = false;
-            boolean hasLine2 = false;
-
-            final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles;
-            if (!hideTitle) {
+            if ((state.derivedMode == MODE_GRID) && mHideGridTitles) {
+                title.setVisibility(View.GONE);
+            } else {
                 title.setText(docDisplayName);
-                hasLine1 = true;
+                title.setVisibility(View.VISIBLE);
             }
 
             Drawable iconDrawable = null;
@@ -1169,11 +1146,7 @@
                 // We've already had to enumerate roots before any results can
                 // be shown, so this will never block.
                 final RootInfo root = roots.getRootBlocking(docAuthority, docRootId);
-                if (state.derivedMode == MODE_GRID) {
-                    iconDrawable = root.loadGridIcon(mContext);
-                } else {
-                    iconDrawable = root.loadIcon(mContext);
-                }
+                iconDrawable = root.loadIcon(mContext);
 
                 if (summary != null) {
                     final boolean alwaysShowSummary = getResources()
@@ -1181,7 +1154,6 @@
                     if (alwaysShowSummary) {
                         summary.setText(root.getDirectoryString());
                         summary.setVisibility(View.VISIBLE);
-                        hasLine2 = true;
                     } else {
                         if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
                             // No summary needed if icon speaks for itself
@@ -1190,7 +1162,6 @@
                             summary.setText(root.getDirectoryString());
                             summary.setVisibility(View.VISIBLE);
                             summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
-                            hasLine2 = true;
                         }
                     }
                 }
@@ -1207,58 +1178,37 @@
                     if (docSummary != null) {
                         summary.setText(docSummary);
                         summary.setVisibility(View.VISIBLE);
-                        hasLine2 = true;
                     } else {
                         summary.setVisibility(View.INVISIBLE);
                     }
                 }
             }
 
-            if (icon1 != null) icon1.setVisibility(View.GONE);
-            if (icon2 != null) icon2.setVisibility(View.GONE);
-
             if (iconDrawable != null) {
-                if (hasLine1) {
-                    icon1.setVisibility(View.VISIBLE);
-                    icon1.setImageDrawable(iconDrawable);
-                } else {
-                    icon2.setVisibility(View.VISIBLE);
-                    icon2.setImageDrawable(iconDrawable);
-                }
+                icon1.setVisibility(View.VISIBLE);
+                icon1.setImageDrawable(iconDrawable);
+            } else {
+                icon1.setVisibility(View.GONE);
             }
 
             if (docLastModified == -1) {
                 date.setText(null);
             } else {
                 date.setText(formatTime(mContext, docLastModified));
-                hasLine2 = true;
             }
 
-            if (state.showSize) {
-                size.setVisibility(View.VISIBLE);
-                if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
-                    size.setText(null);
-                } else {
-                    size.setText(Formatter.formatFileSize(mContext, docSize));
-                    hasLine2 = true;
-                }
-            } else {
+            if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
                 size.setVisibility(View.GONE);
-            }
-
-            if (line1 != null) {
-                line1.setVisibility(hasLine1 ? View.VISIBLE : View.GONE);
-            }
-            if (line2 != null) {
-                line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
+            } else {
+                size.setVisibility(View.VISIBLE);
+                size.setText(Formatter.formatFileSize(mContext, docSize));
             }
 
             setEnabledRecursive(itemView, enabled);
 
             iconMime.setAlpha(iconAlpha);
             iconThumb.setAlpha(iconAlpha);
-            if (icon1 != null) icon1.setAlpha(iconAlpha);
-            if (icon2 != null) icon2.setAlpha(iconAlpha);
+            icon1.setAlpha(iconAlpha);
 
             if (DEBUG_ENABLE_DND) {
                 setupDragAndDropOnDocumentView(itemView, cursor);
@@ -1330,22 +1280,6 @@
         }
     }
 
-    private boolean isDocumentEnabled(String docMimeType, int docFlags) {
-        final State state = getDisplayState(DirectoryFragment.this);
-
-        // Directories are always enabled
-        if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
-            return true;
-        }
-
-        // Read-only files are disabled when creating
-        if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
-            return false;
-        }
-
-        return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
-    }
-
     private void copyFromClipboard() {
         new AsyncTask<Void, Void, List<DocumentInfo>>() {
 
@@ -1393,7 +1327,7 @@
             return;
         }
 
-        final DocumentStack curStack = getDisplayState(DirectoryFragment.this).stack;
+        final DocumentStack curStack = getDisplayState().stack;
         DocumentStack tmpStack = new DocumentStack();
         if (destination != null) {
             tmpStack.push(destination);
@@ -1590,7 +1524,7 @@
         if (docs.size() == 1) {
             final DocumentInfo doc = docs.get(0);
             return getDocumentIcon(getActivity(), doc.authority, doc.documentId,
-                    doc.mimeType, doc.icon, getDisplayState(this));
+                    doc.mimeType, doc.icon, getDisplayState());
         }
         return getActivity().getDrawable(R.drawable.ic_doc_generic);
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
index 0963845..1135c21 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
@@ -25,6 +25,8 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.TypedValue;
 
+import com.android.documentsui.R;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -43,12 +45,8 @@
     private final Integer mSelectedColor;
 
     public DirectoryItemAnimator(Context context) {
-        mDefaultColor = context.getResources().getColor(android.R.color.transparent);
-        // Get the accent color.
-        TypedValue selColor = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
-        // Set the opacity to 10%.
-        mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+        mDefaultColor = context.getResources().getColor(R.color.item_doc_background);
+        mSelectedColor = context.getResources().getColor(R.color.item_doc_background_selected);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index 7e9bbe2..38d3805 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -17,39 +17,117 @@
 package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.State.ACTION_BROWSE;
+import static com.android.documentsui.State.ACTION_CREATE;
+import static com.android.documentsui.State.ACTION_GET_CONTENT;
 import static com.android.documentsui.State.ACTION_MANAGE;
+import static com.android.documentsui.State.ACTION_OPEN;
+import static com.android.documentsui.State.ACTION_OPEN_TREE;
 import static com.android.internal.util.Preconditions.checkArgument;
 
-import android.os.SystemProperties;
-import android.view.Menu;
-import android.view.MenuItem;
-
 import com.android.documentsui.Menus;
+import com.android.documentsui.MimePredicate;
 import com.android.documentsui.R;
 import com.android.documentsui.State;
 
+import android.os.SystemProperties;
+import android.provider.DocumentsContract.Document;
+import android.view.Menu;
+import android.view.MenuItem;
+
 /**
  * Providers support for specializing the DirectoryFragment to the "host" Activity.
  * Feel free to expand the role of this class to handle other specializations.
  */
 public abstract class FragmentTuner {
+
+    final State mState;
+
+    public FragmentTuner(State state) {
+        mState = state;
+    }
+
     public static FragmentTuner pick(State state) {
         switch (state.action) {
             case ACTION_BROWSE:
-                return new FilesTuner();
+                return new FilesTuner(state);
             case ACTION_MANAGE:
-                return new ManageTuner();
+                return new DownloadsTuner(state);
             default:
-                return new DocumentsTuner();
+                return new DocumentsTuner(state);
         }
     }
 
+
     public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete);
 
+    // Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
+    // A folder is enabled such that it may be double clicked, even in settings
+    // when the folder itself cannot be selected. This may also be true of container types.
+    public boolean canSelectType(String docMimeType, int docFlags) {
+        return true;
+    }
+
+    public boolean isDocumentEnabled(String docMimeType, int docFlags) {
+        if (isDirectory(docMimeType)) {
+            return true;
+        }
+
+        return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
+    }
+
     /**
      * Provides support for Platform specific specializations of DirectoryFragment.
      */
     private static final class DocumentsTuner extends FragmentTuner {
+
+        public DocumentsTuner(State state) {
+            super(state);
+        }
+
+        @Override
+        public boolean canSelectType(String docMimeType, int docFlags) {
+            if (!isDocumentEnabled(docMimeType, docFlags)) {
+                return false;
+            }
+
+            if (isDirectory(docMimeType)) {
+                return false;
+            }
+
+            if (mState.action == ACTION_OPEN_TREE) {
+                // In this case nothing *ever* is selectable...the expected user behavior is
+                // they navigate *into* a folder, then click a confirmation button indicating
+                // that the current directory is the directory they are picking.
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public boolean isDocumentEnabled(String docMimeType, int docFlags) {
+            // Directories are always enabled.
+            if (isDirectory(docMimeType)) {
+                return true;
+            }
+
+            switch (mState.action) {
+                case ACTION_CREATE:
+                    // Read-only files are disabled when creating.
+                    if ((docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
+                        return false;
+                    }
+                case ACTION_OPEN:
+                case ACTION_GET_CONTENT:
+                    final boolean isVirtual = (docFlags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+                    if (isVirtual && mState.openableOnly) {
+                        return false;
+                    }
+            }
+
+            return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
+        }
+
         @Override
         public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
 
@@ -77,7 +155,11 @@
     /**
      * Provides support for Platform specific specializations of DirectoryFragment.
      */
-    private static final class ManageTuner extends FragmentTuner {
+    private static final class DownloadsTuner extends FragmentTuner {
+
+        public DownloadsTuner(State state) {
+            super(state);
+        }
 
         @Override
         public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
@@ -107,6 +189,11 @@
      * Provides support for Files activity specific specializations of DirectoryFragment.
      */
     private static final class FilesTuner extends FragmentTuner {
+
+        public FilesTuner(State state) {
+            super(state);
+        }
+
         @Override
         public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
 
@@ -124,4 +211,8 @@
             Menus.disableHiddenItems(menu, copy, paste);
         }
     }
+
+    private static boolean isDirectory(String mimeType) {
+        return Document.MIME_TYPE_DIR.equals(mimeType);
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index b5a3b93..65e1a28 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -41,11 +41,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import com.android.documentsui.Events;
-import com.android.documentsui.R;
 import com.android.documentsui.Events.InputEvent;
 import com.android.documentsui.Events.MotionInputEvent;
-import com.android.documentsui.R.drawable;
+import com.android.documentsui.R;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -89,9 +87,7 @@
      * @param mode Selection mode
      */
     public MultiSelectManager(final RecyclerView recyclerView, int mode) {
-        this(recyclerView.getAdapter(), mode);
-
-        mEnvironment = new RuntimeSelectionEnvironment(recyclerView);
+        this(recyclerView.getAdapter(), new RuntimeSelectionEnvironment(recyclerView), mode);
 
         if (mode == MODE_MULTIPLE) {
             mBandManager = new BandController();
@@ -138,16 +134,15 @@
 
     /**
      * Constructs a new instance with {@code adapter} and {@code helper}.
+     * @param runtimeSelectionEnvironment
      * @hide
      */
     @VisibleForTesting
-    MultiSelectManager(Adapter<?> adapter, int mode) {
-        checkNotNull(adapter, "'adapter' cannot be null.");
-
+    MultiSelectManager(Adapter<?> adapter, SelectionEnvironment environment, int mode) {
+        mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
+        mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
         mSingleSelect = mode == MODE_SINGLE;
 
-        mAdapter = adapter;
-
         mAdapter.registerAdapterDataObserver(
                 new AdapterDataObserver() {
 
@@ -881,7 +876,7 @@
         void focusItem(int position);
     }
 
-    /** RvFacade implementation backed by good ol' RecyclerView. */
+    /** Recycler view facade implementation backed by good ol' RecyclerView. */
     private static final class RuntimeSelectionEnvironment implements SelectionEnvironment {
 
         private final RecyclerView mView;
@@ -1961,11 +1956,50 @@
             return false;
         }
 
-        int target = RecyclerView.NO_POSITION;
+        // Here we unpack information from the event and pass it to an more
+        // easily tested method....basically eliminating the need to synthesize
+        // events and views and so on in our tests.
+        int position = findTargetPosition(view, keyCode);
+        if (position == RecyclerView.NO_POSITION) {
+            // If there is no valid navigation target, don't handle the keypress.
+            return false;
+        }
+
+        return attemptChangePosition(position, event.isShiftPressed());
+    }
+
+    @VisibleForTesting
+    boolean attemptChangePosition(int targetPosition, boolean isShiftPressed) {
+        // Focus the new file.
+        mEnvironment.focusItem(targetPosition);
+
+        if (isShiftPressed) {
+            if (!hasSelection()) {
+                // If there is no selection, start a selection when the user presses shift-arrow.
+                toggleSelection(targetPosition);
+            } else if (!mSingleSelect) {
+                mRanger.snapSelection(targetPosition);
+                notifySelectionChanged();
+            } else {
+                // We're in single select and have an existing selection.
+                // Our best guess as to what the user would expect is to advance the selection.
+                clearSelection();
+                toggleSelection(targetPosition);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the adapter position that the key combo is targeted at.
+     */
+    private int findTargetPosition(View view, int keyCode) {
+        int position = RecyclerView.NO_POSITION;
         if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
-            target = 0;
+            position = 0;
         } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
-            target = mAdapter.getItemCount() - 1;
+            position = 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.
@@ -1989,30 +2023,10 @@
                 // TargetView can be null, for example, if the user pressed <down> at the bottom of
                 // the list.
                 if (targetView != null) {
-                    target = mEnvironment.getAdapterPositionForChildView(targetView);
+                    position = 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;
+        return position;
     }
-
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 501392c..ae5644d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -52,7 +52,7 @@
     public static final int TYPE_DOWNLOADS = 5;
     public static final int TYPE_LOCAL = 6;
     public static final int TYPE_MTP = 7;
-    public static final int TYPE_CLOUD = 8;
+    public static final int TYPE_OTHER = 8;
 
     public String authority;
     public String rootId;
@@ -168,7 +168,10 @@
         derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
 
         // TODO: remove these special case icons
-        if (isExternalStorage()) {
+        if (isHome()) {
+            derivedIcon = R.drawable.ic_root_home;
+            derivedType = TYPE_LOCAL;
+        } else if (isExternalStorage()) {
             derivedIcon = R.drawable.ic_root_sdcard;
             derivedType = TYPE_LOCAL;
         } else if (isDownloads()) {
@@ -188,7 +191,7 @@
         } else if (isMtp()) {
             derivedType = TYPE_MTP;
         } else {
-            derivedType = TYPE_CLOUD;
+            derivedType = TYPE_OTHER;
         }
     }
 
@@ -196,6 +199,13 @@
         return authority == null && rootId == null;
     }
 
+    public boolean isHome() {
+        // Note that "home" is the expected root id for the auto-created
+        // user home directory on external storage. The "home" value should
+        // match ExternalStorageProvider.ROOT_ID_HOME.
+        return isExternalStorage() && "home".equals(rootId);
+    }
+
     public boolean isExternalStorage() {
         return "com.android.externalstorage.documents".equals(authority);
     }
@@ -249,15 +259,6 @@
         }
     }
 
-    public Drawable loadGridIcon(Context context) {
-        if (derivedIcon != 0) {
-            return IconUtils.applyTintAttr(context, derivedIcon,
-                    android.R.attr.textColorPrimaryInverse);
-        } else {
-            return IconUtils.loadPackageIcon(context, authority, icon);
-        }
-    }
-
     public Drawable loadToolbarIcon(Context context) {
         if (derivedIcon != 0) {
             return IconUtils.applyTintAttr(context, derivedIcon,
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 6f1a89b..369ab7d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -32,6 +32,8 @@
 import android.test.MoreAsserts;
 import android.test.ServiceTestCase;
 import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -52,6 +54,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+@MediumTest
 public class CopyTest extends ServiceTestCase<CopyService> {
 
     public CopyTest() {
@@ -89,9 +92,6 @@
         super.tearDown();
     }
 
-    /**
-     * Test copying a single file.
-     */
     public void testCopyFile() throws Exception {
         String srcPath = "/test0.txt";
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
@@ -131,9 +131,6 @@
         MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
     }
 
-    /**
-     * Test copying multiple files.
-     */
     public void testCopyMultipleFiles() throws Exception {
         String testContent[] = {
                 "The five boxing wizards jump quickly",
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 9060516..71d8b34 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -31,11 +31,13 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
 import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.documentsui.model.RootInfo;
 
+@LargeTest
 public class FilesActivityUiTest extends InstrumentationTestCase {
 
     private static final int TIMEOUT = 5000;
@@ -123,6 +125,7 @@
                 "Videos",
                 "Audio",
                 "Downloads",
+                "Home",
                 ROOT_0_ID,
                 ROOT_1_ID);
     }
@@ -134,6 +137,13 @@
         mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
+    public void testRootClickSetsWindowTitle() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot("Home");
+        mBot.assertWindowTitle("Home");
+    }
+
     public void testFilesList_LiveUpdate() throws Exception {
         initTestFiles();
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 5c09794..ecad061 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import static junit.framework.Assert.assertEquals;
+
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -80,6 +82,20 @@
         mDevice.waitForIdle();
     }
 
+    void assertWindowTitle(String expected) {
+        // Turns out the title field on a window does not have
+        // an id associated with it at runtime (which confuses the hell out of me)
+        // In code we address this via "android.R.id.title".
+        UiObject2 o = find(By.text(expected));
+        // It's a bit of a conceit that we then *assert* that the title
+        // is the value that we used to identify the UiObject2.
+        // If the preceeding lookup fails, this'll choke with an NPE.
+        // But given the issue described in the comment above, we're
+        // going to do it anyway. Because we shouldn't be looking up
+        // the uiobject by it's expected content :|
+        assertEquals(expected, o.getText());
+    }
+
     void assertHasRoots(String... labels) throws UiObjectNotFoundException {
         List<String> missing = new ArrayList<>();
         for (String label : labels) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
index 746e2117..b250e5d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
@@ -25,6 +25,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.ViewGroup;
 
 import com.android.documentsui.DirectoryResult;
@@ -34,6 +35,7 @@
 
 import java.util.List;
 
+@SmallTest
 public class DirectoryFragmentModelTest extends AndroidTestCase {
 
     private static final int ITEM_COUNT = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 24f5c9e..b3d45ae 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -18,12 +18,12 @@
 
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseBooleanArray;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.documentsui.TestInputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
 import org.mockito.Mockito;
@@ -33,6 +33,7 @@
 import java.util.List;
 import java.util.Set;
 
+@SmallTest
 public class MultiSelectManagerTest extends AndroidTestCase {
 
     private static final List<String> items;
@@ -49,11 +50,13 @@
     private MultiSelectManager mManager;
     private TestAdapter mAdapter;
     private TestCallback mCallback;
+    private TestSelectionEnvironment mEnv;
 
     public void setUp() throws Exception {
         mAdapter = new TestAdapter(items);
         mCallback = new TestCallback();
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+        mEnv = new TestSelectionEnvironment();
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_MULTIPLE);
         mManager.addCallback(mCallback);
     }
 
@@ -162,7 +165,6 @@
         assertRangeSelection(14, 17);
     }
 
-
     public void testSingleTapUp_ShiftReversesSelectionDirection() {
         longPress(7);
         shiftTap(17);
@@ -171,7 +173,7 @@
     }
 
     public void testSingleSelectMode() {
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(20);
         tap(13);
@@ -179,13 +181,21 @@
     }
 
     public void testSingleSelectMode_ShiftTap() {
-        mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(13);
         shiftTap(20);
         assertSelection(20);
     }
 
+    public void testSingleSelectMode_ShiftDoesNotExtendSelection() {
+        mManager = new MultiSelectManager(mAdapter, mEnv, MultiSelectManager.MODE_SINGLE);
+        mManager.addCallback(mCallback);
+        longPress(20);
+        keyToPosition(22, true);
+        assertSelection(22);
+    }
+
     public void testProvisionalSelection() {
         Selection s = mManager.getSelection();
         assertSelection();
@@ -235,6 +245,10 @@
         mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
     }
 
+    private void keyToPosition(int position, boolean shift) {
+        mManager.attemptChangePosition(position, shift);
+    }
+
     private void assertSelected(int... expected) {
         for (int i = 0; i < expected.length; i++) {
             Selection selection = mManager.getSelection();
@@ -290,11 +304,8 @@
 
     private static final class TestHolder extends RecyclerView.ViewHolder {
         // each data item is just a string in this case
-        public View view;
-        public String string;
         public TestHolder(View view) {
             super(view);
-            this.view = view;
         }
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index c4b6ce5..c856b22 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -20,11 +20,13 @@
 import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView.OnScrollListener;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseBooleanArray;
 import android.view.View;
 
 import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
 
+@SmallTest
 public class MultiSelectManager_GridModelTest extends AndroidTestCase {
 
     private static final int VIEW_PADDING_PX = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index 64da750..72fc108 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -17,10 +17,11 @@
 package com.android.documentsui.dirlist;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
-
+@SmallTest
 public class MultiSelectManager_SelectionTest extends AndroidTestCase{
 
     private Selection selection;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
new file mode 100644
index 0000000..b4324a8
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dirlist;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.view.View;
+
+import com.android.documentsui.dirlist.MultiSelectManager.SelectionEnvironment;
+
+public class TestSelectionEnvironment implements SelectionEnvironment {
+
+    @Override
+    public void showBand(Rect rect) {
+    }
+
+    @Override
+    public void hideBand() {
+    }
+
+    @Override
+    public void addOnScrollListener(OnScrollListener listener) {
+    }
+
+    @Override
+    public void removeOnScrollListener(OnScrollListener listener) {
+    }
+
+    @Override
+    public void scrollBy(int dy) {
+    }
+
+    @Override
+    public int getHeight() {
+        return 0;
+    }
+
+    @Override
+    public void invalidateView() {
+    }
+
+    @Override
+    public void runAtNextFrame(Runnable r) {
+    }
+
+    @Override
+    public void removeCallback(Runnable r) {
+    }
+
+    @Override
+    public Point createAbsolutePoint(Point relativePoint) {
+        return null;
+    }
+
+    @Override
+    public Rect getAbsoluteRectForChildViewAt(int index) {
+        return null;
+    }
+
+    @Override
+    public int getAdapterPositionAt(int index) {
+        return 0;
+    }
+
+    @Override
+    public int getAdapterPositionForChildView(View view) {
+        return 0;
+    }
+
+    @Override
+    public int getColumnCount() {
+        return 0;
+    }
+
+    @Override
+    public int getRowCount() {
+        return 0;
+    }
+
+    @Override
+    public int getChildCount() {
+        return 0;
+    }
+
+    @Override
+    public int getVisibleChildCount() {
+        return 0;
+    }
+
+    @Override
+    public void focusItem(int position) {
+    }
+}
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index f1c1ade..e48436e 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -20,6 +20,6 @@
 
     <!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
     <string name="root_internal_storage">Internal storage</string>
-    <!-- Title for documents backend that offers documents. [CHAR LIMIT=24] -->
-    <string name="root_documents">Documents</string>
+    <!-- Title for user home dir. [CHAR LIMIT=24] -->
+    <string name="root_home">Home</string>
 </resources>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index fcd45f2..2cedc72 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -85,9 +85,11 @@
         public String docId;
         public File visiblePath;
         public File path;
+        public boolean reportAvailableBytes = true;
     }
 
     private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
+    private static final String ROOT_ID_HOME = "home";
 
     private StorageManager mStorageManager;
     private Handler mHandler;
@@ -118,6 +120,7 @@
     private void updateVolumesLocked() {
         mRoots.clear();
 
+        VolumeInfo primaryVolume = null;
         final int userId = UserHandle.myUserId();
         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
         for (VolumeInfo volume : volumes) {
@@ -126,6 +129,9 @@
             final String rootId;
             final String title;
             if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
+                // save off the primary volume for subsequent "Home" dir initialization.
+                primaryVolume = volume;
+
                 // We currently only support a single emulated volume mounted at
                 // a time, and it's always considered the primary
                 rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -152,25 +158,58 @@
                 continue;
             }
 
+            final RootInfo root = new RootInfo();
+            mRoots.put(rootId, root);
+
+            root.rootId = rootId;
+            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+                    | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+
+            // Dunno when this would NOT be the case, but never hurts to be correct.
+            if (volume.isMountedWritable()) {
+                root.flags |= Root.FLAG_SUPPORTS_CREATE;
+            }
+            root.title = title;
+            if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
+                root.flags |= Root.FLAG_HAS_SETTINGS;
+            }
+            if (volume.isVisibleForRead(userId)) {
+                root.visiblePath = volume.getPathForUser(userId);
+            } else {
+                root.visiblePath = null;
+            }
+            root.path = volume.getInternalPathForUser(userId);
             try {
-                final RootInfo root = new RootInfo();
-                mRoots.put(rootId, root);
-
-                root.rootId = rootId;
-                root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
-                        | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
-                root.title = title;
-                if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
-                    root.flags |= Root.FLAG_HAS_SETTINGS;
-                }
-                if (volume.isVisibleForRead(userId)) {
-                    root.visiblePath = volume.getPathForUser(userId);
-                } else {
-                    root.visiblePath = null;
-                }
-                root.path = volume.getInternalPathForUser(userId);
                 root.docId = getDocIdForFile(root.path);
+            } catch (FileNotFoundException e) {
+                throw new IllegalStateException(e);
+            }
+        }
 
+        // Finally, if primary storage is available we add the "Home" directory,
+        // creating it as needed.
+        if (primaryVolume != null && primaryVolume.isVisible()) {
+            final RootInfo root = new RootInfo();
+            root.rootId = ROOT_ID_HOME;
+            mRoots.put(root.rootId, root);
+            root.title = getContext().getString(R.string.root_home);
+
+            // Only report bytes on *volumes*...as a matter of policy.
+            root.reportAvailableBytes = false;
+            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH
+                    | Root.FLAG_SUPPORTS_IS_CHILD;
+
+            // Dunno when this would NOT be the case, but never hurts to be correct.
+            if (primaryVolume.isMountedWritable()) {
+                root.flags |= Root.FLAG_SUPPORTS_CREATE;
+            }
+
+            root.visiblePath = new File(
+                    primaryVolume.getPathForUser(userId), root.rootId);
+            root.path = new File(
+                    primaryVolume.getInternalPathForUser(userId), root.rootId);
+            try {
+                root.docId = getDocIdForFile(root.path);
             } catch (FileNotFoundException e) {
                 throw new IllegalStateException(e);
             }
@@ -312,7 +351,8 @@
                 row.add(Root.COLUMN_FLAGS, root.flags);
                 row.add(Root.COLUMN_TITLE, root.title);
                 row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
-                row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace());
+                row.add(Root.COLUMN_AVAILABLE_BYTES,
+                        root.reportAvailableBytes ? root.path.getFreeSpace() : -1);
             }
         }
         return result;
diff --git a/packages/FakeOemFeatures/AndroidManifest.xml b/packages/FakeOemFeatures/AndroidManifest.xml
index 93b8b47..fe74ad8 100644
--- a/packages/FakeOemFeatures/AndroidManifest.xml
+++ b/packages/FakeOemFeatures/AndroidManifest.xml
@@ -11,7 +11,9 @@
         android:allowClearUserData="false"
         android:allowBackup="false"
         android:hardwareAccelerated="true"
-        android:label="Fake OEM Features">
+        android:label="Fake OEM Features"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
         <service android:name=".FakeCoreService" android:process=":core"
                 android:label="Fake OEM Core Service" />
diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml
index 6a4d4bf..ed84c0d 100644
--- a/packages/FusedLocation/AndroidManifest.xml
+++ b/packages/FusedLocation/AndroidManifest.xml
@@ -28,7 +28,9 @@
 
     <application
             android:label="@string/app_label"
-            android:process="system">
+            android:process="system"
+            android:forceDeviceEncrypted="true"
+            android:encryptionAware="true">
 
         <uses-library android:name="com.android.location.provider" />
 
@@ -37,7 +39,7 @@
              version. -->
         <service android:name="com.android.location.fused.FusedLocationService"
                  android:exported="true"
-                 android:permission="android.permission.WRITE_SECURE_SETTINGS" >
+                 android:permission="android.permission.WRITE_SECURE_SETTINGS">
            <intent-filter>
                <action android:name="com.android.location.service.FusedLocationProvider" />
            </intent-filter>
diff --git a/packages/InputDevices/AndroidManifest.xml b/packages/InputDevices/AndroidManifest.xml
index f0e4abc..07885ea 100644
--- a/packages/InputDevices/AndroidManifest.xml
+++ b/packages/InputDevices/AndroidManifest.xml
@@ -6,7 +6,9 @@
     <application
             android:allowClearUserData="false"
             android:label="@string/app_label"
-            android:process="system">
+            android:process="system"
+            android:forceDeviceEncrypted="true"
+            android:encryptionAware="true">
 
         <receiver android:name=".InputDeviceReceiver"
                 android:label="@string/keyboard_layouts_label">
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml
index e19246c..54972b4 100644
--- a/packages/Keyguard/AndroidManifest.xml
+++ b/packages/Keyguard/AndroidManifest.xml
@@ -45,7 +45,9 @@
     <application android:label="@string/app_name"
         android:process="com.android.systemui"
         android:persistent="true"
-        android:supportsRtl="true">
+        android:supportsRtl="true"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
     </application>
 </manifest>
diff --git a/packages/Keyguard/res/values-ja/strings.xml b/packages/Keyguard/res/values-ja/strings.xml
index cea4319..3230beb 100644
--- a/packages/Keyguard/res/values-ja/strings.xml
+++ b/packages/Keyguard/res/values-ja/strings.xml
@@ -110,8 +110,8 @@
     <string name="keyguard_carrier_default" msgid="8700650403054042153">"通信サービスはありません。"</string>
     <string name="accessibility_ime_switch_button" msgid="5032926134740456424">"入力方法の切り替えボタン。"</string>
     <string name="airplane_mode" msgid="3122107900897202805">"機内モード"</string>
-    <string name="kg_prompt_reason_restart_pattern" msgid="489430505491862444">"端末を再起動するにはパターンが必要です。"</string>
-    <string name="kg_prompt_reason_restart_pin" msgid="994878216570694974">"端末を再起動するにはPINが必要です。"</string>
+    <string name="kg_prompt_reason_restart_pattern" msgid="489430505491862444">"端末を再起動した時にはパターンが必要です。"</string>
+    <string name="kg_prompt_reason_restart_pin" msgid="994878216570694974">"端末を再起動した時にはPINが必要です。"</string>
     <string name="kg_prompt_reason_restart_password" msgid="2375742919528461664">"端末を再起動した時にはパスワードが必要です。"</string>
     <string name="kg_prompt_reason_timeout_pattern" msgid="8930047492617900785">"セキュリティを強化するため、パターンが必要です。"</string>
     <string name="kg_prompt_reason_timeout_pin" msgid="7470468607947726377">"セキュリティを強化するため、PINが必要です。"</string>
diff --git a/packages/Keyguard/res/values-zh-rCN/strings.xml b/packages/Keyguard/res/values-zh-rCN/strings.xml
index 5b1e45d..c7b217c 100644
--- a/packages/Keyguard/res/values-zh-rCN/strings.xml
+++ b/packages/Keyguard/res/values-zh-rCN/strings.xml
@@ -92,8 +92,8 @@
     <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="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_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>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index f95b0ae..9d1df26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -198,12 +197,18 @@
     }
 
     private String getOwnerInfo() {
-        ContentResolver res = getContext().getContentResolver();
         String info = null;
-        final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (ownerInfoEnabled) {
-            info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+        if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) {
+            // Use the device owner information set by device policy client via
+            // device policy manager.
+            info = mLockPatternUtils.getDeviceOwnerInfo();
+        } else {
+            // Use the current user owner information if enabled.
+            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                    KeyguardUpdateMonitor.getCurrentUser());
+            if (ownerInfoEnabled) {
+                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+            }
         }
         return info;
     }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 0d4265a..1c96906 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,7 @@
 
 import android.content.ContentResolver;
 import android.database.Cursor;
-import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -26,6 +26,7 @@
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Date;
@@ -44,12 +45,14 @@
 
     private final MtpManager mMtpManager;
     private final ContentResolver mResolver;
+    private final MtpDatabase mDatabase;
     private final TaskList mTaskList = new TaskList();
     private boolean mHasBackgroundThread = false;
 
-    DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
+    DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
         mMtpManager = mtpManager;
         mResolver = resolver;
+        mDatabase = database;
     }
 
     private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
@@ -65,13 +68,17 @@
             throws IOException {
         LoaderTask task = mTaskList.findTask(parent);
         if (task == null) {
+            if (parent.mDocumentId == null) {
+                throw new FileNotFoundException("Parent not found.");
+            }
+
             int parentHandle = parent.mObjectHandle;
             // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
             // getObjectHandles if we would like to obtain children under the root.
             if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
                 parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
             }
-            task = new LoaderTask(parent, mMtpManager.getObjectHandles(
+            task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
                     parent.mDeviceId, parent.mStorageId, parentHandle));
             task.fillDocuments(loadDocuments(
                     mMtpManager,
@@ -83,11 +90,10 @@
         }
 
         mTaskList.addFirst(task);
-        if (!task.completed() && !mHasBackgroundThread) {
+        if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
             mHasBackgroundThread = true;
             new BackgroundLoaderThread().start();
         }
-
         return task.createCursor(mResolver, columnNames);
     }
 
@@ -120,26 +126,20 @@
                     deviceId = task.mIdentifier.mDeviceId;
                     handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
                 }
-                MtpObjectInfo[] objectInfos;
+
                 try {
-                    objectInfos = loadDocuments(mMtpManager, deviceId, handles);
-                } catch (IOException exception) {
-                    objectInfos = null;
-                    Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
-                }
-                synchronized (DocumentLoader.this) {
-                    if (objectInfos != null) {
-                        task.fillDocuments(objectInfos);
-                        final boolean shouldNotify =
-                                task.mLastNotified.getTime() <
-                                new Date().getTime() - NOTIFY_PERIOD_MS ||
-                                task.completed();
-                        if (shouldNotify) {
-                            task.notify(mResolver);
-                        }
-                    } else {
-                        mTaskList.remove(task);
+                    final MtpObjectInfo[] objectInfos =
+                            loadDocuments(mMtpManager, deviceId, handles);
+                    task.fillDocuments(objectInfos);
+                    final boolean shouldNotify =
+                            task.mLastNotified.getTime() <
+                            new Date().getTime() - NOTIFY_PERIOD_MS ||
+                            task.getState() != LoaderTask.STATE_LOADING;
+                    if (shouldNotify) {
+                        task.notify(mResolver);
                     }
+                } catch (IOException exception) {
+                    task.setError(exception);
                 }
             }
         }
@@ -156,7 +156,7 @@
 
         LoaderTask findRunningTask() {
             for (int i = 0; i < size(); i++) {
-                if (!get(i).completed())
+                if (get(i).getState() == LoaderTask.STATE_LOADING)
                     return get(i);
             }
             return null;
@@ -165,7 +165,7 @@
         void clearCompletedTasks() {
             int i = 0;
             while (i < size()) {
-                if (get(i).completed()) {
+                if (get(i).getState() == LoaderTask.STATE_COMPLETED) {
                     remove(i);
                 } else {
                     i++;
@@ -186,36 +186,51 @@
     }
 
     private static class LoaderTask {
+        static final int STATE_LOADING = 0;
+        static final int STATE_COMPLETED = 1;
+        static final int STATE_ERROR = 2;
+
+        final MtpDatabase mDatabase;
         final Identifier mIdentifier;
         final int[] mObjectHandles;
-        final MtpObjectInfo[] mObjectInfos;
         Date mLastNotified;
         int mNumLoaded;
+        Exception mError;
 
-        LoaderTask(Identifier identifier, int[] objectHandles) {
+        LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) {
+            mDatabase = database;
             mIdentifier = identifier;
             mObjectHandles = objectHandles;
-            mObjectInfos = new MtpObjectInfo[mObjectHandles.length];
             mNumLoaded = 0;
             mLastNotified = new Date();
         }
 
-        Cursor createCursor(ContentResolver resolver, String[] columnNames) {
-            final MatrixCursor cursor = new MatrixCursor(columnNames);
-            final Identifier rootIdentifier = new Identifier(
-                    mIdentifier.mDeviceId, mIdentifier.mStorageId);
-            for (int i = 0; i < mNumLoaded; i++) {
-                CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow());
-            }
+        Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
             final Bundle extras = new Bundle();
-            extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
+            switch (getState()) {
+                case STATE_LOADING:
+                    extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+                    break;
+                case STATE_ERROR:
+                    throw new IOException(mError);
+            }
+
+            final Cursor cursor = mDatabase.queryChildDocuments(
+                    columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
             cursor.setNotificationUri(resolver, createUri());
             cursor.respond(extras);
+
             return cursor;
         }
 
-        boolean completed() {
-            return mNumLoaded == mObjectInfos.length;
+        int getState() {
+            if (mError != null) {
+                return STATE_ERROR;
+            } else if (mNumLoaded == mObjectHandles.length) {
+                return STATE_COMPLETED;
+            } else {
+                return STATE_LOADING;
+            }
         }
 
         int[] getUnloadedObjectHandles(int count) {
@@ -230,9 +245,32 @@
             mLastNotified = new Date();
         }
 
-        void fillDocuments(MtpObjectInfo[] objectInfos) {
-            for (int i = 0; i < objectInfos.length; i++) {
-                mObjectInfos[mNumLoaded++] = objectInfos[i];
+        void fillDocuments(MtpObjectInfo[] objectInfoList) {
+            if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
+                return;
+            }
+            if (mNumLoaded == 0) {
+                mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+            try {
+                mDatabase.putChildDocuments(
+                        mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
+                mNumLoaded += objectInfoList.length;
+            } catch (SQLiteException exp) {
+                mError = exp;
+                mNumLoaded = 0;
+            }
+            if (getState() != STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+        }
+
+        void setError(Exception message) {
+            final int lastState = getState();
+            mError = message;
+            mNumLoaded = 0;
+            if (lastState == STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
             }
         }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index ae29f52..4238721e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -23,6 +23,7 @@
     final int mDeviceId;
     final int mStorageId;
     final int mObjectHandle;
+    final String mDocumentId;
 
     static Identifier createFromRootId(String rootId) {
         final String[] components = rootId.split("_");
@@ -45,9 +46,14 @@
     }
 
     Identifier(int deviceId, int storageId, int objectHandle) {
+        this(deviceId, storageId, objectHandle, null);
+    }
+
+    Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
         mDeviceId = deviceId;
         mStorageId = storageId;
         mObjectHandle = objectHandle;
+        mDocumentId = documentId;
     }
 
     // TODO: Make the ID persistent.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 8568da0..3dc69cc 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -16,20 +16,21 @@
 
 package com.android.mtp;
 
+import static com.android.mtp.MtpDatabaseConstants.*;
+
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
 import android.mtp.MtpObjectInfo;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Objects;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Database for MTP objects.
@@ -44,300 +45,288 @@
  * remembers the map of document ID and object handle, and remaps new object handle with document ID
  * by comparing the directory structure and object name.
  *
+ * To start putting documents into the database, the client needs to call
+ * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
+ * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
+ * (All explanations are same for root documents)
+ *
+ * database.startAddingChildDocuments();
+ * database.putChildDocuments();
+ * database.stopAddingChildDocuments();
+ *
+ * To update the existing documents, the client code can repeat to call the three methods again.
+ * The newly added rows update corresponding existing rows that have same MTP identifier like
+ * objectHandle.
+ *
+ * The client can call putChildDocuments multiple times to add documents by chunk, but it needs to
+ * put all documents under the parent before calling stopAddingChildDocuments. Otherwise missing
+ * documents are regarded as deleted, and will be removed from the database.
+ *
+ * If the client calls clearMtpIdentifier(), it clears MTP identifier in the database. In this case,
+ * the database tries to find corresponding rows by using document's name instead of MTP identifier
+ * at the next update cycle.
+ *
  * TODO: Remove @VisibleForTesting annotation when we start to use this class.
  * TODO: Improve performance by SQL optimization.
  */
 @VisibleForTesting
 class MtpDatabase {
-    private static final int VERSION = 1;
-    private static final String NAME = "mtp";
-
-    private static final String TABLE_DOCUMENTS = "Documents";
-
-    static final String COLUMN_DEVICE_ID = "device_id";
-    static final String COLUMN_STORAGE_ID = "storage_id";
-    static final String COLUMN_OBJECT_HANDLE = "object_handle";
-    static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
-    static final String COLUMN_ROW_STATE = "row_state";
+    private final MtpDatabaseInternal mDatabase;
 
     /**
-     * The state represents that the row has a valid object handle.
+     * Mapping mode for roots/documents where we start adding child documents.
+     * Methods operate the state needs to be synchronized.
      */
-    static final int ROW_STATE_MAPPED = 0;
-
-    /**
-     * The state represents that the object handle was cleared because the MTP session closed.
-     * External application can still fetch the unmapped documents. If the external application
-     * tries to open an unmapped document, the provider resolves the document with new object handle
-     * ahead.
-     */
-    static final int ROW_STATE_UNMAPPED = 1;
-
-    /**
-     * The state represents the raw has a valid object handle but it may be going to be merged into
-     * another unmapped row. After fetching all documents under the parent, the database tries to
-     * map the mapping document and the unmapped document in order to keep old document ID alive.
-     */
-    static final int ROW_STATE_MAPPING = 2;
-
-    private static final String SELECTION_DOCUMENT_ID =
-            DocumentsContract.Document.COLUMN_DOCUMENT_ID + " = ?";
-    private static final String SELECTION_ROOT_DOCUMENTS =
-            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
-    private static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
-
-    static class ParentNotFoundException extends Exception {}
-
-    private static class OpenHelper extends SQLiteOpenHelper {
-        private static final String QUERY_CREATE_DOCUMENTS =
-                "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
-                DocumentsContract.Document.COLUMN_DOCUMENT_ID +
-                    " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
-                COLUMN_STORAGE_ID + " INTEGER," +
-                COLUMN_OBJECT_HANDLE + " INTEGER," +
-                COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
-                COLUMN_ROW_STATE + " INTEGER 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(QUERY_CREATE_DOCUMENTS);
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    private final SQLiteDatabase database;
+    private final Map<String, Integer> mMappingMode = new HashMap<>();
 
     @VisibleForTesting
-    MtpDatabase(Context context) {
-        final OpenHelper helper = new OpenHelper(context);
-        database = helper.getWritableDatabase();
+    MtpDatabase(Context context, int flags) {
+        mDatabase = new MtpDatabaseInternal(context, flags);
     }
 
+    /**
+     * Closes the database.
+     */
     @VisibleForTesting
-    static void deleteDatabase(Context context) {
-        SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
+    void close() {
+        mDatabase.close();
     }
 
+    /**
+     * {@link MtpDatabaseInternal#queryRoots}
+     */
+    Cursor queryRoots(String[] columnNames) {
+        return mDatabase.queryRoots(columnNames);
+    }
+
+    /**
+     * {@link MtpDatabaseInternal#queryRootDocuments}
+     */
     @VisibleForTesting
     Cursor queryRootDocuments(String[] columnNames) {
-        return database.query(
-                TABLE_DOCUMENTS,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?)",
-                strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED),
-                null,
-                null,
-                null);
+        return mDatabase.queryRootDocuments(columnNames);
     }
 
+    /**
+     * {@link MtpDatabaseInternal#queryChildDocuments}
+     */
     @VisibleForTesting
     Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
-        return database.query(
-                TABLE_DOCUMENTS,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
-                strings(ROW_STATE_MAPPED, ROW_STATE_UNMAPPED, parentDocumentId),
-                null,
-                null,
-                null);
+        return queryChildDocuments(columnNames, parentDocumentId, false);
     }
 
     @VisibleForTesting
-    void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
-        final ContentValues[] valuesList = new ContentValues[roots.length];
-        for (int i = 0; i < roots.length; i++) {
-            if (roots[i].mDeviceId != deviceId) {
-                throw new IllegalArgumentException();
+    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
+        final String[] newColumnNames = new String[columnNames.length];
+
+        // TODO: Temporary replace document ID with old format.
+        for (int i = 0; i < columnNames.length; i++) {
+            if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
+                newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
+                        " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
+                        DocumentsContract.Document.COLUMN_DOCUMENT_ID;
+            } else {
+                newColumnNames[i] = columnNames[i];
             }
-            valuesList[i] = new ContentValues();
-            getRootDocumentValues(valuesList[i], resources, roots[i]);
         }
-        putDocuments(valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
+
+        return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
     }
 
+    Identifier createIdentifier(String parentDocumentId) {
+        return mDatabase.createIdentifier(parentDocumentId);
+    }
+
+    /**
+     * {@link MtpDatabaseInternal#removeDeviceRows}
+     */
+    void removeDeviceRows(int deviceId) {
+        mDatabase.removeDeviceRows(deviceId);
+    }
+
+    /**
+     * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
+     * @param deviceId Device ID.
+     */
+    synchronized void startAddingRootDocuments(int deviceId) {
+        final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+        if (mMappingMode.containsKey(mappingStateKey)) {
+            throw new Error("Mapping for the root has already started.");
+        }
+        mMappingMode.put(
+                mappingStateKey,
+                mDatabase.startAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+    }
+
+    /**
+     * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
+     * @param parentDocumentId Document ID for parent document.
+     */
     @VisibleForTesting
-    void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+    synchronized void startAddingChildDocuments(String parentDocumentId) {
+        final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+        if (mMappingMode.containsKey(mappingStateKey)) {
+            throw new Error("Mapping for the root has already started.");
+        }
+        mMappingMode.put(
+                mappingStateKey,
+                mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
+    }
+
+    /**
+     * Puts root information to database.
+     * @param deviceId Device ID
+     * @param resources Resources required to localize root name.
+     * @param roots List of root information.
+     * @return If roots are added or removed from the database.
+     */
+    synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+        mDatabase.beginTransaction();
+        try {
+            final boolean heuristic;
+            final String mapColumn;
+            switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
+                case MAP_BY_MTP_IDENTIFIER:
+                    heuristic = false;
+                    mapColumn = COLUMN_STORAGE_ID;
+                    break;
+                case MAP_BY_NAME:
+                    heuristic = true;
+                    mapColumn = Document.COLUMN_DISPLAY_NAME;
+                    break;
+                default:
+                    throw new Error("Unexpected map mode.");
+            }
+            final ContentValues[] valuesList = new ContentValues[roots.length];
+            for (int i = 0; i < roots.length; i++) {
+                if (roots[i].mDeviceId != deviceId) {
+                    throw new IllegalArgumentException();
+                }
+                valuesList[i] = new ContentValues();
+                getRootDocumentValues(valuesList[i], resources, roots[i]);
+            }
+            final boolean changed = mDatabase.putDocuments(
+                    valuesList,
+                    SELECTION_ROOT_DOCUMENTS,
+                    Integer.toString(deviceId),
+                    heuristic,
+                    mapColumn);
+            final ContentValues values = new ContentValues();
+            int i = 0;
+            for (final MtpRoot root : roots) {
+                // Use the same value for the root ID and the corresponding document ID.
+                final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+                // If it fails to insert/update documents, the document ID will be set with -1.
+                // In this case we don't insert/update root extra information neither.
+                if (documentId == null) {
+                    continue;
+                }
+                values.put(Root.COLUMN_ROOT_ID, documentId);
+                values.put(
+                        Root.COLUMN_FLAGS,
+                        Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+                values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
+                values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
+                values.put(Root.COLUMN_MIME_TYPES, "");
+                mDatabase.putRootExtra(values);
+            }
+            mDatabase.setTransactionSuccessful();
+            return changed;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Puts document information to database.
+     * @param deviceId Device ID
+     * @param parentId Parent document ID.
+     * @param documents List of document information.
+     */
+    @VisibleForTesting
+    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+        final boolean heuristic;
+        final String mapColumn;
+        switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
+            case MAP_BY_MTP_IDENTIFIER:
+                heuristic = false;
+                mapColumn = COLUMN_OBJECT_HANDLE;
+                break;
+            case MAP_BY_NAME:
+                heuristic = true;
+                mapColumn = Document.COLUMN_DISPLAY_NAME;
+                break;
+            default:
+                throw new Error("Unexpected map mode.");
+        }
         final ContentValues[] valuesList = new ContentValues[documents.length];
         for (int i = 0; i < documents.length; i++) {
             valuesList[i] = new ContentValues();
             getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
         }
-        putDocuments(valuesList, SELECTION_CHILD_DOCUMENTS, parentId);
+        mDatabase.putDocuments(
+                valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
     }
 
     /**
-     * Clears MTP related identifier.
-     * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
-     * the all documents as 'unmapped'. It also remove 'mapping' rows as mapping is cancelled now.
+     * Clears mapping between MTP identifier and document/root ID.
      */
     @VisibleForTesting
-    void clearMapping() {
-        database.beginTransaction();
-        try {
-            database.delete(
-                    TABLE_DOCUMENTS, COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_MAPPING));
-            final ContentValues values = new ContentValues();
-            values.putNull(COLUMN_OBJECT_HANDLE);
-            values.putNull(COLUMN_STORAGE_ID);
-            values.put(COLUMN_ROW_STATE, ROW_STATE_UNMAPPED);
-            database.update(TABLE_DOCUMENTS, values, null, null);
-            database.setTransactionSuccessful();
-        } finally {
-            database.endTransaction();
-        }
-    }
-
-    @VisibleForTesting
-    void resolveRootDocuments(int deviceId) {
-        resolveDocuments(SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
-    }
-
-    @VisibleForTesting
-    void resolveChildDocuments(String parentId) {
-        resolveDocuments(SELECTION_CHILD_DOCUMENTS, parentId);
+    synchronized void clearMapping() {
+        mDatabase.clearMapping();
+        mMappingMode.clear();
     }
 
     /**
-     * Puts the documents into the database.
-     * If the database found another unmapped document that shares the same name and parent,
-     * the document may be merged into the unmapped document. In that case, the database marks the
-     * root as 'mapping' and wait for {@link #resolveRootDocuments(int)} is invoked.
-     * @param valuesList Values that are stored in the database.
-     * @param selection SQL where closure to select rows that shares the same parent.
-     * @param arg Argument for selection SQL.
+     * Stops adding root documents.
+     * @param deviceId Device ID.
+     * @return True if new rows are added/removed.
      */
-    private void putDocuments(ContentValues[] valuesList, String selection, String arg) {
-        database.beginTransaction();
-        try {
-            for (final ContentValues values : valuesList) {
-                final String displayName =
-                        values.getAsString(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
-                final long numUnmapped = DatabaseUtils.queryNumEntries(
-                        database,
-                        TABLE_DOCUMENTS,
-                        selection + " AND " +
-                        COLUMN_ROW_STATE + " = ? AND " +
-                        DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = ?",
-                        strings(arg, ROW_STATE_UNMAPPED, displayName));
-                if (numUnmapped != 0) {
-                    values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPING);
-                }
-                database.insert(TABLE_DOCUMENTS, null, values);
-            }
-
-            database.setTransactionSuccessful();
-        } finally {
-            database.endTransaction();
+    synchronized boolean stopAddingRootDocuments(int deviceId) {
+        final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
+        switch (mMappingMode.get(mappingModeKey)) {
+            case MAP_BY_MTP_IDENTIFIER:
+                mMappingMode.remove(mappingModeKey);
+                return mDatabase.stopAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS,
+                        Integer.toString(deviceId),
+                        COLUMN_STORAGE_ID);
+            case MAP_BY_NAME:
+                mMappingMode.remove(mappingModeKey);
+                return mDatabase.stopAddingDocuments(
+                        SELECTION_ROOT_DOCUMENTS,
+                        Integer.toString(deviceId),
+                        Document.COLUMN_DISPLAY_NAME);
+            default:
+                throw new Error("Unexpected mapping state.");
         }
     }
 
     /**
-     * Maps 'unmapped' document and 'mapping' document that don't have document but shares the same
-     * name.
-     * If the database does not find corresponding 'mapping' document, it just removes 'unmapped'
-     * document from the database.
-     * @param selection Query to select rows for resolving.
-     * @param arg Argument for selection SQL.
+     * Stops adding documents under the parent.
+     * @param parentId Document ID of the parent.
      */
-    private void resolveDocuments(String selection, String arg) {
-        database.beginTransaction();
-        try {
-            // Get 1-to-1 mapping of unmapped document and mapping document.
-            final String unmappedIdQuery = createStateFilter(
-                    ROW_STATE_UNMAPPED, DocumentsContract.Document.COLUMN_DOCUMENT_ID);
-            final String mappingIdQuery = createStateFilter(
-                    ROW_STATE_MAPPING, DocumentsContract.Document.COLUMN_DOCUMENT_ID);
-            // SQL should be like:
-            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
-            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
-            // WHERE device_id = ? AND parent_document_id IS NULL
-            // GROUP BY display_name
-            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
-            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
-            final Cursor mergingCursor = database.query(
-                    TABLE_DOCUMENTS,
-                    new String[] {
-                            "group_concat(" + unmappedIdQuery + ")",
-                            "group_concat(" + mappingIdQuery + ")"
-                    },
-                    selection,
-                    strings(arg),
-                    DocumentsContract.Document.COLUMN_DISPLAY_NAME,
-                    "count(" + unmappedIdQuery + ") = 1 AND count(" + mappingIdQuery + ") = 1",
-                    null);
-
-            final ContentValues values = new ContentValues();
-            while (mergingCursor.moveToNext()) {
-                final String unmappedId = mergingCursor.getString(0);
-                final String mappingId = mergingCursor.getString(1);
-
-                // Obtain the new values including the latest object handle from mapping row.
-                final Cursor mappingCursor = database.query(
-                        TABLE_DOCUMENTS,
-                        null,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { mappingId },
-                        null,
-                        null,
-                        null);
-                mappingCursor.moveToNext();
-                values.clear();
-                DatabaseUtils.cursorRowToContentValues(mappingCursor, values);
-                mappingCursor.close();
-                values.remove(DocumentsContract.Document.COLUMN_DOCUMENT_ID);
-
-                // Set the new values into unmapped documents and get it back to 'normal' state.
-                values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
-                database.update(
-                        TABLE_DOCUMENTS,
-                        values,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { unmappedId });
-
-                // Delete 'mapping' row.
-                database.delete(
-                        TABLE_DOCUMENTS,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { mappingId });
-            }
-            mergingCursor.close();
-
-            // Delete all unmapped rows that cannot be mapped.
-            database.delete(
-                    TABLE_DOCUMENTS,
-                    COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_UNMAPPED, arg));
-
-            // The database cannot find old document ID for the mapping rows.
-            // Turn the all mapping rows into mapped state, which means the rows become to be
-            // valid with new document ID.
-            values.clear();
-            values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
-            database.update(
-                    TABLE_DOCUMENTS,
-                    values,
-                    COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_MAPPING, arg));
-            database.setTransactionSuccessful();
-        } finally {
-            database.endTransaction();
+    @VisibleForTesting
+    synchronized void stopAddingChildDocuments(String parentId) {
+        final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
+        switch (mMappingMode.get(mappingModeKey)) {
+            case MAP_BY_MTP_IDENTIFIER:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_CHILD_DOCUMENTS,
+                        parentId,
+                        COLUMN_OBJECT_HANDLE);
+                break;
+            case MAP_BY_NAME:
+                mDatabase.stopAddingDocuments(
+                        SELECTION_CHILD_DOCUMENTS,
+                        parentId,
+                        Document.COLUMN_DISPLAY_NAME);
+                break;
+            default:
+                throw new Error("Unexpected mapping state.");
         }
+        mMappingMode.remove(mappingModeKey);
     }
 
     /**
@@ -353,8 +342,8 @@
         values.put(COLUMN_STORAGE_ID, root.mStorageId);
         values.putNull(COLUMN_OBJECT_HANDLE);
         values.putNull(COLUMN_PARENT_DOCUMENT_ID);
-        values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
-        values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
+        values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+        values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
         values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
         values.putNull(Document.COLUMN_SUMMARY);
         values.putNull(Document.COLUMN_LAST_MODIFIED);
@@ -377,20 +366,20 @@
         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;
+            flag |= Document.FLAG_SUPPORTS_DELETE |
+                    Document.FLAG_SUPPORTS_WRITE;
+            if (mimeType == Document.MIME_TYPE_DIR) {
+                flag |= Document.FLAG_DIR_SUPPORTS_CREATE;
             }
         }
         if (info.getThumbCompressedSize() > 0) {
-            flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
+            flag |= Document.FLAG_SUPPORTS_THUMBNAIL;
         }
         values.put(COLUMN_DEVICE_ID, deviceId);
         values.put(COLUMN_STORAGE_ID, info.getStorageId());
         values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
         values.put(COLUMN_PARENT_DOCUMENT_ID, parentId);
-        values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
+        values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
         values.put(Document.COLUMN_MIME_TYPE, mimeType);
         values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
         values.putNull(Document.COLUMN_SUMMARY);
@@ -403,27 +392,18 @@
     }
 
     /**
-     * Converts values into string array.
-     * @param args Values converted into string array.
-     * @return String array.
+     * @param deviceId Device ID.
+     * @return Key for {@link #mMappingMode}.
      */
-    private static String[] strings(Object... args) {
-        final String[] results = new String[args.length];
-        for (int i = 0; i < args.length; i++) {
-            results[i] = Objects.toString(args[i]);
-        }
-        return results;
+    private static String getRootDocumentsMappingStateKey(int deviceId) {
+        return "RootDocuments/" + deviceId;
     }
 
     /**
-     * Gets SQL expression that represents the given value or NULL depends on the row state.
-     * @param state Expected row state.
-     * @param a SQL value.
-     * @return Expression that represents a if the row state is expected one, and represents NULL
-     *     otherwise.
+     * @param parentDocumentId Document ID for the parent document.
+     * @return Key for {@link #mMappingMode}.
      */
-    private static String createStateFilter(int state, String a) {
-        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
-                " THEN " + a + " ELSE NULL END";
+    private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
+        return "ChildDocuments/" + parentDocumentId;
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
new file mode 100644
index 0000000..97c1d29
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.mtp;
+
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+/**
+ * Class containing MtpDatabase constants.
+ */
+class MtpDatabaseConstants {
+    static final int DATABASE_VERSION = 1;
+    static final String DATABASE_NAME = null;
+
+    static final int FLAG_DATABASE_IN_MEMORY = 1;
+    static final int FLAG_DATABASE_IN_FILE = 0;
+
+    /**
+     * Table representing documents including root documents.
+     */
+    static final String TABLE_DOCUMENTS = "Documents";
+
+    /**
+     * Table containing additional information only available for root documents.
+     * The table uses same primary keys with corresponding documents.
+     */
+    static final String TABLE_ROOT_EXTRA = "RootExtra";
+
+    /**
+     * View to join Documents and RootExtra tables to provide roots information.
+     */
+    static final String VIEW_ROOTS = "Roots";
+
+    static final String COLUMN_DEVICE_ID = "device_id";
+    static final String COLUMN_STORAGE_ID = "storage_id";
+    static final String COLUMN_OBJECT_HANDLE = "object_handle";
+    static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id";
+    static final String COLUMN_ROW_STATE = "row_state";
+
+    /**
+     * The state represents that the row has a valid object handle.
+     */
+    static final int ROW_STATE_VALID = 0;
+
+    /**
+     * The state represents that the rows added at the previous cycle and need to be updated with
+     * fresh values.
+     * The row may not have valid object handle. External application can still fetch the documents.
+     * If the external application tries to fetch object handle, the provider resolves pending
+     * documents with invalidated documents ahead.
+     */
+    static final int ROW_STATE_INVALIDATED = 1;
+
+    /**
+     * The state represents the raw has a valid object handle but it may be going to be mapped with
+     * another rows invalidated. After fetching all documents under the parent, the database tries
+     * to map the pending documents and the invalidated documents in order to keep old document ID
+     * alive.
+     */
+    static final int ROW_STATE_PENDING = 2;
+
+    /**
+     * Mapping mode that uses MTP identifier to find corresponding rows.
+     */
+    static final int MAP_BY_MTP_IDENTIFIER = 0;
+
+    /**
+     * Mapping mode that uses name to find corresponding rows.
+     */
+    static final int MAP_BY_NAME = 1;
+
+    static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
+    static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
+    static final String SELECTION_ROOT_DOCUMENTS =
+            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+    static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+
+    static final String QUERY_CREATE_DOCUMENTS =
+            "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
+            Document.COLUMN_DOCUMENT_ID +
+                " INTEGER PRIMARY KEY AUTOINCREMENT," +
+            COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
+            COLUMN_STORAGE_ID + " INTEGER," +
+            COLUMN_OBJECT_HANDLE + " INTEGER," +
+            COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
+            COLUMN_ROW_STATE + " INTEGER NOT NULL," +
+            Document.COLUMN_MIME_TYPE + " TEXT," +
+            Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
+            Document.COLUMN_SUMMARY + " TEXT," +
+            Document.COLUMN_LAST_MODIFIED + " INTEGER," +
+            Document.COLUMN_ICON + " INTEGER," +
+            Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
+            Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+
+    static final String QUERY_CREATE_ROOT_EXTRA =
+            "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
+            Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
+            Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
+            Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
+            Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
+            Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
+
+    /**
+     * Creates a view to join Documents table and RootExtra table on their primary keys to
+     * provide DocumentContract.Root equivalent information.
+     */
+    static final String QUERY_CREATE_VIEW_ROOTS =
+            "CREATE VIEW " + VIEW_ROOTS + " AS SELECT " +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+                            Root.COLUMN_ROOT_ID + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " +
+                            Root.COLUMN_ICON + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " +
+                            Root.COLUMN_TITLE + "," +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
+                            Root.COLUMN_SUMMARY + "," +
+                    // Temporary replace COLUMN_DOCUMENT_ID with old format.
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
+                    Root.COLUMN_DOCUMENT_ID + "_," +
+                    TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
+                    TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
+                    Root.COLUMN_DOCUMENT_ID + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES + "," +
+                    TABLE_DOCUMENTS + "." + COLUMN_ROW_STATE +
+            " FROM " + TABLE_DOCUMENTS + " INNER JOIN " + TABLE_ROOT_EXTRA +
+            " ON " +
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL AND " +
+                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
+                    "=" +
+                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID;
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
new file mode 100644
index 0000000..9c5d6b6
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.mtp;
+
+import static com.android.mtp.MtpDatabaseConstants.*;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import java.util.Objects;
+
+/**
+ * Class that provides operations processing SQLite database directly.
+ */
+class MtpDatabaseInternal {
+    private static class OpenHelper extends SQLiteOpenHelper {
+        public OpenHelper(Context context, int flags) {
+            super(context,
+                  flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+                  null,
+                  DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(QUERY_CREATE_DOCUMENTS);
+            db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+            db.execSQL(QUERY_CREATE_VIEW_ROOTS);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private final SQLiteDatabase mDatabase;
+
+    MtpDatabaseInternal(Context context, int flags) {
+        final OpenHelper helper = new OpenHelper(context, flags);
+        mDatabase = helper.getWritableDatabase();
+    }
+
+    void close() {
+        mDatabase.close();
+    }
+
+    /**
+     * Queries roots information.
+     * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
+     * @return Database cursor.
+     */
+    Cursor queryRoots(String[] columnNames) {
+        return mDatabase.query(
+                VIEW_ROOTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?)",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+                null,
+                null,
+                null);
+    }
+
+    /**
+     * Queries root documents information.
+     * @param columnNames Column names defined in
+     *     {@link android.provider.DocumentsContract.Document}.
+     * @return Database cursor.
+     */
+    Cursor queryRootDocuments(String[] columnNames) {
+        return mDatabase.query(
+                TABLE_DOCUMENTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?)",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+                null,
+                null,
+                null);
+    }
+
+    /**
+     * Queries documents information.
+     * @param columnNames Column names defined in
+     *     {@link android.provider.DocumentsContract.Document}.
+     * @return Database cursor.
+     */
+    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
+        return mDatabase.query(
+                TABLE_DOCUMENTS,
+                columnNames,
+                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+                null,
+                null,
+                null);
+    }
+
+    /**
+     * Remove all rows belong to a device.
+     * @param deviceId Device ID.
+     */
+    void removeDeviceRows(int deviceId) {
+        deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
+    }
+
+    /**
+     * Gets identifier from document ID.
+     * @param documentId Document ID.
+     * @return Identifier.
+     */
+    Identifier createIdentifier(String documentId) {
+        // Currently documentId is old format.
+        final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
+        final String selection;
+        final String[] args;
+        if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId);
+        } else {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_OBJECT_HANDLE + " = ?";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId,
+                    oldIdentifier.mObjectHandle);
+        }
+
+        final Cursor cursor = mDatabase.query(
+                TABLE_DOCUMENTS,
+                strings(Document.COLUMN_DOCUMENT_ID),
+                selection,
+                args,
+                null,
+                null,
+                null,
+                "1");
+        try {
+            if (cursor.getCount() == 0) {
+                return oldIdentifier;
+            } else {
+                cursor.moveToNext();
+                return new Identifier(
+                        oldIdentifier.mDeviceId,
+                        oldIdentifier.mStorageId,
+                        oldIdentifier.mObjectHandle,
+                        cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Starts adding new documents.
+     * The methods decides mapping mode depends on if all documents under the given parent have MTP
+     * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
+     * a corresponding existing row. Otherwise it does heuristic.
+     *
+     * @param selection Query matches valid documents.
+     * @param arg Argument for selection.
+     * @return Mapping mode.
+     */
+    int startAddingDocuments(String selection, String arg) {
+        mDatabase.beginTransaction();
+        try {
+            // Delete all pending rows.
+            deleteDocumentsAndRoots(
+                    selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+
+            // Set all documents as invalidated.
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+            mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+
+            // If we have rows that does not have MTP identifier, do heuristic mapping by name.
+            final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
+                    mDatabase,
+                    TABLE_DOCUMENTS,
+                    selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
+                    new String[] { arg }) > 0;
+            mDatabase.setTransactionSuccessful();
+            return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Puts the documents into the database.
+     * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
+     * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
+     * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
+     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+     * rows. If the methods adds rows to database, it updates valueList with correct document ID.
+     *
+     * @param valuesList Values for documents to be stored in the database.
+     * @param selection SQL where closure to select rows that shares the same parent.
+     * @param arg Argument for selection SQL.
+     * @param heuristic Whether the mapping mode is heuristic.
+     * @return Whether the method adds new rows.
+     */
+    boolean putDocuments(
+            ContentValues[] valuesList,
+            String selection,
+            String arg,
+            boolean heuristic,
+            String mappingKey) {
+        boolean added = false;
+        mDatabase.beginTransaction();
+        try {
+            for (final ContentValues values : valuesList) {
+                final Cursor candidateCursor = mDatabase.query(
+                        TABLE_DOCUMENTS,
+                        strings(Document.COLUMN_DOCUMENT_ID),
+                        selection + " AND " +
+                        COLUMN_ROW_STATE + "=? AND " +
+                        mappingKey + "=?",
+                        strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+                        null,
+                        null,
+                        null,
+                        "1");
+                try {
+                    final long rowId;
+                    if (candidateCursor.getCount() == 0) {
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                        if (rowId == -1) {
+                            throw new SQLiteException("Failed to put a document into database.");
+                        }
+                        added = true;
+                    } else if (!heuristic) {
+                        candidateCursor.moveToNext();
+                        final String documentId = candidateCursor.getString(0);
+                        rowId = mDatabase.update(
+                                TABLE_DOCUMENTS,
+                                values,
+                                SELECTION_DOCUMENT_ID,
+                                strings(documentId));
+                    } else {
+                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                    }
+                    // Document ID is a primary integer key of the table. So the returned row
+                    // IDs should be same with the document ID.
+                    values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+                } finally {
+                    candidateCursor.close();
+                }
+            }
+
+            mDatabase.setTransactionSuccessful();
+            return added;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Puts extra information for root documents.
+     * @param values Values containing extra information.
+     */
+    void putRootExtra(ContentValues values) {
+        mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
+    }
+
+    /**
+     * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
+     * If the database does not find corresponding 'invalidated' document, it just removes
+     * 'invalidated' document from the database.
+     * @param selection Query to select rows for resolving.
+     * @param arg Argument for selection SQL.
+     * @param groupKey Column name used to find corresponding rows.
+     * @return Whether the methods adds or removed visible rows.
+     */
+    boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+        mDatabase.beginTransaction();
+        try {
+            // Get 1-to-1 mapping of invalidated document and pending document.
+            final String invalidatedIdQuery = createStateFilter(
+                    ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
+            final String pendingIdQuery = createStateFilter(
+                    ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
+            // SQL should be like:
+            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
+            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
+            // WHERE device_id = ? AND parent_document_id IS NULL
+            // GROUP BY display_name
+            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
+            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
+            final Cursor mergingCursor = mDatabase.query(
+                    TABLE_DOCUMENTS,
+                    new String[] {
+                            "group_concat(" + invalidatedIdQuery + ")",
+                            "group_concat(" + pendingIdQuery + ")"
+                    },
+                    selection,
+                    strings(arg),
+                    groupKey,
+                    "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
+                    null);
+
+            final ContentValues values = new ContentValues();
+            while (mergingCursor.moveToNext()) {
+                final String invalidatedId = mergingCursor.getString(0);
+                final String pendingId = mergingCursor.getString(1);
+
+                // Obtain the new values including the latest object handle from mapping row.
+                getFirstRow(
+                        TABLE_DOCUMENTS,
+                        SELECTION_DOCUMENT_ID,
+                        new String[] { pendingId },
+                        values);
+                values.remove(Document.COLUMN_DOCUMENT_ID);
+                values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+                mDatabase.update(
+                        TABLE_DOCUMENTS,
+                        values,
+                        SELECTION_DOCUMENT_ID,
+                        new String[] { invalidatedId });
+
+                getFirstRow(
+                        TABLE_ROOT_EXTRA,
+                        SELECTION_ROOT_ID,
+                        new String[] { pendingId },
+                        values);
+                if (values.size() > 0) {
+                    values.remove(Root.COLUMN_ROOT_ID);
+                    mDatabase.update(
+                            TABLE_ROOT_EXTRA,
+                            values,
+                            SELECTION_ROOT_ID,
+                            new String[] { invalidatedId });
+                }
+
+                // Delete 'pending' row.
+                deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId });
+            }
+            mergingCursor.close();
+
+            boolean changed = false;
+
+            // Delete all invalidated rows that cannot be mapped.
+            if (deleteDocumentsAndRoots(
+                    COLUMN_ROW_STATE + " = ? AND " + selection,
+                    strings(ROW_STATE_INVALIDATED, arg))) {
+                changed = true;
+            }
+
+            // The database cannot find old document ID for the pending rows.
+            // Turn the all pending rows into valid state, which means the rows become to be
+            // valid with new document ID.
+            values.clear();
+            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+            if (mDatabase.update(
+                    TABLE_DOCUMENTS,
+                    values,
+                    COLUMN_ROW_STATE + " = ? AND " + selection,
+                    strings(ROW_STATE_PENDING, arg)) != 0) {
+                changed = true;
+            }
+            mDatabase.setTransactionSuccessful();
+            return changed;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Clears MTP related identifier.
+     * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
+     * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
+     * now.
+     */
+    void clearMapping() {
+        mDatabase.beginTransaction();
+        try {
+            deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+            final ContentValues values = new ContentValues();
+            values.putNull(COLUMN_OBJECT_HANDLE);
+            values.putNull(COLUMN_STORAGE_ID);
+            values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+            mDatabase.update(TABLE_DOCUMENTS, values, null, null);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
+     */
+    void beginTransaction() {
+        mDatabase.beginTransaction();
+    }
+
+    /**
+     * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
+     */
+    void setTransactionSuccessful() {
+        mDatabase.setTransactionSuccessful();
+    }
+
+    /**
+     * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
+     */
+    void endTransaction() {
+        mDatabase.endTransaction();
+    }
+
+    /**
+     * Deletes a document, and its root information if the document is a root document.
+     * @param selection Query to select documents.
+     * @param args Arguments for selection.
+     * @return Whether the method deletes rows.
+     */
+    private boolean deleteDocumentsAndRoots(String selection, String[] args) {
+        mDatabase.beginTransaction();
+        try {
+            int deleted = 0;
+            deleted += mDatabase.delete(
+                    TABLE_ROOT_EXTRA,
+                    Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+                            false,
+                            TABLE_DOCUMENTS,
+                            new String[] { Document.COLUMN_DOCUMENT_ID },
+                            selection,
+                            null,
+                            null,
+                            null,
+                            null) + ")",
+                    args);
+            deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+            mDatabase.setTransactionSuccessful();
+            return deleted != 0;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * Obtains values of the first row for the query.
+     * @param values ContentValues that the values are stored to.
+     * @param table Target table.
+     * @param selection Query to select rows.
+     * @param args Argument for query.
+     */
+    private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+        values.clear();
+        final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
+        if (cursor.getCount() == 0) {
+            return;
+        }
+        cursor.moveToNext();
+        DatabaseUtils.cursorRowToContentValues(cursor, values);
+        cursor.close();
+    }
+
+    /**
+     * Gets SQL expression that represents the given value or NULL depends on the row state.
+     * @param state Expected row state.
+     * @param a SQL value.
+     * @return Expression that represents a if the row state is expected one, and represents NULL
+     *     otherwise.
+     */
+    private static String createStateFilter(int state, String a) {
+        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
+                " THEN " + a + " ELSE NULL END";
+    }
+
+    /**
+     * Converts values into string array.
+     * @param args Values converted into string array.
+     * @return String array.
+     */
+    private static String[] strings(Object... args) {
+        final String[] results = new String[args.length];
+        for (int i = 0; i < args.length; i++) {
+            results[i] = Objects.toString(args[i]);
+        }
+        return results;
+    }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 7883e61..7c0676f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -31,6 +31,7 @@
 import android.provider.DocumentsProvider;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.FileNotFoundException;
@@ -59,9 +60,11 @@
 
     private MtpManager mMtpManager;
     private ContentResolver mResolver;
+    @GuardedBy("mDeviceToolkits")
     private Map<Integer, DeviceToolkit> mDeviceToolkits;
     private RootScanner mRootScanner;
     private Resources mResources;
+    private MtpDatabase mDatabase;
 
     /**
      * Provides singleton instance to MtpDocumentsService.
@@ -77,17 +80,23 @@
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
-        mRootScanner = new RootScanner(mResolver, mMtpManager);
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
+        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
         return true;
     }
 
     @VisibleForTesting
-    void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) {
+    void onCreateForTesting(
+            Resources resources,
+            MtpManager mtpManager,
+            ContentResolver resolver,
+            MtpDatabase database) {
         mResources = resources;
         mMtpManager = mtpManager;
         mResolver = resolver;
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
-        mRootScanner = new RootScanner(mResolver, mMtpManager);
+        mDatabase = database;
+        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
     }
 
     @Override
@@ -95,17 +104,7 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
         }
-        final MatrixCursor cursor = new MatrixCursor(projection);
-        final MtpRoot[] roots = mRootScanner.getRoots();
-        for (final MtpRoot root : roots) {
-            final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId);
-            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.getRootName(mResources));
-            builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId());
-            builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace);
-        }
+        final Cursor cursor = mDatabase.queryRoots(projection);
         cursor.setNotificationUri(
                 mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
         return cursor;
@@ -158,7 +157,7 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
         }
-        final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId);
+        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
         try {
             return getDocumentLoader(parentIdentifier).queryChildDocuments(
                     projection, parentIdentifier);
@@ -225,9 +224,11 @@
 
     @Override
     public void onTrimMemory(int level) {
-      for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
-          toolkit.mDocumentLoader.clearCompletedTasks();
-      }
+        synchronized (mDeviceToolkits) {
+            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
+                toolkit.mDocumentLoader.clearCompletedTasks();
+            }
+        }
     }
 
     @Override
@@ -257,23 +258,32 @@
     }
 
     void openDevice(int deviceId) throws IOException {
-        mMtpManager.openDevice(deviceId);
-        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver));
-        mRootScanner.scanNow();
+        synchronized (mDeviceToolkits) {
+            mMtpManager.openDevice(deviceId);
+            mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+        }
+        mRootScanner.resume();
     }
 
-    void closeDevice(int deviceId) throws IOException {
+    void closeDevice(int deviceId) throws IOException, InterruptedException {
         // TODO: Flush the device before closing (if not closed externally).
-        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
-        mDeviceToolkits.remove(deviceId);
-        mMtpManager.closeDevice(deviceId);
-        mRootScanner.scanNow();
+        synchronized (mDeviceToolkits) {
+            getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+            mDeviceToolkits.remove(deviceId);
+            mDatabase.removeDeviceRows(deviceId);
+            mMtpManager.closeDevice(deviceId);
+        }
+        mRootScanner.notifyChange();
+        if (!hasOpenedDevices()) {
+            mRootScanner.pause();
+        }
     }
 
-    void closeAllDevices() {
+    synchronized void closeAllDevices() throws InterruptedException {
         boolean closed = false;
         for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
             try {
+                mDatabase.removeDeviceRows(deviceId);
                 mMtpManager.closeDevice(deviceId);
                 getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
                 closed = true;
@@ -282,7 +292,8 @@
             }
         }
         if (closed) {
-            mRootScanner.scanNow();
+            mRootScanner.notifyChange();
+            mRootScanner.pause();
         }
     }
 
@@ -290,6 +301,22 @@
         return mMtpManager.getOpenedDeviceIds().length != 0;
     }
 
+    /**
+     * Finalize the content provider for unit tests.
+     */
+    @Override
+    public void shutdown() {
+        try {
+            closeAllDevices();
+        } catch (InterruptedException e) {
+            // It should fail unit tests by throwing runtime exception.
+            throw new RuntimeException(e.getMessage());
+        } finally {
+            mDatabase.close();
+            super.shutdown();
+        }
+    }
+
     private void notifyChildDocumentsChange(String parentDocumentId) {
         mResolver.notifyChange(
                 DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
@@ -298,11 +325,13 @@
     }
 
     private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
-        final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
-        if (toolkit == null) {
-            throw new FileNotFoundException();
+        synchronized (mDeviceToolkits) {
+            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
+            if (toolkit == null) {
+                throw new FileNotFoundException();
+            }
+            return toolkit;
         }
-        return toolkit;
     }
 
     private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
@@ -317,9 +346,9 @@
         public final PipeManager mPipeManager;
         public final DocumentLoader mDocumentLoader;
 
-        public DeviceToolkit(MtpManager manager, ContentResolver resolver) {
+        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
             mPipeManager = new PipeManager();
-            mDocumentLoader = new DocumentLoader(manager, resolver);
+            mDocumentLoader = new DocumentLoader(manager, resolver, database);
         }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 328f618..723dc14 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -67,10 +67,10 @@
                 provider.openDevice(device.getDeviceId());
                 return START_STICKY;
             } catch (IOException error) {
-                Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+                Log.e(MtpDocumentsProvider.TAG, error.getMessage());
             }
         } else {
-            Log.d(MtpDocumentsProvider.TAG, "Received unknown intent action.");
+            Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
         }
         stopSelfIfNeeded();
         return Service.START_NOT_STICKY;
@@ -79,9 +79,13 @@
     @Override
     public void onDestroy() {
         final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
-        provider.closeAllDevices();
         unregisterReceiver(mReceiver);
         mReceiver = null;
+        try {
+            provider.closeAllDevices();
+        } catch (InterruptedException e) {
+            Log.e(MtpDocumentsProvider.TAG, e.getMessage());
+        }
         super.onDestroy();
     }
 
@@ -101,8 +105,8 @@
                 final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
                 try {
                     provider.closeDevice(device.getDeviceId());
-                } catch (IOException error) {
-                    Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+                } catch (IOException | InterruptedException error) {
+                    Log.e(MtpDocumentsProvider.TAG, error.getMessage());
                 }
                 stopSelfIfNeeded();
             }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index ffab176..b0962dd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -1,14 +1,18 @@
 package com.android.mtp;
 
 import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Process;
 import android.provider.DocumentsContract;
 import android.util.Log;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
 
 final class RootScanner {
     /**
@@ -27,79 +31,118 @@
      */
     private final static long SHORT_POLLING_TIMES = 10;
 
-    final ContentResolver mResolver;
-    final MtpManager mManager;
-    MtpRoot[] mLastRoots = new MtpRoot[0];
-    int mPollingCount;
-    boolean mHasBackgroundTask = false;
+    /**
+     * Milliseconds we wait for background thread when pausing.
+     */
+    private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
 
-    RootScanner(ContentResolver resolver, MtpManager manager) {
+    final ContentResolver mResolver;
+    final Resources mResources;
+    final MtpManager mManager;
+    final MtpDatabase mDatabase;
+
+    ExecutorService mExecutor;
+    FutureTask<Void> mCurrentTask;
+
+    RootScanner(
+            ContentResolver resolver,
+            Resources resources,
+            MtpManager manager,
+            MtpDatabase database) {
         mResolver = resolver;
+        mResources = resources;
         mManager = manager;
+        mDatabase = database;
     }
 
-    synchronized MtpRoot[] getRoots() {
-        return mLastRoots;
+    /**
+     * Notifies a change of the roots list via ContentResolver.
+     */
+    void notifyChange() {
+        final Uri uri =
+                DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
+        mResolver.notifyChange(uri, null, false);
     }
 
     /**
      * Starts to check new changes right away.
      * If the background thread has already gone, it restarts another background thread.
      */
-    synchronized void scanNow() {
-        mPollingCount = 0;
-        if (!mHasBackgroundTask) {
-            mHasBackgroundTask = true;
-            new BackgroundLoaderThread().start();
-        } else {
-            notify();
+    synchronized void resume() {
+        if (mExecutor == null) {
+            // Only single thread updates the database.
+            mExecutor = Executors.newSingleThreadExecutor();
         }
+        if (mCurrentTask != null) {
+            // Cancel previous task.
+            mCurrentTask.cancel(true);
+        }
+        mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+        mExecutor.submit(mCurrentTask);
     }
 
-    private MtpRoot[] createRoots(int[] deviceIds) {
-        final ArrayList<MtpRoot> roots = new ArrayList<>();
-        for (final int deviceId : deviceIds) {
-            try {
-                roots.addAll(Arrays.asList(mManager.getRoots(deviceId)));
-            } catch (IOException error) {
-                // Skip device that return error.
-                Log.d(MtpDocumentsProvider.TAG, error.getMessage());
-            }
+    /**
+     * Stops background thread and wait for its termination.
+     * @throws InterruptedException
+     */
+    synchronized void pause() throws InterruptedException {
+        if (mExecutor == null) {
+            return;
         }
-        return roots.toArray(new MtpRoot[roots.size()]);
+        mExecutor.shutdownNow();
+        if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
+            Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread.");
+        }
+        mExecutor = null;
     }
 
-    private final class BackgroundLoaderThread extends Thread {
+    /**
+     * Runnable to scan roots and update the database information.
+     */
+    private final class UpdateRootsRunnable implements Runnable {
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            synchronized (RootScanner.this) {
-                while (true) {
-                    final int[] deviceIds = mManager.getOpenedDeviceIds();
-                    final MtpRoot[] newRoots = createRoots(deviceIds);
-                    if (!Arrays.equals(mLastRoots, newRoots)) {
-                        final Uri uri =
-                                DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
-                        mResolver.notifyChange(uri, null, false);
-                        mLastRoots = newRoots;
-                    }
-                    if (deviceIds.length == 0) {
-                        break;
-                    }
-                    mPollingCount++;
+            int pollingCount = 0;
+            while (!Thread.interrupted()) {
+                final int[] deviceIds = mManager.getOpenedDeviceIds();
+                if (deviceIds.length == 0) {
+                    return;
+                }
+                boolean changed = false;
+                for (int deviceId : deviceIds) {
                     try {
-                        // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
-                        // more likely to add new root just after the device is added.
-                        // TODO: Use short interval only for a device that is just added.
-                        RootScanner.this.wait(
-                                mPollingCount > SHORT_POLLING_TIMES ?
-                                        LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
-                    } catch (InterruptedException exception) {
-                        break;
+                        final MtpRoot[] roots = mManager.getRoots(deviceId);
+                        mDatabase.startAddingRootDocuments(deviceId);
+                        try {
+                            if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+                                changed = true;
+                            }
+                        } finally {
+                            if (mDatabase.stopAddingRootDocuments(deviceId)) {
+                                changed = true;
+                            }
+                        }
+                    } catch (IOException | SQLiteException exception) {
+                        // The error may happen on the device. We would like to continue getting
+                        // roots for other devices.
+                        Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
                     }
                 }
-
-                mHasBackgroundTask = false;
+                if (changed) {
+                    notifyChange();
+                }
+                pollingCount++;
+                try {
+                    // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
+                    // more likely to add new root just after the device is added.
+                    // TODO: Use short interval only for a device that is just added.
+                    Thread.sleep(pollingCount > SHORT_POLLING_TIMES ?
+                        LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
+                } catch (InterruptedException exp) {
+                    // The while condition handles the interrupted flag.
+                    continue;
+                }
             }
         }
     }
diff --git a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
index 28ad3f4..e1307e9 100644
--- a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
@@ -18,7 +18,4 @@
     <instrumentation android:name="com.android.mtp.TestResultInstrumentation"
         android:targetPackage="com.android.mtp"
         android:label="Tests for MtpDocumentsProvider with the UI for output." />
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.mtp"
-        android:label="Tests for MtpDocumentsProvider." />
 </manifest>
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a012d7f..a80eb51 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -22,26 +22,37 @@
 import android.net.Uri;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
-@SmallTest
+@MediumTest
 public class DocumentLoaderTest extends AndroidTestCase {
+    private MtpDatabase mDatabase;
     private BlockableTestMtpManager mManager;
     private TestContentResolver mResolver;
     private DocumentLoader mLoader;
-    final private Identifier mParentIdentifier = new Identifier(0, 0, 0);
+    final private Identifier mParentIdentifier = new Identifier(0, 0, 0, "1");
 
     @Override
     public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
         mManager = new BlockableTestMtpManager(getContext());
         mResolver = new TestContentResolver();
-        mLoader = new DocumentLoader(mManager, mResolver);
+        mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
+    }
+
+    @Override
+    public void tearDown() {
+        mDatabase.close();
     }
 
     public void testBasic() throws Exception {
@@ -88,6 +99,7 @@
             childDocuments[i] = objectHandle;
             manager.setObjectInfo(0, new MtpObjectInfo.Builder()
                     .setObjectHandle(objectHandle)
+                    .setName(Integer.toString(i))
                     .build());
         }
         manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7bc9972..25dd1c8 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -20,6 +20,7 @@
 import android.mtp.MtpConstants;
 import android.mtp.MtpObjectInfo;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Root;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -27,9 +28,9 @@
 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,
+        MtpDatabaseConstants.COLUMN_DEVICE_ID,
+        MtpDatabaseConstants.COLUMN_STORAGE_ID,
+        MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
         DocumentsContract.Document.COLUMN_MIME_TYPE,
         DocumentsContract.Document.COLUMN_DISPLAY_NAME,
         DocumentsContract.Document.COLUMN_SUMMARY,
@@ -40,45 +41,100 @@
     };
 
     private final TestResources resources = new TestResources();
+    MtpDatabase mDatabase;
+
+    @Override
+    public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+    }
 
     @Override
     public void tearDown() {
-        MtpDatabase.deleteDatabase(getContext());
+        mDatabase.close();
+        mDatabase = null;
     }
 
     public void testPutRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
-                new MtpRoot(0, 2, "Device", "Storage", 1000, 2000, ""),
-                new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000,"")
+                new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
+                new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
         });
 
-        final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES);
-        assertEquals(3, cursor.getCount());
+        {
+            final Cursor cursor = mDatabase.queryRootDocuments(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("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4));
-        assertEquals("displayName", "Device Storage", cursor.getString(5));
-        assertTrue("summary", cursor.isNull(6));
-        assertTrue("lastModified", cursor.isNull(7));
-        assertTrue("icon", cursor.isNull(8));
-        assertEquals("flag", 0, cursor.getInt(9));
-        assertEquals("size", 1000, cursor.getInt(10));
+            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("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4));
+            assertEquals("displayName", "Device Storage", cursor.getString(5));
+            assertTrue("summary", cursor.isNull(6));
+            assertTrue("lastModified", cursor.isNull(7));
+            assertTrue("icon", cursor.isNull(8));
+            assertEquals("flag", 0, cursor.getInt(9));
+            assertEquals("size", 1000, cursor.getInt(10));
 
-        cursor.moveToNext();
-        assertEquals("documentId", 2, cursor.getInt(0));
-        assertEquals("displayName", "Device Storage", cursor.getString(5));
+            cursor.moveToNext();
+            assertEquals("documentId", 2, cursor.getInt(0));
+            assertEquals("displayName", "Device Storage", cursor.getString(5));
 
-        cursor.moveToNext();
-        assertEquals("documentId", 3, cursor.getInt(0));
-        assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5));
+            cursor.moveToNext();
+            assertEquals("documentId", 3, cursor.getInt(0));
+            assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5));
 
-        cursor.close();
+            cursor.close();
+        }
+
+        {
+            final Cursor cursor = mDatabase.queryRoots(new String [] {
+                    Root.COLUMN_ROOT_ID,
+                    Root.COLUMN_FLAGS,
+                    Root.COLUMN_ICON,
+                    Root.COLUMN_TITLE,
+                    Root.COLUMN_SUMMARY,
+                    Root.COLUMN_DOCUMENT_ID + "_",
+                    Root.COLUMN_AVAILABLE_BYTES,
+                    Root.COLUMN_CAPACITY_BYTES
+            });
+            assertEquals(3, cursor.getCount());
+
+            cursor.moveToNext();
+            assertEquals(1, cursor.getInt(0));
+            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
+            assertTrue(cursor.isNull(2));
+            assertEquals("Device Storage", cursor.getString(3));
+            assertTrue(cursor.isNull(4));
+            assertEquals(1, cursor.getInt(5));
+            assertEquals(1000, cursor.getInt(6));
+            assertEquals(2000, cursor.getInt(7));
+
+            cursor.moveToNext();
+            assertEquals(2, cursor.getInt(0));
+            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
+            assertTrue(cursor.isNull(2));
+            assertEquals("Device Storage", cursor.getString(3));
+            assertTrue(cursor.isNull(4));
+            assertEquals(2, cursor.getInt(5));
+            assertEquals(2000, cursor.getInt(6));
+            assertEquals(4000, cursor.getInt(7));
+
+            cursor.moveToNext();
+            assertEquals(3, cursor.getInt(0));
+            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
+            assertTrue(cursor.isNull(2));
+            assertEquals("Device /@#%&<>Storage", cursor.getString(3));
+            assertTrue(cursor.isNull(4));
+            assertEquals(3, cursor.getInt(5));
+            assertEquals(3000, cursor.getInt(6));
+            assertEquals(6000, cursor.getInt(7));
+
+            cursor.close();
+        }
     }
 
     private MtpObjectInfo createDocument(int objectHandle, String name, int format, int size) {
@@ -91,15 +147,14 @@
     }
 
     public void testPutChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
 
-        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId");
+        final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
         assertEquals(3, cursor.getCount());
 
         cursor.moveToNext();
@@ -157,19 +212,24 @@
     }
 
     public void testRestoreIdForRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
-                new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "")
+        final String[] rootColumns = new String[] {
+                Root.COLUMN_ROOT_ID,
+                Root.COLUMN_AVAILABLE_BYTES
+        };
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
+                new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -182,10 +242,22 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            cursor.close();
+        }
+
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -198,13 +270,26 @@
             cursor.close();
         }
 
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage A", 0, 0, ""),
-                new MtpRoot(0, 202, "Device", "Storage C", 0, 0, "")
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            cursor.close();
+        }
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
+                new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -221,10 +306,25 @@
             cursor.close();
         }
 
-        database.resolveRootDocuments(0);
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(3, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 4, cursor.getInt(0));
+            assertEquals("availableBytes", 2002, cursor.getInt(1));
+            cursor.close();
+        }
+
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -236,24 +336,36 @@
             assertEquals("name", "Device Storage C", cursor.getString(2));
             cursor.close();
         }
+
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 4, cursor.getInt(0));
+            assertEquals("availableBytes", 2002, cursor.getInt(1));
+            cursor.close();
+        }
     }
 
     public void testRestoreIdForChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_OBJECT_HANDLE,
+                MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
@@ -274,13 +386,14 @@
             cursor.close();
         }
 
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
         });
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(4, cursor.getCount());
 
             cursor.moveToPosition(3);
@@ -291,10 +404,10 @@
             cursor.close();
         }
 
-        database.resolveChildDocuments("parentId");
+        mDatabase.stopAddingChildDocuments("parentId");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(2, cursor.getCount());
 
             cursor.moveToNext();
@@ -311,21 +424,26 @@
     }
 
     public void testRestoreIdForDifferentDevices() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        final String[] rootColumns = new String[] {
+                Root.COLUMN_ROOT_ID,
+                Root.COLUMN_AVAILABLE_BYTES
+        };
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -338,19 +456,33 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 0, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 0, cursor.getInt(1));
+            cursor.close();
+        }
 
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 0, 0, "")
+        mDatabase.clearMapping();
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
-                new MtpRoot(1, 300, "Device", "Storage", 0, 0, "")
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+                new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        database.resolveRootDocuments(0);
-        database.resolveRootDocuments(1);
+        mDatabase.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(1);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -362,31 +494,48 @@
             assertEquals("name", "Device Storage", cursor.getString(2));
             cursor.close();
         }
+
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 3000, cursor.getInt(1));
+            cursor.close();
+        }
     }
 
     public void testRestoreIdForDifferentParents() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_OBJECT_HANDLE
+                MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.clearMapping();
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        mDatabase.clearMapping();
+
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.resolveChildDocuments("parentId1");
+        mDatabase.stopAddingChildDocuments("parentId1");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -394,7 +543,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId2");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -404,26 +553,36 @@
     }
 
     public void testClearMtpIdentifierBeforeResolveRootDocuments() {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        final String[] rootColumns = new String[] {
+                Root.COLUMN_ROOT_ID,
+                Root.COLUMN_AVAILABLE_BYTES
+        };
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 0, 0, ""),
+        mDatabase.clearMapping();
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
-        database.clearMapping();
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 300, "Device", "Storage", 0, 0, ""),
+        mDatabase.clearMapping();
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        database.resolveRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
+
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -431,26 +590,42 @@
             assertEquals("name", "Device Storage", cursor.getString(2));
             cursor.close();
         }
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 3000, cursor.getInt(1));
+            cursor.close();
+        }
     }
 
     public void testPutSameNameRootsAfterClearing() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
-                MtpDatabase.COLUMN_STORAGE_ID,
+                MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        final String[] rootColumns = new String[] {
+                Root.COLUMN_ROOT_ID,
+                Root.COLUMN_AVAILABLE_BYTES
+        };
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
-        database.putRootDocuments(0, resources, new MtpRoot[] {
-                new MtpRoot(0, 200, "Device", "Storage", 0, 0, ""),
-                new MtpRoot(0, 201, "Device", "Storage", 0, 0, ""),
+        mDatabase.clearMapping();
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
+                new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
-        database.resolveRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
+
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -462,5 +637,81 @@
             assertEquals("name", "Device Storage", cursor.getString(2));
             cursor.close();
         }
+        {
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
+            assertEquals(2, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 2, cursor.getInt(0));
+            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            cursor.moveToNext();
+            assertEquals("rootId", 3, cursor.getInt(0));
+            assertEquals("availableBytes", 2001, cursor.getInt(1));
+            cursor.close();
+        }
+    }
+
+    public void testReplaceExistingRoots() {
+        // The client code should be able to replace existing rows with new information.
+        // Add one.
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        });
+        mDatabase.stopAddingRootDocuments(0);
+        // Replace it.
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+        });
+        mDatabase.stopAddingRootDocuments(0);
+        {
+            final String[] columns = new String[] {
+                    DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+                    MtpDatabaseConstants.COLUMN_STORAGE_ID,
+                    DocumentsContract.Document.COLUMN_DISPLAY_NAME
+            };
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("documentId", 1, cursor.getInt(0));
+            assertEquals("storageId", 100, cursor.getInt(1));
+            assertEquals("name", "Device Storage B", cursor.getString(2));
+            cursor.close();
+        }
+        {
+            final String[] columns = new String[] {
+                    Root.COLUMN_ROOT_ID,
+                    Root.COLUMN_AVAILABLE_BYTES
+            };
+            final Cursor cursor = mDatabase.queryRoots(columns);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("rootId", 1, cursor.getInt(0));
+            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            cursor.close();
+        }
+    }
+
+    public void _testFailToReplaceExisitingUnmappedRoots() {
+        // The client code should not be able to replace rows before resolving 'unmapped' rows.
+        // Add one.
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+        });
+        mDatabase.clearMapping();
+        // Add one.
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+        });
+        // Add one more before resolving unmapped documents.
+        try {
+            mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+                    new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+            });
+            fail();
+        } catch (Throwable e) {
+            assertTrue(e instanceof Error);
+        }
     }
 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 5765f0a..cabb08d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -36,13 +36,20 @@
     private MtpDocumentsProvider mProvider;
     private TestMtpManager mMtpManager;
     private final TestResources mResources = new TestResources();
+    private MtpDatabase mDatabase;
 
     @Override
     public void setUp() throws IOException {
         mResolver = new TestContentResolver();
         mMtpManager = new TestMtpManager(getContext());
         mProvider = new MtpDocumentsProvider();
-        mProvider.onCreateForTesting(mResources, mMtpManager, mResolver);
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
+    }
+
+    @Override
+    public void tearDown() {
+        mProvider.shutdown();
     }
 
     public void testOpenAndCloseDevice() throws Exception {
@@ -96,26 +103,6 @@
         mResolver.waitForNotification(ROOTS_URI, 1);
     }
 
-    public void testCloseAllDevices() throws Exception {
-        mMtpManager.addValidDevice(0);
-        mMtpManager.setRoots(0, new MtpRoot[] {
-                new MtpRoot(
-                        0 /* deviceId */,
-                        1 /* storageId */,
-                        "Device A" /* device model name */,
-                        "Storage A" /* volume description */,
-                        1024 /* free space */,
-                        2048 /* total space */,
-                        "" /* no volume identifier */)
-        });
-        mProvider.closeAllDevices();
-        mProvider.openDevice(0);
-        mResolver.waitForNotification(ROOTS_URI, 1);
-
-        mProvider.closeAllDevices();
-        mResolver.waitForNotification(ROOTS_URI, 2);
-    }
-
     public void testQueryRoots() throws Exception {
         mMtpManager.addValidDevice(0);
         mMtpManager.addValidDevice(1);
@@ -146,7 +133,7 @@
             final Cursor cursor = mProvider.queryRoots(null);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("0_1", cursor.getString(0));
+            assertEquals("1", cursor.getString(0));
             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 */);
@@ -162,7 +149,7 @@
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             cursor.moveToNext();
-            assertEquals("1_1", cursor.getString(0));
+            assertEquals("2", cursor.getString(0));
             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 */);
@@ -170,13 +157,6 @@
             assertEquals("1_1_0", cursor.getString(4));
             assertEquals(2048, cursor.getInt(5));
         }
-
-        {
-            mProvider.closeAllDevices();
-            mResolver.waitForNotification(ROOTS_URI, 3);
-            final Cursor cursor = mProvider.queryRoots(null);
-            assertEquals(0, cursor.getCount());
-        }
     }
 
     public void testQueryRoots_error() throws Exception {
@@ -201,7 +181,7 @@
             final Cursor cursor = mProvider.queryRoots(null);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("1_1", cursor.getString(0));
+            assertEquals("1", cursor.getString(0));
             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 */);
@@ -216,6 +196,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
                 .setName("image.jpg")
                 .setDateModified(1422716400000L)
@@ -226,6 +207,7 @@
         assertEquals(1, cursor.getCount());
 
         cursor.moveToNext();
+
         assertEquals("0_1_2", cursor.getString(0));
         assertEquals("image/jpeg", cursor.getString(1));
         assertEquals("image.jpg", cursor.getString(2));
@@ -243,6 +225,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
                 .setName("directory")
                 .setDateModified(1422716400000L)
@@ -293,6 +276,12 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
 
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
+
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 53018cc..7c947f5 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -19,15 +19,14 @@
 import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-@SmallTest
+@MediumTest
 public class PipeManagerTest extends AndroidTestCase {
     private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' };
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 3d92cc2..3833799 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -96,7 +96,7 @@
         if (mRoots.containsKey(deviceId)) {
             return mRoots.get(deviceId);
         } else {
-            throw new IOException("getRoots error");
+            throw new IOException("getRoots error: " + Integer.toString(deviceId));
         }
     }
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index a243375..0fb0f34 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -12,8 +12,20 @@
 
     @Override
     public void onCreate(Bundle arguments) {
+        if (arguments == null) {
+            arguments = new Bundle();
+        }
+        final boolean includeRealDeviceTest =
+                Boolean.parseBoolean(arguments.getString("realDeviceTest", "false"));
+        if (!includeRealDeviceTest) {
+            arguments.putString("notAnnotation", "com.android.mtp.RealDeviceTest");
+        }
         super.onCreate(arguments);
-        addTestListener(this);
+        if (includeRealDeviceTest) {
+            // Show the test result by using activity because we need to disconnect USB cable
+            // from adb host while testing with real MTP device.
+            addTestListener(this);
+        }
     }
 
     @Override
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index c7cf61a..5a6f1d1 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -61,7 +61,7 @@
 
         <activity
             android:name=".ui.PrintActivity"
-            android:configChanges="orientation|screenSize"
+            android:configChanges="screenSize|smallestScreenSize|orientation"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
             android:theme="@style/PrintActivity">
             <intent-filter>
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/packages/PrintSpooler/res/drawable/ic_add.xml
similarity index 67%
copy from core/res/res/drawable/non_client_decor_title.xml
copy to packages/PrintSpooler/res/drawable/ic_add.xml
index e50daea..1442b1b 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/packages/PrintSpooler/res/drawable/ic_add.xml
@@ -14,8 +14,12 @@
      limitations under the License.
 -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_window_focused="true"
-          android:drawable="@drawable/non_client_decor_title_focused" />
-    <item android:drawable="@drawable/non_client_decor_title_unfocused" />
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml
deleted file mode 100644
index 991fa38b..0000000
--- a/packages/PrintSpooler/res/drawable/ic_search.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:autoMirrored="true">
-
-    <item
-        android:state_checked="true">
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item
-        android:state_pressed="true">
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item>
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlNormal">
-        </bitmap>
-    </item>
-
-</selector>
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 6b8aa47..a2f710d 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -69,7 +69,9 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="16dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="?android:attr/textColorPrimary">
+            android:textColor="?android:attr/textColorPrimary"
+            android:singleLine="true"
+            android:ellipsize="end">
         </TextView>
 
         <TextView
@@ -87,7 +89,9 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="16dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="?android:attr/textColorPrimary">
+            android:textColor="?android:attr/textColorPrimary"
+            android:singleLine="true"
+            android:ellipsize="end">
         </TextView>
 
     </LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
new file mode 100644
index 0000000..11fef2d
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
@@ -0,0 +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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?android:attr/textAppearanceMedium"
+      android:textIsSelectable="false"
+      android:textColor="?android:attr/textColorPrimary"
+      android:paddingStart="20dip"
+      android:paddingEnd="8dip"
+      android:minHeight="56dip"
+      android:orientation="horizontal"
+      android:text="@string/destination_default_text"
+      android:gravity="start|center_vertical" />
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
index 8da5769..15cc139 100644
--- a/packages/PrintSpooler/res/menu/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -19,7 +19,7 @@
     <item
         android:id="@+id/action_search"
         android:title="@string/search"
-        android:icon="@*android:drawable/ic_search"
+        android:icon="@*android:drawable/ic_search_api_material"
         android:actionViewClass="android.widget.SearchView"
         android:showAsAction="ifRoom|collapseActionView"
         android:alphabeticShortcut="f"
@@ -29,7 +29,7 @@
     <item
         android:id="@+id/action_add_printer"
         android:title="@string/print_add_printer"
-        android:icon="@*android:drawable/create_contact"
+        android:icon="@drawable/ic_add"
         android:showAsAction="ifRoom"
         android:alphabeticShortcut="a">
     </item>
diff --git a/packages/PrintSpooler/res/values-af/strings.xml b/packages/PrintSpooler/res/values-af/strings.xml
index 0cfa0de..f263af7 100644
--- a/packages/PrintSpooler/res/values-af/strings.xml
+++ b/packages/PrintSpooler/res/values-af/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Tweesydig"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oriëntasie"</string>
     <string name="label_pages" msgid="7768589729282182230">"Bladsye"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Kies \'n drukker"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Al <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Omvang van <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"bv. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Kies drukdiens"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Soek tans vir drukkers"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Geen drukdienste is geaktiveer nie"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Geen drukkers gekry nie"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Druk tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kanselleer tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met drukker nie"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie beskikbaar nie"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Gebruik <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Jou dokument kan dalk deur een of meer bedieners op pad na die drukker gaan."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Swart en wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
diff --git a/packages/PrintSpooler/res/values-am/strings.xml b/packages/PrintSpooler/res/values-am/strings.xml
index 72fc67b..a93e0a9 100644
--- a/packages/PrintSpooler/res/values-am/strings.xml
+++ b/packages/PrintSpooler/res/values-am/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"ባለ ሁለት-ጎን"</string>
     <string name="label_orientation" msgid="2853142581990496477">"አቀማመጠ ገፅ"</string>
     <string name="label_pages" msgid="7768589729282182230">"ገፆች"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"አንድ አታሚ ይምረጡ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ሁሉም <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"የ<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ክልል"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ለምሳሌ፦ 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"የህትመት አገልግሎት ይምረጡ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"አታሚዎችን በመፈለግ ላይ"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ምንም የህትመት አገልግሎቶች አልነቁም"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ምንም አታሚዎች አልተገኙም"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በማተም ላይ"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በመተው ላይ"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ከአታሚ ጋር ምንም ግንኙነት የለም"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"አይታወቅም"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – አይገኝም"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> ይጠቀሙ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ሰነድዎ ወደ አታሚው በሚሄድበት ወቅት በአንድ ወይም ከዚያ በላይ አገልጋዮች ውስጥ ሊያልፍ ይችላል።"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ጥቁር እና ነጭ"</item>
     <item msgid="2762241247228983754">"ቀለም"</item>
diff --git a/packages/PrintSpooler/res/values-ar/strings.xml b/packages/PrintSpooler/res/values-ar/strings.xml
index e28c7bd..c9a6a395 100644
--- a/packages/PrintSpooler/res/values-ar/strings.xml
+++ b/packages/PrintSpooler/res/values-ar/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"الجانبان"</string>
     <string name="label_orientation" msgid="2853142581990496477">"الاتجاه"</string>
     <string name="label_pages" msgid="7768589729282182230">"الصفحات"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"اختر طابعة"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"جميع الصفحات وعددها <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"النطاق <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"على سبيل المثال، 1—5،8،11—13"</string>
@@ -64,6 +65,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"اختر خدمة طباعة"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"البحث عن طابعات"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"لم يتم تمكين أي خدمات طباعة"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"لم يتم العثور على طابعات"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"جارٍ طباعة <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"جارٍ إلغاء <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -82,6 +84,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"لا يوجد اتصال بالطابعة"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"غير معروف"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – غير متاحة"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"هل تريد استخدام <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"من الممكن أن يمر المستند عبر خادم أو أكثر أثناء إرساله إلى الطابعة."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"أبيض وأسود"</item>
     <item msgid="2762241247228983754">"ملونة"</item>
diff --git a/packages/PrintSpooler/res/values-az-rAZ/strings.xml b/packages/PrintSpooler/res/values-az-rAZ/strings.xml
index c102745..5aeb7bb 100644
--- a/packages/PrintSpooler/res/values-az-rAZ/strings.xml
+++ b/packages/PrintSpooler/res/values-az-rAZ/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"İkitərəfli"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oriyentasiya"</string>
     <string name="label_pages" msgid="7768589729282182230">"Səhifələr"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Printer seçin"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Bütün <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> diapazonu"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"məsələn, 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Çap xidmətini seçin"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printer axtarılır"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Heç bir çap xidməti aktiv edilməyib"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Heç bir printer tapılmadı"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> çap edilir"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ləğv edilir"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printerə heç bir bağlantı yoxdur"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"naməlum"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>– əlçatmaz"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> xidmətindən istifadə edilsin?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Sənədiniz printerə qədər bir və ya daha çox server vasitəsilə keçə bilər."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Qara &amp; Ağ"</item>
     <item msgid="2762241247228983754">"Rəng"</item>
diff --git a/packages/PrintSpooler/res/values-bg/strings.xml b/packages/PrintSpooler/res/values-bg/strings.xml
index c88d0d4..93feea5 100644
--- a/packages/PrintSpooler/res/values-bg/strings.xml
+++ b/packages/PrintSpooler/res/values-bg/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Двустранно"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="7768589729282182230">"Страници"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Избиране на принтер"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Всички <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Обхват от <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"напр. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Избиране на услуга за отпечатване"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Търсене на принтери"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Няма активирани услуги за отпечатване"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Няма намерени принтери"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се отпечатва"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се анулира"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Няма връзка с принтера"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"няма данни"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – не е налице"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Да се използва ли „<xliff:g id="SERVICE">%1$s</xliff:g>“?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"По пътя към принтера документът ви може да премине през един или повече сървъри."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-бяло"</item>
     <item msgid="2762241247228983754">"Цветно"</item>
diff --git a/packages/PrintSpooler/res/values-bn-rBD/strings.xml b/packages/PrintSpooler/res/values-bn-rBD/strings.xml
index 45f20a0..0eed9aa 100644
--- a/packages/PrintSpooler/res/values-bn-rBD/strings.xml
+++ b/packages/PrintSpooler/res/values-bn-rBD/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"দ্বিভুজ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"সজ্জা"</string>
     <string name="label_pages" msgid="7768589729282182230">"পৃষ্ঠাগুলি"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"একটি মুদ্রক নির্বাচন করুন"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"সমস্ত <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> এর পরিসর"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"যেমন, ১—৫,৮,১১—১৩"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"মুদ্রণ পরিষেবা চয়ন করুন"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"মুদ্রকগুলি অনুসন্ধান করা হচ্ছে"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"মুদ্রণ পরিষেবা সক্ষম নেই"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"কোনো মুদ্রক পাওয়া যায়নি"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> মুদ্রণ করা হচ্ছে"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> বাতিল করা হচ্ছে"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"মুদ্রকে কোনো সংযোগ নেই"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"অজানা"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – অনুপলব্ধ"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> ব্যবহার করবেন?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"আপনার দস্তাবেজ মুদ্রকে যাওয়ার সময় এক বা একাধিক সার্ভারের মাধ্যমে পাস হতে পারে।"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"কালো এবং সাদা"</item>
     <item msgid="2762241247228983754">"রঙ"</item>
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index 612be97..03d3060 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"De dues cares"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientació"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pàgines"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Tria una impressora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Totes (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Interval de: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Selecció del servei d\'impressió"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Cerca d\'impressores"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No hi ha cap servei d\'impressió activat"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No s\'ha trobat cap impressora"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"S\'està imprimint <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"S\'està cancel·lant <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hi ha connexió amb la impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconegut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Vols fer servir <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"És possible que el document passi com a mínim per un servidor abans d\'imprimir-se."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanc i negre"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index 99ed75d..414abf9 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Oboustranně"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientace"</string>
     <string name="label_pages" msgid="7768589729282182230">"Stránky"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Vyberte tiskárnu"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Vše: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Rozsah: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"např. 1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Zvolte službu tisku"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhledávání tiskáren"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nejsou aktivovány žádné tiskové služby"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nebyly nalezeny žádné tiskárny"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tisk úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Rušení úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nelze se připojit k tiskárně"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznámé"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – není k dispozici"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Použít službu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokument může cestou do tiskárny projít jedním i více servery."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Černobíle"</item>
     <item msgid="2762241247228983754">"Barevně"</item>
diff --git a/packages/PrintSpooler/res/values-da/strings.xml b/packages/PrintSpooler/res/values-da/strings.xml
index 526e976..893c991 100644
--- a/packages/PrintSpooler/res/values-da/strings.xml
+++ b/packages/PrintSpooler/res/values-da/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Tosidet"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sider"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Vælg en printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Alle <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Interval på <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"f.eks. 1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Vælg udskriftstjeneste"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Søger efter printere"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ingen udskrivningstjenester er aktiveret"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Der blev ikke fundet nogen printere"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> udskrives"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annulleres"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse til printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukendt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ikke tilgængelig"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Vil du bruge <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dit dokument passerer muligvis gennem én eller flere servere på vej til printeren."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Sort/hvid"</item>
     <item msgid="2762241247228983754">"Farve"</item>
diff --git a/packages/PrintSpooler/res/values-de/strings.xml b/packages/PrintSpooler/res/values-de/strings.xml
index ef5e34c..f6f53ea 100644
--- a/packages/PrintSpooler/res/values-de/strings.xml
+++ b/packages/PrintSpooler/res/values-de/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Zweiseitig"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ausrichtung"</string>
     <string name="label_pages" msgid="7768589729282182230">"Seiten"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Drucker auswählen"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Alle <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Auswahl von <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"z. B. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Druckdienst auswählen"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Suche nach Druckern"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Keine Druckdienste aktiviert"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Keine Drucker gefunden"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird gedruckt..."</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird abgebrochen..."</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Keine Verbindung zum Drucker"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unbekannt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nicht verfügbar"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> verwenden?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Ihr Dokument passiert bei der Übermittlung an den Drucker möglicherweise einen oder mehrere Server."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Schwarz-weiß"</item>
     <item msgid="2762241247228983754">"Farbe"</item>
diff --git a/packages/PrintSpooler/res/values-el/strings.xml b/packages/PrintSpooler/res/values-el/strings.xml
index a4bb04c..10ddf62 100644
--- a/packages/PrintSpooler/res/values-el/strings.xml
+++ b/packages/PrintSpooler/res/values-el/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Δύο όψεων"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Προσανατολισμός"</string>
     <string name="label_pages" msgid="7768589729282182230">"Σελίδες"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Επιλέξτε εκτυπωτή"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Και οι <xliff:g id="PAGE_COUNT">%1$s</xliff:g> σελίδες"</string>
     <string name="template_page_range" msgid="428638530038286328">"Εύρος σελίδων από <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"π.χ. 1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Επιλέξτε υπηρεσία εκτύπωσης"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Αναζήτηση για εκτυπωτές"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Δεν έχουν ενεργοποιηθεί υπηρεσίες εκτύπωσης"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Δεν βρέθηκαν εκτυπωτές"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Εκτύπωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ακύρωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Δεν υπάρχει σύνδεση με εκτυπωτή"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"άγνωστο"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – μη διαθέσιμο"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Να χρησιμοποιηθεί η υπηρεσία <xliff:g id="SERVICE">%1$s</xliff:g>;"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Το έγγραφό σας μπορεί να περάσει από έναν ή περισσότερους διακομιστές κατά τη μετάβαση στον εκτυπωτή."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Ασπρόμαυρο"</item>
     <item msgid="2762241247228983754">"Χρώμα"</item>
diff --git a/packages/PrintSpooler/res/values-en-rAU/strings.xml b/packages/PrintSpooler/res/values-en-rAU/strings.xml
index 9f0d7e5..a540ac5 100644
--- a/packages/PrintSpooler/res/values-en-rAU/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rAU/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Use <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
diff --git a/packages/PrintSpooler/res/values-en-rGB/strings.xml b/packages/PrintSpooler/res/values-en-rGB/strings.xml
index 9f0d7e5..a540ac5 100644
--- a/packages/PrintSpooler/res/values-en-rGB/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rGB/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Use <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
diff --git a/packages/PrintSpooler/res/values-en-rIN/strings.xml b/packages/PrintSpooler/res/values-en-rIN/strings.xml
index 9f0d7e5..a540ac5 100644
--- a/packages/PrintSpooler/res/values-en-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No print services enabled"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Use <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
diff --git a/packages/PrintSpooler/res/values-es-rUS/strings.xml b/packages/PrintSpooler/res/values-es-rUS/strings.xml
index ef0dbb1..8929aa8 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Ambos lados"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páginas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Seleccionar una impresora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Todas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Rango de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"Ej.: 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Elegir servicio de impresión"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No hay servicios de impresión habilitados"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"¿Deseas usar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Es posible que el documento pase por uno o varios servidores antes de imprimirse."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 067c134..7cfd92a 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dos caras"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páginas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Elige una impresora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Todas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalo de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"p. ej.: 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Seleccionar servicio de impresión"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"No hay servicios de impresión habilitados"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – no disponible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"¿Usar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Es posible que el documento pase por uno o varios servidores antes de imprimirse."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-et-rEE/strings.xml b/packages/PrintSpooler/res/values-et-rEE/strings.xml
index 0227131..ee93bcf 100644
--- a/packages/PrintSpooler/res/values-et-rEE/strings.xml
+++ b/packages/PrintSpooler/res/values-et-rEE/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Kahepoolne"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suund"</string>
     <string name="label_pages" msgid="7768589729282182230">"Lehed"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Printeri valimine"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Kõik <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Vahemik <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"nt 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Prinditeenuse valimine"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printerite otsimine"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ühtegi printimisteenust pole lubatud"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Printereid ei leitud"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> printimine"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> tühistamine"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printeriühendus puudub"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"teadmata"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – pole saadaval"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Kas soovite kasutada teenust <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Printerini jõudmiseks võib dokument läbida ühe või mitu serverit."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustvalge"</item>
     <item msgid="2762241247228983754">"Värv"</item>
diff --git a/packages/PrintSpooler/res/values-eu-rES/strings.xml b/packages/PrintSpooler/res/values-eu-rES/strings.xml
index 461a92f..882e888 100644
--- a/packages/PrintSpooler/res/values-eu-rES/strings.xml
+++ b/packages/PrintSpooler/res/values-eu-rES/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Bi aldekoa"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientazioa"</string>
     <string name="label_pages" msgid="7768589729282182230">"Orriak"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Hautatu inprimagailua"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> orriak"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> orriko tartea"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"adib., 1-5, 8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Aukeratu inprimatze-zerbitzua"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Inprimagailuak bilatzen"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ez dago gaituta inprimatzeko zerbitzurik"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Ez da inprimagailurik aurkitu"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> inprimatzen"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> bertan behera uzten"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Inprimagailua ez dago konektatuta"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ezezaguna"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: ez dago erabilgarri"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> erabili nahi duzu?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Baliteke dokumentuak zerbitzari batean edo gehiagotan zehar igarotzea inprimagailurako bidean."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Zuri-beltza"</item>
     <item msgid="2762241247228983754">"Koloretakoa"</item>
diff --git a/packages/PrintSpooler/res/values-fa/strings.xml b/packages/PrintSpooler/res/values-fa/strings.xml
index 0e1629f..10743e7 100644
--- a/packages/PrintSpooler/res/values-fa/strings.xml
+++ b/packages/PrintSpooler/res/values-fa/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"دوطرفه"</string>
     <string name="label_orientation" msgid="2853142581990496477">"جهت"</string>
     <string name="label_pages" msgid="7768589729282182230">"صفحه‌ها"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"چاپگری انتخاب کنید"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"همه <xliff:g id="PAGE_COUNT">%1$s</xliff:g> صفحه"</string>
     <string name="template_page_range" msgid="428638530038286328">"محدوده <xliff:g id="PAGE_COUNT">%1$s</xliff:g> صفحه"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"‏‏‎مثلاً ۱—۵،‏۹،۷—۱۰"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"انتخاب سرویس چاپ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"درحال جستجوی چاپگرها"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"هیچ خدمات چاپی فعال نیست"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"هیچ چاپگری یافت نشد"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"در حال چاپ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"در حال لغو <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"اتصال با چاپگر برقرار نیست"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"نامعلوم"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - در دسترس نیست"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"از <xliff:g id="SERVICE">%1$s</xliff:g> استفاده شود؟"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ممکن است سندتان برای رسیدن به چاپگر از یک یا چند سرور عبور کند."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"سیاه و سفید"</item>
     <item msgid="2762241247228983754">"رنگی"</item>
diff --git a/packages/PrintSpooler/res/values-fi/strings.xml b/packages/PrintSpooler/res/values-fi/strings.xml
index faf40e2..ee35c41 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Kaksipuolinen"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suunta"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sivut"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Valitse tulostin"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Kaikki <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Sivumäärä: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"esim. 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Valitse tulostuspalvelu"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Etsitään tulostimia"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ei käytössä olevia tulostuspalveluita"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Tulostimia ei löydy"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tulostetaan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Peruutetaan työ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ei yhteyttä tulostimeen"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tuntematon"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ei käytettävissä"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Käytetäänkö palvelua <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Asiakirja saattaa kulkea yhden tai useamman palvelimen kautta matkalla tulostimeen."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustavalkoinen"</item>
     <item msgid="2762241247228983754">"Väri"</item>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index f949bb7..eb99441 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Recto verso"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Sélectionnez une imprimante"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Toutes (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Plage de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours..."</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Aucun service d\'impression activé"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Impression de <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> en cours…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de « <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> »…"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnu"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — indisponible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Utiliser <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Votre document peut passer par un ou plusieurs serveurs avant d\'arriver à l\'imprimante."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 1f5c716..c0eecfb 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Recto verso"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pages"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Sélect. imprimante"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Toutes (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Plage de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ex. : 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Aucun service d\'impression activé"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Impression de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnue"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Utiliser <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Votre document peut passer par un ou plusieurs serveurs avant d\'être envoyé sur l\'imprimante."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
diff --git a/packages/PrintSpooler/res/values-gl-rES/strings.xml b/packages/PrintSpooler/res/values-gl-rES/strings.xml
index 0d4b040..b4a1ec6 100644
--- a/packages/PrintSpooler/res/values-gl-rES/strings.xml
+++ b/packages/PrintSpooler/res/values-gl-rES/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dual"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páxinas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Escoller impresora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"As <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalo de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ex.: 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Escoller servizo de impresión"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Busca de impresoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Non hai servizos de impresión activados"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Non se atopou ningunha impresora"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Non hai conexión coa impresora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"descoñecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: non dispoñible"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Queres usar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"É posible que o teu documento pase por un ou máis servidores antes de imprimirse."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Branco e negro"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-gu-rIN/strings.xml b/packages/PrintSpooler/res/values-gu-rIN/strings.xml
index 28ffc2a..8f77953 100644
--- a/packages/PrintSpooler/res/values-gu-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-gu-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"દ્વિભુજ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ઓરિએન્ટેશન"</string>
     <string name="label_pages" msgid="7768589729282182230">"પૃષ્ઠો"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"પ્રિન્ટર પસંદ કરો"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"તમામ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ની શ્રેણી"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"દા.ત. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"પ્રિન્ટ સેવા પસંદ કરો"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"પ્રિન્ટર્સ માટે શોધી રહ્યું છે"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"કોઈ છાપ સેવાઓ સક્ષમ કરેલ નથી"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"કોઈ પ્રિન્ટર મળ્યા નથી"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> છાપી રહ્યાં છે"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ને રદ કરી રહ્યું છે"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"પ્રિન્ટર માટે કોઈ કનેક્શન નથી"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"અજાણ્યું"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – અનુપલબ્ધ"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> નો ઉપયોગ કરીએ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"તમારો દસ્તાવેજ પ્રિન્ટર સુધીના તેના માર્ગમાં એક અથવા વધુ સર્વર્સથી પસાર થઈ શકે છે."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"શ્યામ અને શ્વેત"</item>
     <item msgid="2762241247228983754">"રંગ"</item>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 1a0f7d1..4c11323 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"दो-तरफ़ा"</string>
     <string name="label_orientation" msgid="2853142581990496477">"अभिविन्‍यास"</string>
     <string name="label_pages" msgid="7768589729282182230">"पृष्ठ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"कोई प्रिंटर चुनें"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"सभी <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"पृष्ठ संख्या <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"उदा. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"प्रिंट सेवा चुनें"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर खोज रहा है"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"कोई भी प्रिंट सेवा सक्षम नहीं है"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"कोई प्रिंटर नहीं मिले"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> प्रिंट हो रहा है"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द हो रहा है"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिंटर के लिए कोई कनेक्शन नहीं"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – अनुपलब्ध"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> का उपयोग करें?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"प्रिंटर पर जाते समय आपका दस्तावेज़ एक या अधिक सर्वर से गुज़र सकता है."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"श्याम और श्वेत"</item>
     <item msgid="2762241247228983754">"रंग"</item>
diff --git a/packages/PrintSpooler/res/values-hr/strings.xml b/packages/PrintSpooler/res/values-hr/strings.xml
index 3e38d65..4cec3ba 100644
--- a/packages/PrintSpooler/res/values-hr/strings.xml
+++ b/packages/PrintSpooler/res/values-hr/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Obostrano"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orijentacija"</string>
     <string name="label_pages" msgid="7768589729282182230">"Stranice"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Odaberite pisač"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Sve stranice (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Raspon od <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"npr. 1 – 5,8,11 – 13"</string>
@@ -61,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Odaberite uslugu ispisa"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Traženje pisača"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nije omogućena nijedna usluga ispisa"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nije pronađen nijedan pisač"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Ispisivanje <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Otkazivanje zadatka <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -76,6 +78,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nema veze s pisačem"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nepoznato"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – zadatak nije dostupan"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Želite li upotrijebiti uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Na putu do pisača vaš dokument može proći kroz jedan ili više poslužitelja."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Crno-bijelo"</item>
     <item msgid="2762241247228983754">"U boji"</item>
diff --git a/packages/PrintSpooler/res/values-hu/strings.xml b/packages/PrintSpooler/res/values-hu/strings.xml
index 2b24b1f..ac1ba6e 100644
--- a/packages/PrintSpooler/res/values-hu/strings.xml
+++ b/packages/PrintSpooler/res/values-hu/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Kétoldalas"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Tájolás"</string>
     <string name="label_pages" msgid="7768589729282182230">"Oldalak"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Válasszon ki egy nyomtatót"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Összes (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> oldalas tartomány"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"pl. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Nyomtatási szolgáltatás kiválasztása"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Nyomtatók keresése"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nincs engedélyezett nyomtatási szolgáltatás"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nem található nyomtató"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> nyomtatása"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> törlése"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nincs kapcsolat a nyomtatóval"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ismeretlen"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nem érhető el"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Használni szeretné a következő szolgáltatást: <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"A dokumentum áthaladhat egy vagy több szerveren, mielőtt a nyomtatóhoz érne."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Fekete-fehér"</item>
     <item msgid="2762241247228983754">"Szín"</item>
diff --git a/packages/PrintSpooler/res/values-hy-rAM/strings.xml b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
index 56f94b4..dda6745 100644
--- a/packages/PrintSpooler/res/values-hy-rAM/strings.xml
+++ b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Երկկողմանի"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Դիրքավորում"</string>
     <string name="label_pages" msgid="7768589729282182230">"Էջեր"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Ընտրել տպիչ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Բոլորը՝ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Միջակայքը՝ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"օր.՝ 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Ընտրեք տպելու ծառայությունը"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Տպիչների որոնում"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ակտիվացված տպման ծառայություններ չկան"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Տպիչներ չեն գտնվել"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Տպվում է՝ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ը չեղարկվում է"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Տպիչի հետ կապ չկա"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"անհայտ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> տպիչն անհասանելի է"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Օգտագործե՞լ <xliff:g id="SERVICE">%1$s</xliff:g>-ը:"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Հնարավոր է՝ փաստաթուղթը մի քանի սերվերներով անցնի մինչ տպվելը:"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Սև ու սպիտակ"</item>
     <item msgid="2762241247228983754">"Գույնը"</item>
diff --git a/packages/PrintSpooler/res/values-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index d09fcfe..b203e2b 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Bersisi ganda"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="7768589729282182230">"Halaman"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Pilih printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Semua dari <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Rentang dari <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"misalnya 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pilih layanan cetak"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari printer"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Tidak ada layanan cetak yang aktif"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Tidak ditemukan printer"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tidak ada sambungan ke printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Gunakan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokumen Anda dapat melewati satu atau beberapa server saat menuju printer."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
diff --git a/packages/PrintSpooler/res/values-is-rIS/strings.xml b/packages/PrintSpooler/res/values-is-rIS/strings.xml
index bf13ebf..6dfdabc 100644
--- a/packages/PrintSpooler/res/values-is-rIS/strings.xml
+++ b/packages/PrintSpooler/res/values-is-rIS/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Tvíhliða"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Stefna"</string>
     <string name="label_pages" msgid="7768589729282182230">"Síður"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Veldu prentara"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Allar <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"t.d. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Veldu prentþjónustu"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Leitar að prentara"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Engin prentþjónusta er virk"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Engir prentarar fundust"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Prentar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Hættir við <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Engin tenging við prentara"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"óþekkt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ekki í boði"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Nota <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Skjalið gæti þurft að fara í gegnum einn eða fleiri þjóna á leið sinni til prentarans."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svarthvítt"</item>
     <item msgid="2762241247228983754">"Í lit"</item>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index b052341..fd5473a 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Con doppia funzione"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientamento"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pagine"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Seleziona stampante"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Tutte e <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervallo di <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"Es.: 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Scegli servizio di stampa"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Ricerca di stampanti"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Non è stato attivato alcun servizio di stampa"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nessuna stampante trovata"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Stampa di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annullamento di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nessun collegamento alla stampante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"sconosciuto"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - non disponibile"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Utilizzare <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Il tuo documento potrebbe passare da uno o più server per raggiungere la stampante."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Bianco e nero"</item>
     <item msgid="2762241247228983754">"A colori"</item>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index bb1967d..dd062a3 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"דו-צדדי"</string>
     <string name="label_orientation" msgid="2853142581990496477">"כיוון"</string>
     <string name="label_pages" msgid="7768589729282182230">"עמודים"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"בחר מדפסת"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"הכל <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"טווח של <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"למשל 1–5‏,8,‏11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"בחר שירות הדפסה"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"מחפש מדפסות"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"לא הופעלו שירותי הדפסה"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"לא נמצאו מדפסות"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"מדפיס את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"מבטל את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"אין חיבור למדפסת"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"לא ידוע"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – לא זמינה"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"האם להשתמש ב-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ייתכן שהמסמך שלך יעבור בשרת אחד או יותר בדרכו למדפסת."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"שחור ולבן"</item>
     <item msgid="2762241247228983754">"צבע"</item>
diff --git a/packages/PrintSpooler/res/values-ja/strings.xml b/packages/PrintSpooler/res/values-ja/strings.xml
index 8ef7517..23e4809 100644
--- a/packages/PrintSpooler/res/values-ja/strings.xml
+++ b/packages/PrintSpooler/res/values-ja/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"両面"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="7768589729282182230">"ページ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"プリンタを選択"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>ページすべて"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>ページ分"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例: 1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"印刷サービスの選択"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"プリンタの検索中"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"使用できる印刷サービスがありません"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"プリンタが見つかりません"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>を印刷しています"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>をキャンセルしています"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"プリンタに接続されていません"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>–使用不可"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>を利用しますか?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ドキュメントは1つ以上のサーバーを経由してプリンタに送信されることがあります。"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"モノクロ"</item>
     <item msgid="2762241247228983754">"カラー"</item>
diff --git a/packages/PrintSpooler/res/values-ka-rGE/strings.xml b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
index b65d013..9f86f05 100644
--- a/packages/PrintSpooler/res/values-ka-rGE/strings.xml
+++ b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"ორმხრივი"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ორიენტაცია"</string>
     <string name="label_pages" msgid="7768589729282182230">"გვერდები"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"პრინტერის არჩევა"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ყველა <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>-ის არეალი"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"მაგ. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"აირჩიეთ ბეჭდვის სერვისი"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"მიმდინარეობს პრინტერების ძიება"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ბეჭდვის სერვისები გააქტიურებული არ არის"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"პრინტერები ვერ მოიძებნა"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"იბეჭდება <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"მიმდინარეობს <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ის გაუქმება"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"პრინტერთან კავშირი არ არის"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"უცნობი"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – მიუწვდომელია"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"გსურთ, გამოიყენოთ <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"პრინტერამდე გზად დოკუმენტმა შეიძლება ერთი ან მეტი სერვერი გაიაროს."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"შავ-თეთრი"</item>
     <item msgid="2762241247228983754">"ფერი"</item>
diff --git a/packages/PrintSpooler/res/values-kk-rKZ/strings.xml b/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
index ef914f0c..05c300e 100644
--- a/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
+++ b/packages/PrintSpooler/res/values-kk-rKZ/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Екі жақты"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Бағыты"</string>
     <string name="label_pages" msgid="7768589729282182230">"Беттер"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Принтерді таңдау"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Барлық <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ауқымы"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"мысалы, 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Принтер қызметін таңдау"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Принтерлерді іздеу"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Басып шығару қызметтері қосылмаған"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Ешқандай принтер табылмады"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> басып шығарылуда"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> жұмысын тоқтатуда"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Принтермен байланыс жоқ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"белгісіз"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – қол жетімсіз"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> қолданылсын ба?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Құжат принтерге жеткенше бір немесе бірнеше серверден өтуі мүмкін."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Қара &amp; Ақ"</item>
     <item msgid="2762241247228983754">"Түс"</item>
diff --git a/packages/PrintSpooler/res/values-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index 3aac97f..0861e59 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"សងខាង"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ទិស"</string>
     <string name="label_pages" msgid="7768589729282182230">"ទំព័រ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ជ្រើសម៉ាស៊ីនបោះពុម្ព"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ទាំងអស់"</string>
     <string name="template_page_range" msgid="428638530038286328">"ជួរ​នៃ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ឧ. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ជ្រើស​សេវា​បោះពុម្ព"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ស្វែងរក​ម៉ាស៊ីន​បោះពុម្ព"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"គ្មានការបើកដំណើរការសេវាបោះពុម្ពទេ"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"រក​មិន​ឃើញ​ម៉ាស៊ីន​បោះពុម្ព"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"កំពុង​​បោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"ការ​បោះបង់ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មាន​​​ការ​ភ្ជាប់​ទៅ​ម៉ាស៊ីន​បោះពុម្ព​"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"មិន​ស្គាល់"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – មិន​អាច​ប្រើ​បាន"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"ប្រើ <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ឯកសាររបស់អ្នកអាចនឹងឆ្លងកាត់ម៉ាស៊ីនមេមួយ ឬច្រើននៅពេលដែលវាធ្វើដំណើរទៅកាន់ម៉ាស៊ីនបោះពុម្ព។"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ស &amp; ខ្មៅ"</item>
     <item msgid="2762241247228983754">"ពណ៌"</item>
diff --git a/packages/PrintSpooler/res/values-kn-rIN/strings.xml b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
index 72e0bac..71b098d 100644
--- a/packages/PrintSpooler/res/values-kn-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-kn-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"ಎರಡು ಬದಿ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ಓರಿಯಂಟೇಶನ್"</string>
     <string name="label_pages" msgid="7768589729282182230">"ಪುಟಗಳು"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ಪ್ರಿಂಟರ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ಎಲ್ಲಾ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ನ ಶ್ರೇಣಿ"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ಉದಾ. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ಮುದ್ರಣ ಸೇವೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ಪ್ರಿಂಟರ್‌‌ಗಳಿಗಾಗಿ ಹುಡುಕಲಾಗುತ್ತಿದೆ"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ಯಾವುದೇ ಮುದ್ರಣ ಸೇವೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿಲ್ಲ"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ಯಾವುದೇ ಮುದ್ರಕಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ಮುದ್ರಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ರದ್ದು ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ಮುದ್ರಕಕ್ಕೆ ಸಂಪರ್ಕವಿಲ್ಲ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ಅಜ್ಞಾತ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ಲಭ್ಯವಿಲ್ಲ"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> ಬಳಸುವುದೇ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ನಿಮ್ಮ ಡಾಕ್ಯುಮೆಂಟ್‌ ಪ್ರಿಂಟರ್‌ಗೆ ಹೋಗುವ ಸಂದರ್ಭದಲ್ಲಿ ಒಂದು ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚು ಸರ್ವರ್‌ಗಳ ಮೂಲಕ ಹಾದು ಹೋಗಬಹುದು."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ಕಪ್ಪು &amp; ಬಿಳುಪು"</item>
     <item msgid="2762241247228983754">"ಬಣ್ಣ"</item>
diff --git a/packages/PrintSpooler/res/values-ko/strings.xml b/packages/PrintSpooler/res/values-ko/strings.xml
index 0d9a4dd..451ab58 100644
--- a/packages/PrintSpooler/res/values-ko/strings.xml
+++ b/packages/PrintSpooler/res/values-ko/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"양면"</string>
     <string name="label_orientation" msgid="2853142581990496477">"방향"</string>
     <string name="label_pages" msgid="7768589729282182230">"페이지"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"프린터 선택"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>페이지 모두"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>페이지 범위"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"예: 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"인쇄 서비스 선택"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"프린터 검색 중"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"사용 가능한 프린트 서비스 없음"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"프린터 없음"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 인쇄 중"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 취소 중"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"프린터와 연결되지 않음"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"알 수 없음"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 사용할 수 없음"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>을(를) 사용할까요?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"문서가 프린터로 전송되는 중에 하나 이상의 서버를 통과할 수 있습니다."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"흑백"</item>
     <item msgid="2762241247228983754">"컬러"</item>
diff --git a/packages/PrintSpooler/res/values-ky-rKG/strings.xml b/packages/PrintSpooler/res/values-ky-rKG/strings.xml
index 7f6eb10..98da08c 100644
--- a/packages/PrintSpooler/res/values-ky-rKG/strings.xml
+++ b/packages/PrintSpooler/res/values-ky-rKG/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Эки тараптуу"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Багыттоо"</string>
     <string name="label_pages" msgid="7768589729282182230">"Баракчалар"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Принтер тандаңыз"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Бардыгы <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> аралыгы"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"мис. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Принтер кызматын тандоо"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Принтерлер изделүүдө"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Принтер-кызматтары иштетилген эмес"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Принтерлер табылган жок"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> басылууда"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> токтотулууда"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Принтер менен байланыш жок"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"белгисиз"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – жеткиликтүү эмес"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> колдонулсунбу?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Принтерге жеткиче документиңиз бир же андан көп серверлерден өтүшү мүмкүн."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Кара-ак"</item>
     <item msgid="2762241247228983754">"Түстүү"</item>
diff --git a/packages/PrintSpooler/res/values-lo-rLA/strings.xml b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
index 7e65b72..2029fdf 100644
--- a/packages/PrintSpooler/res/values-lo-rLA/strings.xml
+++ b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"ສອງ​ດ້ານ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ລວງ"</string>
     <string name="label_pages" msgid="7768589729282182230">"ໜ້າ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ເລືອກເຄື່ອງພິມ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ທັງ​ໝົດ <xliff:g id="PAGE_COUNT">%1$s</xliff:g> ໜ້າ"</string>
     <string name="template_page_range" msgid="428638530038286328">"ໄລ​ຍະ <xliff:g id="PAGE_COUNT">%1$s</xliff:g> ໜ້າ"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ຕົວຢ່າງ: 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ເລືອກບໍລິການການພິມ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ກຳລັງຊອກຫາເຄື່ອງພິມ"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ບໍ່​ມີ​ການ​ບໍ​ລິ​ການ​ພິມ​ເປີດ​ໃຊ້​ງານ​ໄວ້"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ບໍ່ພົບເຄື່ອງພິມ"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"ກຳລັງພິມ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"ກຳລັງຍົກເລີກ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ບໍ່ມີການເຊື່ອມຕໍ່ຫາເຄື່ອງພິມ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - ບໍ່ມີຢູ່"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"ໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ເອກະສານຂອງທ່ານອາດເດີນທາງຜ່ານໜຶ່ງ ຫຼື ຫຼາຍເຊີບເວີ ເພື່ອໄປຮອດເຄື່ອງພິມ."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ຂາວດຳ"</item>
     <item msgid="2762241247228983754">"ສີ"</item>
diff --git a/packages/PrintSpooler/res/values-lt/strings.xml b/packages/PrintSpooler/res/values-lt/strings.xml
index a3129cc..972abb5 100644
--- a/packages/PrintSpooler/res/values-lt/strings.xml
+++ b/packages/PrintSpooler/res/values-lt/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dvipusis"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacija"</string>
     <string name="label_pages" msgid="7768589729282182230">"Puslapiai"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Spausdint. pasirink."</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Visi <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Diapazonas: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"pvz., 1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pasirinkite spausdinimo paslaugą"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Ieškoma spausdintuvų"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Neįgalinta jokių spausdinimo paslaugų"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nerasta spausdintuvų"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Spausdinama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Atšaukiama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nėra ryšio su spausdintuvu"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nežinoma"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ – nepasiekiama"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Naudoti „<xliff:g id="SERVICE">%1$s</xliff:g>“?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Kai dokumentas siunčiamas į spausdintuvą, jis gali būti perduodamas per vieną ar daugiau serverių."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nespalvotas"</item>
     <item msgid="2762241247228983754">"Spalva"</item>
diff --git a/packages/PrintSpooler/res/values-lv/strings.xml b/packages/PrintSpooler/res/values-lv/strings.xml
index daadf99..f565b23 100644
--- a/packages/PrintSpooler/res/values-lv/strings.xml
+++ b/packages/PrintSpooler/res/values-lv/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Divpusējs"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Virziens"</string>
     <string name="label_pages" msgid="7768589729282182230">"Lapas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Atlasīt printeri"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Visas <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Diapazons: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"piem., 1–5,8,11–13"</string>
@@ -61,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Izvēlieties drukāšanas pakalpojumu"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printeru meklēšana"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nav iespējots neviens drukas pakalpojums"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Netika atrasts neviens printeris."</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Notiek darba <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> drukāšana…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Pārtrauc drukas darbu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
@@ -76,6 +78,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nav savienojuma ar printeri"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nezināms"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — nav pieejams"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Vai izmantot pakalpojumu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokuments, iespējams, tiek pārsūtīts caur vienu vai vairākiem serveriem, līdz tas nonāk līdz printerim."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Melnbalts"</item>
     <item msgid="2762241247228983754">"Krāsa"</item>
diff --git a/packages/PrintSpooler/res/values-mk-rMK/strings.xml b/packages/PrintSpooler/res/values-mk-rMK/strings.xml
index 3ebc1c9..f5c06d1 100644
--- a/packages/PrintSpooler/res/values-mk-rMK/strings.xml
+++ b/packages/PrintSpooler/res/values-mk-rMK/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Двостран"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентација"</string>
     <string name="label_pages" msgid="7768589729282182230">"Страници"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Избери печатач"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Сите <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Опсег од <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"на пр.: 1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Избери услуга печатење"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Пребарување печатачи"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Нема овозможени услуги за печатење"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Не се пронајдени печатачи"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> се печати"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> се откажува"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нема поврзување со печатач"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"непознато"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - недостапен"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Користи <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"На пат до печатачот, документот може да помине преку еден или повеќе сервери."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Црно-бела"</item>
     <item msgid="2762241247228983754">"Во боја"</item>
diff --git a/packages/PrintSpooler/res/values-ml-rIN/strings.xml b/packages/PrintSpooler/res/values-ml-rIN/strings.xml
index 63b4060..2d45ce5 100644
--- a/packages/PrintSpooler/res/values-ml-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-ml-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"രണ്ട് വശങ്ങളുള്ളത്"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ഓറിയന്‍റേഷന്‍‌"</string>
     <string name="label_pages" msgid="7768589729282182230">"പേജുകൾ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ഒരു പ്രിന്റർ തിരഞ്ഞെടുക്കുക"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"എല്ലാ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> എന്നതിന്റെ പരിധി"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ഉദാ. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"പ്രിന്റ് സേവനം തിരഞ്ഞെടുക്കുക"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"പ്രിന്ററുകൾക്കായി തിരയുന്നു"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"പ്രിന്റ് സേവനങ്ങളൊന്നും പ്രവർത്തനക്ഷമാക്കിയിട്ടില്ല"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"പ്രിന്ററുകളൊന്നും കണ്ടെത്തിയില്ല"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> പ്രിന്റുചെയ്യുന്നു"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> റദ്ദാക്കുന്നു"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"പ്രിന്ററിൽ കണക്ഷനൊന്നുമില്ല"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"അജ്ഞാതം"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ലഭ്യമല്ല"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"നിങ്ങളുടെ പ്രമാണം പ്രിന്ററിലേക്ക് പോകുന്നതിനിടെ അത് ഒന്നോ അതിലധികമോ സെർവറുകളിലൂടെ കടന്നുപോകാനിടയുണ്ട്."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"കറുപ്പ് &amp; വെള്ള"</item>
     <item msgid="2762241247228983754">"നിറം"</item>
diff --git a/packages/PrintSpooler/res/values-mn-rMN/strings.xml b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
index a8628c9..f2c7b73 100644
--- a/packages/PrintSpooler/res/values-mn-rMN/strings.xml
+++ b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Хоёр талт"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Чиглэл"</string>
     <string name="label_pages" msgid="7768589729282182230">"Хуудас"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Хэвлэгчийг сонгоно уу"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Нийт <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Хүрээ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ж.нь. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Хэвлэх үйлчилгээг сонгох"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Принтер хайж байна"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Хэвлэх үйлчилгээг идэвхжүүлээгүй"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Принтер олдсонгүй"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Хэвлэж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Цуцлаж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Принтер холбогдоогүй байна"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"тодорхойгүй"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ашиглах боломжгүй"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>-г ашиглах уу?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Таны документ хэвлэгчид иртэл нэг эсвэл хэд хэдэн серверээр дамжина."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Хар &amp; Цагаан"</item>
     <item msgid="2762241247228983754">"Өнгө"</item>
diff --git a/packages/PrintSpooler/res/values-mr-rIN/strings.xml b/packages/PrintSpooler/res/values-mr-rIN/strings.xml
index c08e440..1c079dc 100644
--- a/packages/PrintSpooler/res/values-mr-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-mr-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"दोन्ही बाजूंनी"</string>
     <string name="label_orientation" msgid="2853142581990496477">"अभिमुखता"</string>
     <string name="label_pages" msgid="7768589729282182230">"पृष्ठे"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"प्रिंटर निवडा"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"सर्व <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ची श्रेणी"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"उदा. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"मुद्रण सेवा निवडा"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर शोधत आहे"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"कोणत्याही मुद्रण सेवा सक्षम केलेल्या नाहीत"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"कोणतेही प्रिंटर आढळले नाही"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करीत आहे"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करीत आहे"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिंटरवर कोणतेही कनेक्‍शन नाही"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – अनुपलब्‍ध"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> वापरायची?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"आपला दस्तऐवज प्रिंटरपर्यंत पोहचण्‍यापूर्वी एक किंवा अधिक सर्व्हरद्वारे जाऊ शकतो."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"कृष्‍ण धवल"</item>
     <item msgid="2762241247228983754">"रंग"</item>
diff --git a/packages/PrintSpooler/res/values-ms-rMY/strings.xml b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
index 4ed1019..d6b5ea7 100644
--- a/packages/PrintSpooler/res/values-ms-rMY/strings.xml
+++ b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dua sisi"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="7768589729282182230">"Halaman"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Pilih pencetak"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Semua <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Julat <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"cth. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pilih perkhidmatan cetak"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari pencetak"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Perkhidmatan cetak tidak didayakan"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Tiada pencetak ditemui"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tiada sambungan ke pencetak"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tidak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Gunakan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokumen anda mungkin melalui satu atau beberapa pelayan dalam perjalanan ke pencetak."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
diff --git a/packages/PrintSpooler/res/values-my-rMM/strings.xml b/packages/PrintSpooler/res/values-my-rMM/strings.xml
index 0a65a11..c3dc490 100644
--- a/packages/PrintSpooler/res/values-my-rMM/strings.xml
+++ b/packages/PrintSpooler/res/values-my-rMM/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"နှစ်ဖက်လှ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"အနေအထား"</string>
     <string name="label_pages" msgid="7768589729282182230">"စာမျက်နှာများ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ပုံနှိပ်စက်ကို ရွေးပါ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"အားလုံး <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>ဘောင် ထဲမှာ"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ဥပမာ ၁-၅၊ ၈၊ ၁၁-၁၃"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"စာထုတ်ရန် ဝန်ဆောင်မှုကို ရွေးချယ်ပါ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"စာထုတ်စက်များကို ရှာနေပါသည်"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ပုံနှိပ်ထုတ်ယူရေး ဝန်ဆောင်မှုများ ဖွင့်မထားပါ"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"စာထုတ်စက် တစ်ခုမှ မတွေ့ရှိပါ"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ကို စာထုတ်နေပါသည်"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ကို ပယ်ဖျက်နေပါသည်"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"စာထုတ်စက်နဲ့ ဆက်သွယ်ထားမှု မရှိပါ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"အကြောင်းအရာ မသိရှိ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – မတွေ့ရှိပါ"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ကိုသုံးမလား။"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"သင်၏ စာရွက်စာတမ်းများသည် ပရင်တာထံသို့ သွားစဉ် ဆာဗာ တစ်ခု သို့မဟုတ် ပိုများပြီး ဖြတ်ကျော်နိုင်ရသည်။"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"အဖြူ အမည်း"</item>
     <item msgid="2762241247228983754">"ရောင်စုံ"</item>
diff --git a/packages/PrintSpooler/res/values-nb/strings.xml b/packages/PrintSpooler/res/values-nb/strings.xml
index 7c74b39..945bbea 100644
--- a/packages/PrintSpooler/res/values-nb/strings.xml
+++ b/packages/PrintSpooler/res/values-nb/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Tosidig"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sider"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Velg en skriver"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Alle <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Område på <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"f.eks. 1–5, 8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Velg utskriftstjeneste"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Søker etter skrivere"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ingen utskriftstjenester er slått på"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Fant ingen skrivere"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse med skriveren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukjent"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – utilgjengelig"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Vil du bruke <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokumentet ditt kan gå via flere tjenere før det når skriveren."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svart og hvitt"</item>
     <item msgid="2762241247228983754">"Farge"</item>
diff --git a/packages/PrintSpooler/res/values-ne-rNP/strings.xml b/packages/PrintSpooler/res/values-ne-rNP/strings.xml
index 42cd98b..34c1dd4f 100644
--- a/packages/PrintSpooler/res/values-ne-rNP/strings.xml
+++ b/packages/PrintSpooler/res/values-ne-rNP/strings.xml
@@ -27,6 +27,8 @@
     <string name="label_duplex" msgid="5370037254347072243">"दुई-पक्षीय"</string>
     <string name="label_orientation" msgid="2853142581990496477">"अभिमुखिकरण"</string>
     <string name="label_pages" msgid="7768589729282182230">"पृष्ठहरू"</string>
+    <!-- no translation found for destination_default_text (5422708056807065710) -->
+    <skip />
     <string name="template_all_pages" msgid="3322235982020148762">"सबै <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> को सीमा"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"उदाहरण १-५,८,११-१३"</string>
@@ -60,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"प्रिन्ट सेवा छनौट गर्नुहोस्"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिन्टरहरू खोज्दै"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"कुनै पनि मुद्रण सेवाहरू सक्रिय छैनन्"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"कुनै प्रिन्टरहरू भेटाइएन"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"प्रिन्ट गरिँदै <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"रद्द गरिँदै <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +77,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिन्टरमा कुनै जडान छैन"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - अनुपलब्ध"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"तपाईँको कागजात प्रिन्टरमा जाँदा यसको मार्गमा एक वा धेरै सर्भरहरू पार हुनसक्छन्।"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"कालो &amp; सेतो"</item>
     <item msgid="2762241247228983754">"रङ्ग"</item>
diff --git a/packages/PrintSpooler/res/values-nl/strings.xml b/packages/PrintSpooler/res/values-nl/strings.xml
index 4855e31..76c8656 100644
--- a/packages/PrintSpooler/res/values-nl/strings.xml
+++ b/packages/PrintSpooler/res/values-nl/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dubbelzijdig"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Stand"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pagina\'s"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Printer selecteren"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Alle <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Bereik van <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"bijv. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Afdrukservice kiezen"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printers zoeken"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Geen afdrukservices ingeschakeld"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Geen printers gevonden"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> afdrukken"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annuleren"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niet beschikbaar"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> gebruiken?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Je document kan via een of meer servers naar de printer worden verzonden."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Zwart-wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
diff --git a/packages/PrintSpooler/res/values-pa-rIN/strings.xml b/packages/PrintSpooler/res/values-pa-rIN/strings.xml
index 912cf4a..45fa460 100644
--- a/packages/PrintSpooler/res/values-pa-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-pa-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"ਦੋ-ਪਾਸੇ ਦਾ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ਅਨੁਕੂਲਨ"</string>
     <string name="label_pages" msgid="7768589729282182230">"ਸਫ਼ੇ"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ਇੱਕ ਪ੍ਰਿੰਟਰ ਚੁਣੋ"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ਸਾਰੇ <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> ਦੀ ਰੇਂਜ"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ਉਦਾਹਰਨ ਲਈ 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ਪ੍ਰਿੰਟ ਸੇਵਾ ਚੁਣੋ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ਪ੍ਰਿੰਟਰ ਖੋਜ ਰਿਹਾ ਹੈ"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ਪ੍ਰਿੰਟ ਸੇਵਾਵਾਂ ਯੋਗ ਨਹੀਂ ਬਣਾਈਆਂ ਗਈਆਂ"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ਕੋਈ ਪ੍ਰਿੰਟਰ ਨਹੀਂ ਮਿਲੇ"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ਨੂੰ ਪ੍ਰਿੰਟ ਕਰ ਰਿਹਾ ਹੈ"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ਨੂੰ ਰੱਦ ਕਰ ਰਿਹਾ ਹੈ"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ਪ੍ਰਿੰਟਰ ਲਈ ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ਅਗਿਆਤ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ਅਣਉਪਲਬਧ"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਵਰਤਣੀ ਹੈ?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ਤੁਹਾਡਾ ਦਸਤਾਵੇਜ਼ ਪ੍ਰਿੰਟਰ ਵਿੱਚ ਜਾਣ ਲਈ ਇੱਕ ਜਾਂ ਦੋ ਸਰਵਰਾਂ ਵਿੱਚੋਂ ਲੰਘਦਾ ਹੈ।"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ਕਾਲਾ &amp; ਚਿੱਟਾ"</item>
     <item msgid="2762241247228983754">"ਰੰਗ"</item>
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index 055fb5f..df3ee924 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dwustronny"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacja"</string>
     <string name="label_pages" msgid="7768589729282182230">"Strony"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Wybierz drukarkę"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Wszystkie <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Zakres <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"np. 1-5, 8, 11-13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Wybierz usługę drukowania"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Szukanie drukarek"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Brak włączonych usług drukowania"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nie znaleziono drukarek"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Drukowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Anulowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Brak połączenia z drukarką"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"brak informacji"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niedostępne"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Użyć usługi <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Zanim dokument dotrze do drukarki, może przejść przez jeden lub kilka serwerów."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Czarno-białe"</item>
     <item msgid="2762241247228983754">"Kolor"</item>
diff --git a/packages/PrintSpooler/res/values-pt-rBR/strings.xml b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
index 9e38506..90da72b 100644
--- a/packages/PrintSpooler/res/values-pt-rBR/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dois lados"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páginas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Selec. impressora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Todas as <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalo de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"Ex.: 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Selecione o serviço de impressão"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Procurando impressoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem conexão com a impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – não disponível"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Usar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Seu documento pode passar por um ou mais servidores até chegar à impressora."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index 73fabab..99bbd81 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dois lados"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páginas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Selec. impressora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Todas as <xliff:g id="PAGE_COUNT">%1$s</xliff:g> páginas"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalo de <xliff:g id="PAGE_COUNT">%1$s</xliff:g> pág."</string>
     <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Escolher o serviço de impressão"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"A procurar impressoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"A imprimir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"A cancelar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem ligação à impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponível"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Pretende utilizar o <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"O seu documento pode passar por um ou mais servidores no respetivo caminho para a impressora."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index 9e38506..90da72b 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dois lados"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="7768589729282182230">"Páginas"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Selec. impressora"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Todas as <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalo de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"Ex.: 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Selecione o serviço de impressão"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Procurando impressoras"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nenhum serviço de impressão ativado"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem conexão com a impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – não disponível"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Usar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Seu documento pode passar por um ou mais servidores até chegar à impressora."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index 3ff1045..4cfb8ab 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Față-verso"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientare"</string>
     <string name="label_pages" msgid="7768589729282182230">"Pagini"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Selectați imprimanta"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Toate cele <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervalul de <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"de ex. 1-5, 8, 11-13"</string>
@@ -61,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Alegeți serviciul de printare"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Se caută imprimante"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Niciun serviciu de printare activat"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nu au fost găsite imprimante"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Se printează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Se anulează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -76,6 +78,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nu există conexiune la o imprimantă"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"necunoscut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - indisponibil"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Folosiți <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Documentul poate trece prin unul sau mai multe servere pe calea spre imprimantă."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Alb-negru"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-ru/strings.xml b/packages/PrintSpooler/res/values-ru/strings.xml
index a4e12fe..fb49330 100644
--- a/packages/PrintSpooler/res/values-ru/strings.xml
+++ b/packages/PrintSpooler/res/values-ru/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Двусторонний"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="7768589729282182230">"Страницы"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Выберите принтер"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Все <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Диапазон <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"напр., 1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Выберите службу печати"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Поиск принтеров…"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Службы печати недоступны"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Ничего не найдено"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Печать задания \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\"…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отмена задания <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нет связи с принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"неизвестно"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступен"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Использовать <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Документ может пересылаться на принтер через несколько серверов."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-белая"</item>
     <item msgid="2762241247228983754">"Цветная"</item>
diff --git a/packages/PrintSpooler/res/values-si-rLK/strings.xml b/packages/PrintSpooler/res/values-si-rLK/strings.xml
index e8052d6..fb6f145 100644
--- a/packages/PrintSpooler/res/values-si-rLK/strings.xml
+++ b/packages/PrintSpooler/res/values-si-rLK/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"පැති-දෙකක"</string>
     <string name="label_orientation" msgid="2853142581990496477">"දිශානතිය"</string>
     <string name="label_pages" msgid="7768589729282182230">"පිටු"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"මුද්‍රණ යන්ත්‍රයක් තෝරන්න"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"සියලුම <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> පරාසය"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"උ.දා. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"මුද්‍රණ සේවාව තෝරන්න"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"මුද්‍රණ යන්ත්‍ර සොයමින්"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"මුද්‍රණ සේවා සබල නැත"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"මුද්‍රණ යන්ත්‍ර සොයා නොගැනුණි"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> මුද්‍රණය වේ"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"අවලංගු කෙරේ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"මුද්‍රණ යන්ත්‍රය වෙත සම්බන්ධය නැත"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"නොදනී"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ලද නොහැක"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> භාවිත කරන්නද?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"ඔබගේ ලේඛනය මුද්‍රණ යන්ත්‍රයට යන අතරතුර සේවාදායක එකක් හෝ කිහිපයක් හරහා යා හැක."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"කළු සහ සුදු"</item>
     <item msgid="2762241247228983754">"වර්ණය"</item>
diff --git a/packages/PrintSpooler/res/values-sk/strings.xml b/packages/PrintSpooler/res/values-sk/strings.xml
index 3e82076..605237b 100644
--- a/packages/PrintSpooler/res/values-sk/strings.xml
+++ b/packages/PrintSpooler/res/values-sk/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Obojstranné"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientácia"</string>
     <string name="label_pages" msgid="7768589729282182230">"Strany"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Výber tlačiarne"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Všetky: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Rozsah: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"napr. 1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Výber tlačovej služby"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhľadávanie tlačiarní"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Žiadne tlačové služby nie sú aktivované"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nenašli sa žiadne tlačiarne"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Prebieha tlač úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prebieha zrušenie úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Žiadne pripojenie k tlačiarni"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznáme"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie je k dispozícii"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Použiť službu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Skôr ako sa váš dokument dostane do tlačiarne, môže prejsť jedným alebo viacerými servermi."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Čiernobiele"</item>
     <item msgid="2762241247228983754">"Farba"</item>
diff --git a/packages/PrintSpooler/res/values-sl/strings.xml b/packages/PrintSpooler/res/values-sl/strings.xml
index 75c2abe..48d2e1d 100644
--- a/packages/PrintSpooler/res/values-sl/strings.xml
+++ b/packages/PrintSpooler/res/values-sl/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dvostransko"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Postavitev"</string>
     <string name="label_pages" msgid="7768589729282182230">"Strani"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Izberite tiskalnik"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Vse (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Obseg strani: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"npr. 1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Izberite tiskalno storitev"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Iskanje tiskalnikov"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ni omogočenih tiskalnih storitev"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Tiskalnikov ni mogoče najti"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tiskanje: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Preklic: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ni povezave s tiskalnikom"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznano"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ni na voljo"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Želite uporabiti storitev <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokument gre lahko na poti do tiskalnika skozi enega ali več strežnikov."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Črno-belo"</item>
     <item msgid="2762241247228983754">"Barvno"</item>
diff --git a/packages/PrintSpooler/res/values-sq-rAL/strings.xml b/packages/PrintSpooler/res/values-sq-rAL/strings.xml
index adbf700..5ba72ff 100644
--- a/packages/PrintSpooler/res/values-sq-rAL/strings.xml
+++ b/packages/PrintSpooler/res/values-sq-rAL/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Në dy anë"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientimi"</string>
     <string name="label_pages" msgid="7768589729282182230">"Faqe"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Zgjidh një printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Të <xliff:g id="PAGE_COUNT">%1$s</xliff:g> faqet"</string>
     <string name="template_page_range" msgid="428638530038286328">"Gama e faqeve: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"p.sh. 1 - 5,8,11 - 13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Zgjidh shërbimin e printimit"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Po kërkon për printerë"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Nuk ka shërbime printimi të aktivizuara"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nuk u gjet asnjë printer"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Po printon <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Po anulon <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printeri nuk është i lidhur"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"e panjohur"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nuk mundësohet"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Përdor <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokumenti mund të kalojë përmes një ose shumë serverëve deri te printeri."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Bardhezi"</item>
     <item msgid="2762241247228983754">"Ngjyra"</item>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index 58e9622..7a04b8d 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Двострано"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Положај"</string>
     <string name="label_pages" msgid="7768589729282182230">"Странице"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Изаберите штампач"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Све странице (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"Опсег од <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"нпр. 1–5, 8, 11–13"</string>
@@ -61,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Изаберите услугу штампања"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Претрага штампача"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Ниједна услуга штампања није омогућена"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Није пронађен ниједан штампач"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Штампа се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отказује се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -76,6 +78,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нема везе са штампачем"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"непознато"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступан"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Желите ли да користите <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Документ може да прође кроз један или више сервера на путу до штампача."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Црно-бело"</item>
     <item msgid="2762241247228983754">"Боја"</item>
diff --git a/packages/PrintSpooler/res/values-sv/strings.xml b/packages/PrintSpooler/res/values-sv/strings.xml
index 0434903..ec4ad30 100644
--- a/packages/PrintSpooler/res/values-sv/strings.xml
+++ b/packages/PrintSpooler/res/values-sv/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Dubbelsidig"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientering"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sidor"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Välj skrivare"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Alla <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Intervall på <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"t.ex. 1–5,8,11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Välj utskriftstjänst"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Söker efter skrivare"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Inga utskriftstjänster har aktiverats"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Det gick inte att hitta några skrivare"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen anslutning till skrivaren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"okänt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – inte tillgänglig"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Vill du använda <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"På vägen till skrivaren kan dokumentet passera en eller flera servrar."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svartvit"</item>
     <item msgid="2762241247228983754">"Färg"</item>
diff --git a/packages/PrintSpooler/res/values-sw/strings.xml b/packages/PrintSpooler/res/values-sw/strings.xml
index 7c08316..eed3356 100644
--- a/packages/PrintSpooler/res/values-sw/strings.xml
+++ b/packages/PrintSpooler/res/values-sw/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Yenye pande mbili"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Mkao"</string>
     <string name="label_pages" msgid="7768589729282182230">"Kurasa"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Chagua printa"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Kurasa zote <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Mfululizo wa <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"k.m. 1–5, 8, 11–13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Chagua huduma ya printa"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Inatafuta printa"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Huduma za kuchapisha hazijawashwa"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Hakuna printa zilizopatikana"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Inachapisha <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Inaghairi <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hakuna muunganisho kwa printa"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"haijulikani"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - haipatikani"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Ungependa kutumia <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Huenda hati yako ikapitia seva moja au zaidi kabla ya kufika kwenye printa."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nyeusi na Nyeupe"</item>
     <item msgid="2762241247228983754">"Rangi"</item>
diff --git a/packages/PrintSpooler/res/values-ta-rIN/strings.xml b/packages/PrintSpooler/res/values-ta-rIN/strings.xml
index 4c9f135..a9879c3 100644
--- a/packages/PrintSpooler/res/values-ta-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-ta-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"இரு பக்க முறை"</string>
     <string name="label_orientation" msgid="2853142581990496477">"திசையமைப்பு"</string>
     <string name="label_pages" msgid="7768589729282182230">"பக்கங்கள்"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"பிரிண்டரை தேர்ந்தெடு"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"எல்லாம்: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"வரம்பில்: <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"எ.கா. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"அச்சுப் பொறியைத் தேர்வுசெய்யவும்"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"அச்சுப்பொறிகளைத் தேடுகிறது"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"அச்சுப் பொறிகள் இல்லை"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"பிரிண்டர்கள் எதுவுமில்லை"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ஐ அச்சிடுகிறது"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ஐ ரத்துசெய்கிறது"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"அச்சுப்பொறியுடன் இணைக்கப்படவில்லை"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"அறியப்படாதது"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – இல்லை"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"உங்கள் ஆவணம் பிரிண்டருக்குச் செல்லும் வழியில் ஒன்று அல்லது அதற்கு மேற்பட்ட சேவையகங்களைக் கடந்து செல்லக்கூடும்."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"கருப்பு &amp; வெள்ளை"</item>
     <item msgid="2762241247228983754">"வண்ணம்"</item>
diff --git a/packages/PrintSpooler/res/values-te-rIN/strings.xml b/packages/PrintSpooler/res/values-te-rIN/strings.xml
index ffa994b..909cb90 100644
--- a/packages/PrintSpooler/res/values-te-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-te-rIN/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"రెండు వైపుల"</string>
     <string name="label_orientation" msgid="2853142581990496477">"దృగ్విన్యాసం"</string>
     <string name="label_pages" msgid="7768589729282182230">"పేజీలు"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ప్రింటర్ ఎంచుకోండి"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"మొత్తం <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> పరిధి"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ఉదా. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ముద్రణ సేవను ఎంచుకోండి"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ప్రింటర్‌ల కోసం శోధిస్తోంది"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ముద్రణ సేవలు ఏవీ ప్రారంభించలేదు"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ప్రింటర్‌లు కనుగొనబడలేదు"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ను ముద్రిస్తోంది"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ను రద్దు చేస్తోంది"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ప్రింటర్‌కు కనెక్షన్ లేదు"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"తెలియదు"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – అందుబాటులో లేదు"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ని ఉపయోగించాలా?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ పత్రం ప్రింటర్‌కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్‌ల గుండా వెళ్లవచ్చు."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"నలుపు &amp; తెలుపు"</item>
     <item msgid="2762241247228983754">"రంగు"</item>
diff --git a/packages/PrintSpooler/res/values-th/strings.xml b/packages/PrintSpooler/res/values-th/strings.xml
index 89e3082..c33a759 100644
--- a/packages/PrintSpooler/res/values-th/strings.xml
+++ b/packages/PrintSpooler/res/values-th/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"2 ด้าน"</string>
     <string name="label_orientation" msgid="2853142581990496477">"การวางแนว"</string>
     <string name="label_pages" msgid="7768589729282182230">"หน้า"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"เลือกเครื่องพิมพ์"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"ทั้ง <xliff:g id="PAGE_COUNT">%1$s</xliff:g> หน้า"</string>
     <string name="template_page_range" msgid="428638530038286328">"ช่วง <xliff:g id="PAGE_COUNT">%1$s</xliff:g> หน้า"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"เช่น 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"เลือกบริการพิมพ์"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"กำลังค้นหาเครื่องพิมพ์"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"ไม่ได้เปิดใช้บริการพิมพ์"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"ไม่พบเครื่องพิมพ์"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"กำลังพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"กำลังยกเลิก <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ไม่มีการเชื่อมต่อไปยังเครื่องพิมพ์"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ไม่ทราบ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ไม่พร้อมใช้งาน"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"ใช้ <xliff:g id="SERVICE">%1$s</xliff:g> ไหม"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"เอกสารของคุณอาจต้องผ่านมากกว่าหนึ่งเซิร์ฟเวอร์ระหว่างส่งไปยังเครื่องพิมพ์"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ขาวดำ"</item>
     <item msgid="2762241247228983754">"สี"</item>
diff --git a/packages/PrintSpooler/res/values-tl/strings.xml b/packages/PrintSpooler/res/values-tl/strings.xml
index 445d8b0..545bda4 100644
--- a/packages/PrintSpooler/res/values-tl/strings.xml
+++ b/packages/PrintSpooler/res/values-tl/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oryentasyon"</string>
     <string name="label_pages" msgid="7768589729282182230">"Mga Page"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Pumili ng printer"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Lahat ng <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Hanay ng <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"hal. 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pumili ng serbisyo ng pag-print"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Naghahanap ng mga printer"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Walang mga naka-enable na serbisyo sa pag-print"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Walang mga printer na nakita"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Pini-print ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kinakansela ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hindi nakakonekta sa printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"hindi alam"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – hindi available"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Gusto mo bang gamitin ang <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Bago ma-print ang iyong dokumento, maaari itong dumaan sa isa o higit pang mga server."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Kulay"</item>
diff --git a/packages/PrintSpooler/res/values-tr/strings.xml b/packages/PrintSpooler/res/values-tr/strings.xml
index 66d1e99..a13f2df 100644
--- a/packages/PrintSpooler/res/values-tr/strings.xml
+++ b/packages/PrintSpooler/res/values-tr/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Çift taraflı"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Sayfa yönü"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sayfa"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Yazıcı seçin"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> sayfanın tamamı"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> sayfalık aralık"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"ör. 1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Yazdırma hizmetini seçin"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Yazıcılar aranıyor"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Etkin yazıcı hizmeti yok"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Yazıcı bulunamadı"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> yazdırılıyor"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> iptal ediliyor"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Yazıcı bağlantısı yok"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"bilinmiyor"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – kullanılamıyor"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> kullanılsın mı?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Dokümanınız yazıcıya giderken bir veya daha fazla sunucudan geçebilir."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Siyah Beyaz"</item>
     <item msgid="2762241247228983754">"Renkli"</item>
diff --git a/packages/PrintSpooler/res/values-uk/strings.xml b/packages/PrintSpooler/res/values-uk/strings.xml
index fcd3fa6..def21ab 100644
--- a/packages/PrintSpooler/res/values-uk/strings.xml
+++ b/packages/PrintSpooler/res/values-uk/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Двосторонній друк"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Орієнтація"</string>
     <string name="label_pages" msgid="7768589729282182230">"Сторінки"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Виберіть принтер"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Усі <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Діапазон <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"напр.,1–5, 8, 11–13"</string>
@@ -62,6 +63,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Вибрати службу друку"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Пошук принтерів"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Немає служб друку"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Принтери не знайдено"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" друкується"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" скасовується"</string>
@@ -78,6 +80,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Немає з’єднання з принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"невідомо"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" не доступне"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Увімкнути службу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Коли ви надсилаєте документ на принтер, він може проходити через декілька серверів."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Чорно-білий"</item>
     <item msgid="2762241247228983754">"Колір"</item>
diff --git a/packages/PrintSpooler/res/values-ur-rPK/strings.xml b/packages/PrintSpooler/res/values-ur-rPK/strings.xml
index 2c2c460..c031aba 100644
--- a/packages/PrintSpooler/res/values-ur-rPK/strings.xml
+++ b/packages/PrintSpooler/res/values-ur-rPK/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"دو طرف"</string>
     <string name="label_orientation" msgid="2853142581990496477">"سمت بندی"</string>
     <string name="label_pages" msgid="7768589729282182230">"صفحات"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"ایک پرنٹر منتخب کریں"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"سبھی <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> کی رینج"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"‏مثلاً ‎1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"پرنٹ سروس منتخب کریں"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"پرنٹرز تلاش کر رہا ہے"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"کوئی پرنٹ سروس فعال نہیں"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"کوئی پرنٹرز نہيں ملے"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> پرنٹ کررہا ہے"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> کو منسوخ کر رہا ہے"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"پرنٹر کے ساتھ کوئی کنکشن نہیں ہے"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"نامعلوم"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – دستیاب نہیں ہے"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> استعمال کریں؟"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"آپ کی دستاویز پرنٹر تک جاتے ہوئے ممکن ہے ایک یا زیادہ سرورز سے گزرے۔"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"سیاہ و سفید"</item>
     <item msgid="2762241247228983754">"رنگ"</item>
diff --git a/packages/PrintSpooler/res/values-uz-rUZ/strings.xml b/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
index f88eca5..9d615fb 100644
--- a/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
+++ b/packages/PrintSpooler/res/values-uz-rUZ/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Ikki tomonlama"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Joylashuv"</string>
     <string name="label_pages" msgid="7768589729282182230">"Sahifalar"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Printer tanlash"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Barchasi (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="template_page_range" msgid="428638530038286328">"O‘zgarish chegarasi (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"masalan: 1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Chop etish xizmatini tanlang"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printerlar qidirilmoqda"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Hech qaysi chop etish xizmati yoqilmagan"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Printerlar topilmadi"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Chop etilmoqda: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> bekor qilinmoqda"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printer ulanmagan"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"noma’lum"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – mavjud emas"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> xizmatidan foydalanilsinmi?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Hujjatingiz chop etilishidan oldin bir yoki bir necha serverlardan o‘tishi mumkin."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Oq &amp; qora"</item>
     <item msgid="2762241247228983754">"Rang"</item>
diff --git a/packages/PrintSpooler/res/values-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index 2d1e8fa..0167823 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Hai mặt"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Hướng"</string>
     <string name="label_pages" msgid="7768589729282182230">"Trang"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Chọn máy in"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Tất cả <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Phạm vi <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"Ví dụ: 1—5, 8, 11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Chọn dịch vụ in"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Đang tìm kiếm máy in"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Chưa kích hoạt dịch vụ in nào"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Không tìm thấy máy in"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"In <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Hủy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Không có kết nối nào với máy in"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"không xác định"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – không khả dụng"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Sử dụng <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Tài liệu của bạn có thể đi qua một hoặc nhiều máy chủ trên đường đến máy in."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Đen trắng"</item>
     <item msgid="2762241247228983754">"Màu"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 553bc89..f2b304a 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -27,6 +27,8 @@
     <string name="label_duplex" msgid="5370037254347072243">"双面"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="7768589729282182230">"页数"</string>
+    <!-- no translation found for destination_default_text (5422708056807065710) -->
+    <skip />
     <string name="template_all_pages" msgid="3322235982020148762">"全部<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例如:1-5、8、11-13"</string>
@@ -60,6 +62,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"选择打印服务"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜索打印机"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"未启用任何打印服务"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"找不到打印机"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
@@ -74,6 +77,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"未与打印机建立连接"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"未知"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - 无法使用"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"要使用<xliff:g id="SERVICE">%1$s</xliff:g>吗?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"您的文档可能会通过一个或多个服务器发送至打印机。"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rHK/strings.xml b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
index 74b301f..35643f3 100644
--- a/packages/PrintSpooler/res/values-zh-rHK/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"雙面"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="7768589729282182230">"頁數"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"選擇打印機"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"全部 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例如:1-5,8,11-13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋打印機"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"沒有已啟用的列印服務"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"找不到打印機"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與打印機連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"要使用 <xliff:g id="SERVICE">%1$s</xliff:g> 嗎?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"您的文件可能會通過一部或多部伺服器才傳送至打印機。"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index d91fe52..40c44ff 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"雙面"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="7768589729282182230">"頁面"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"選取印表機"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"全部 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
     <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例如:1—5,8,11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋印表機"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"未啟用任何列印服務"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"找不到印表機"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與印表機建立連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"要使用「<xliff:g id="SERVICE">%1$s</xliff:g>」嗎?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"您的文件可能會透過一或多個伺服器輾轉傳送至印表機。"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zu/strings.xml b/packages/PrintSpooler/res/values-zu/strings.xml
index 6ef2499..e0f6f34 100644
--- a/packages/PrintSpooler/res/values-zu/strings.xml
+++ b/packages/PrintSpooler/res/values-zu/strings.xml
@@ -27,6 +27,7 @@
     <string name="label_duplex" msgid="5370037254347072243">"Inezinhlangothi ezimbili"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Umumo"</string>
     <string name="label_pages" msgid="7768589729282182230">"Amakhasi"</string>
+    <string name="destination_default_text" msgid="5422708056807065710">"Khetha iphrinta"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"Konke <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="template_page_range" msgid="428638530038286328">"Ibanga le-<xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"isb. 1—5, 8, 11—13"</string>
@@ -60,6 +61,7 @@
     </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Khetha isevisi yephrinta"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Isesha amaphrinta"</string>
+    <string name="print_no_print_services" msgid="8561247706423327966">"Amasevisi ephrinta akavuliwe."</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Awekho amaphrinta atholiwe"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Iphrinta i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ikhansela i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
@@ -74,6 +76,8 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Akukho ukuxhumana kuphrinta"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"akwaziwa"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"I-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ayitholakali"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"Sebenzisa i-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+    <string name="print_service_security_warning_summary" msgid="1427434625361692006">"Idokhumenti yakho ingase idlule iseva eyodwa noma amaningi lapho iya kuphrinta."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Okumnyama nokumhlophe"</item>
     <item msgid="2762241247228983754">"Umbala"</item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 50237832..6d81788 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -49,6 +49,9 @@
     <!-- Label of the page selection widget. [CHAR LIMIT=20] -->
     <string name="label_pages">Pages</string>
 
+    <!-- Label of the destination widget. [CHAR LIMIT=20] -->
+    <string name="destination_default_text">Select a printer</string>
+
     <!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] -->
     <string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
 
@@ -149,6 +152,9 @@
     <!-- Title for the prompt shown as a placeholder if no printers are found while not searching. [CHAR LIMIT=50] -->
     <string name="print_searching_for_printers">Searching for printers</string>
 
+    <!-- Title for the prompt shown as a placeholder if there are no print services. [CHAR LIMIT=50] -->
+    <string name="print_no_print_services">No print services enabled</string>
+
     <!-- Title for the prompt shown as a placeholder if there are no printers while searching. [CHAR LIMIT=50] -->
     <string name="print_no_printers">No printers found</string>
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4f80d91..e758835 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -64,6 +64,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
@@ -125,6 +126,8 @@
 
     private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
 
+    private static final String HAS_PRINTED_PREF = "has_printed";
+
     private static final int ORIENTATION_PORTRAIT = 0;
     private static final int ORIENTATION_LANDSCAPE = 1;
 
@@ -187,6 +190,7 @@
 
     private Spinner mDestinationSpinner;
     private DestinationAdapter mDestinationSpinnerAdapter;
+    private boolean mShowDestinationPrompt;
 
     private Spinner mMediaSizeSpinner;
     private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -240,7 +244,9 @@
             throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
                     + " cannot be null");
         }
-        mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+        if (mPrintJob.getAttributes() == null) {
+            mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+        }
 
         final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
         if (adapter == null) {
@@ -664,9 +670,11 @@
             }
         }
 
-        PrinterId printerId = mCurrentPrinter.getId();
-        final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
-        mDestinationSpinner.setSelection(index);
+        if (mCurrentPrinter != null) {
+            PrinterId printerId = mCurrentPrinter.getId();
+            final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+            mDestinationSpinner.setSelection(index);
+        }
     }
 
     private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
@@ -1089,6 +1097,7 @@
 
         updateOptionsUi();
         addCurrentPrinterToHistory();
+        setUserPrinted();
 
         PageRange[] selectedPages = computeSelectedPages();
         if (!Arrays.equals(mSelectedPages, selectedPages)) {
@@ -1191,6 +1200,29 @@
         // Print button
         mPrintButton = (ImageView) findViewById(R.id.print_button);
         mPrintButton.setOnClickListener(clickListener);
+
+        // Special prompt instead of destination spinner for the first time the user printed
+        if (!hasUserEverPrinted()) {
+            mShowDestinationPrompt = true;
+
+            mSummaryCopies.setEnabled(false);
+            mSummaryPaperSize.setEnabled(false);
+
+            mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    mShowDestinationPrompt = false;
+                    mSummaryCopies.setEnabled(true);
+                    mSummaryPaperSize.setEnabled(true);
+                    updateOptionsUi();
+
+                    mDestinationSpinner.setOnTouchListener(null);
+                    mDestinationSpinnerAdapter.notifyDataSetChanged();
+
+                    return false;
+                }
+            });
+        }
     }
 
     /**
@@ -1328,6 +1360,22 @@
                 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
     }
 
+    /**
+     * Disable all options UI elements, beside the {@link #mDestinationSpinner}
+     */
+    private void disableOptionsUi() {
+        mCopiesEditText.setEnabled(false);
+        mCopiesEditText.setFocusable(false);
+        mMediaSizeSpinner.setEnabled(false);
+        mColorModeSpinner.setEnabled(false);
+        mDuplexModeSpinner.setEnabled(false);
+        mOrientationSpinner.setEnabled(false);
+        mRangeOptionsSpinner.setEnabled(false);
+        mPageRangeEditText.setEnabled(false);
+        mPrintButton.setVisibility(View.GONE);
+        mMoreOptionsButton.setEnabled(false);
+    }
+
     void updateOptionsUi() {
         // Always update the summary.
         updateSummary();
@@ -1342,32 +1390,14 @@
             if (mState != STATE_PRINTER_UNAVAILABLE) {
                 mDestinationSpinner.setEnabled(false);
             }
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
         // If no current printer, or it has no capabilities, or it is not
         // available, we disable all print options except the destination.
         if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
@@ -1675,6 +1705,10 @@
             mCopiesEditText.setText(MIN_COPIES_STRING);
             mCopiesEditText.requestFocus();
         }
+
+        if (mShowDestinationPrompt) {
+            disableOptionsUi();
+        }
     }
 
     private void updateSummary() {
@@ -1976,6 +2010,32 @@
         }
     }
 
+
+    /**
+     * Check if the user has ever printed a document
+     *
+     * @return true iff the user has ever printed a document
+     */
+    private boolean hasUserEverPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        return preferences.getBoolean(HAS_PRINTED_PREF, false);
+    }
+
+    /**
+     * Remember that the user printed a document
+     */
+    private void setUserPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
+            SharedPreferences.Editor edit = preferences.edit();
+
+            edit.putBoolean(HAS_PRINTED_PREF, true);
+            edit.apply();
+        }
+    }
+
     private final class DestinationAdapter extends BaseAdapter
             implements PrinterRegistry.OnPrintersChangeListener {
         private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
@@ -1984,6 +2044,11 @@
 
         private boolean mHistoricalPrintersLoaded;
 
+        /**
+         * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
+         */
+        private boolean hadPromptView;
+
         public DestinationAdapter() {
             mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
             if (mHistoricalPrintersLoaded) {
@@ -2094,9 +2159,20 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = getLayoutInflater().inflate(
-                        R.layout.printer_dropdown_item, parent, false);
+            if (mShowDestinationPrompt) {
+                if (convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_prompt, parent, false);
+                    hadPromptView = true;
+                }
+
+                return convertView;
+            } else {
+                // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
+                if (hadPromptView || convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_item, parent, false);
+                }
             }
 
             CharSequence title = null;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 3905bada..f4c15bd 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -84,6 +84,11 @@
 
     private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
 
+    /**
+     * If there are any enabled print services
+     */
+    private boolean mHasEnabledPrintServices;
+
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
             new ArrayList<>();
 
@@ -175,10 +180,6 @@
             }
         });
 
-        if (mAddPrinterServices.isEmpty()) {
-            menu.removeItem(R.id.action_add_printer);
-        }
-
         return true;
     }
 
@@ -230,6 +231,7 @@
     public void onResume() {
         super.onResume();
         updateServicesWithAddPrinterActivity();
+        updateEmptyView((DestinationAdapter)mListView.getAdapter());
         invalidateOptionsMenu();
     }
 
@@ -258,6 +260,7 @@
     }
 
     private void updateServicesWithAddPrinterActivity() {
+        mHasEnabledPrintServices = true;
         mAddPrinterServices.clear();
 
         // Get all enabled print services.
@@ -266,6 +269,7 @@
 
         // No enabled print services - done.
         if (enabledServices.isEmpty()) {
+            mHasEnabledPrintServices = false;
             return;
         }
 
@@ -324,7 +328,10 @@
         }
         TextView titleView = (TextView) findViewById(R.id.title);
         View progressBar = findViewById(R.id.progress_bar);
-        if (adapter.getUnfilteredCount() <= 0) {
+        if (!mHasEnabledPrintServices) {
+            titleView.setText(R.string.print_no_print_services);
+            progressBar.setVisibility(View.GONE);
+        } else if (adapter.getUnfilteredCount() <= 0) {
             titleView.setText(R.string.print_searching_for_printers);
             progressBar.setVisibility(View.VISIBLE);
         } else {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9e48849..a37196e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -348,4 +348,7 @@
     <!-- Header for items under the work user [CHAR LIMIT=30] -->
     <string name="category_work">Work</string>
 
+    <!-- Full package name of OEM preferred device feedback reporter. Leave this blank, overlaid in Settings/TvSettings [DO NOT TRANSLATE] -->
+    <string name="oem_preferred_feedback_reporter" translatable="false" />
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
new file mode 100644
index 0000000..ff1c866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeviceInfoUtils {
+    private static final String TAG = "DeviceInfoUtils";
+
+    private static final String FILENAME_PROC_VERSION = "/proc/version";
+    private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
+
+    /**
+     * Reads a line from the specified file.
+     * @param filename the file to read from
+     * @return the first line, if any.
+     * @throws IOException if the file couldn't be read
+     */
+    private static String readLine(String filename) throws IOException {
+        BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+        try {
+            return reader.readLine();
+        } finally {
+            reader.close();
+        }
+    }
+
+    public static String getFormattedKernelVersion() {
+        try {
+            return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
+        } catch (IOException e) {
+            Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
+                    e);
+
+            return "Unavailable";
+        }
+    }
+
+    public static String formatKernelVersion(String rawKernelVersion) {
+        // Example (see tests for more):
+        // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
+        //     (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
+        //     Thu Jun 28 11:02:39 PDT 2012
+
+        final String PROC_VERSION_REGEX =
+                "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
+                "\\((\\S+?)\\) " +        /* group 2: "x@y.com" (kernel builder) */
+                "(?:\\(gcc.+? \\)) " +    /* ignore: GCC version information */
+                "(#\\d+) " +              /* group 3: "#1" */
+                "(?:.*?)?" +              /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+                "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
+
+        Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+        if (!m.matches()) {
+            Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
+            return "Unavailable";
+        } else if (m.groupCount() < 4) {
+            Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+                    + " groups");
+            return "Unavailable";
+        }
+        return m.group(1) + "\n" +                 // 3.0.31-g6fb96c9
+                m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1
+                m.group(4);                            // Thu Jun 28 11:02:39 PDT 2012
+    }
+
+    /**
+     * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
+     * @return a string to append to the model number description.
+     */
+    public static String getMsvSuffix() {
+        // Production devices should have a non-zero value. If we can't read it, assume it's a
+        // production device so that we don't accidentally show that it's an ENGINEERING device.
+        try {
+            String msv = readLine(FILENAME_MSV);
+            // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
+            if (Long.parseLong(msv, 16) == 0) {
+                return " (ENGINEERING)";
+            }
+        } catch (IOException|NumberFormatException e) {
+            // Fail quietly, as the file may not exist on some devices, or may be unreadable
+        }
+        return "";
+    }
+
+    public static String getFeedbackReporterPackage(Context context) {
+        final String feedbackReporter =
+                context.getResources().getString(R.string.oem_preferred_feedback_reporter);
+        if (TextUtils.isEmpty(feedbackReporter)) {
+            // Reporter not configured. Return.
+            return feedbackReporter;
+        }
+        // Additional checks to ensure the reporter is on system image, and reporter is
+        // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
+        final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> resolvedPackages =
+                pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
+        for (ResolveInfo info : resolvedPackages) {
+            if (info.activityInfo != null) {
+                if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
+                    try {
+                        ApplicationInfo ai =
+                                pm.getApplicationInfo(info.activityInfo.packageName, 0);
+                        if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                            // Package is on the system image
+                            if (TextUtils.equals(
+                                    info.activityInfo.packageName, feedbackReporter)) {
+                                return feedbackReporter;
+                            }
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // No need to do anything here.
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public static String getSecurityPatch() {
+        String patch = Build.VERSION.SECURITY_PATCH;
+        if (!"".equals(patch)) {
+            try {
+                SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
+                Date patchDate = template.parse(patch);
+                String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy");
+                patch = DateFormat.format(format, patchDate).toString();
+            } catch (ParseException e) {
+                // broken parse; fall through and use the raw string
+            }
+            return patch;
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
new file mode 100644
index 0000000..ef511bb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SyncAdapterType;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class for monitoring accounts on the device for a given user.
+ *
+ * Classes using this helper should implement {@link OnAccountsUpdateListener}.
+ * {@link OnAccountsUpdateListener#onAccountsUpdate(UserHandle)} will then be
+ * called once accounts get updated. For setting up listening for account
+ * updates, {@link #listenToAccountUpdates()} and
+ * {@link #stopListeningToAccountUpdates()} should be used.
+ */
+final public class AuthenticatorHelper extends BroadcastReceiver {
+    private static final String TAG = "AuthenticatorHelper";
+
+    private final Map<String, AuthenticatorDescription> mTypeToAuthDescription = new HashMap<>();
+    private final ArrayList<String> mEnabledAccountTypes = new ArrayList<>();
+    private final Map<String, Drawable> mAccTypeIconCache = new HashMap<>();
+    private final HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = new HashMap<>();
+
+    private final UserHandle mUserHandle;
+    private final Context mContext;
+    private final OnAccountsUpdateListener mListener;
+    private boolean mListeningToAccountUpdates;
+
+    public interface OnAccountsUpdateListener {
+        void onAccountsUpdate(UserHandle userHandle);
+    }
+
+    public AuthenticatorHelper(Context context, UserHandle userHandle,
+            OnAccountsUpdateListener listener) {
+        mContext = context;
+        mUserHandle = userHandle;
+        mListener = listener;
+        // This guarantees that the helper is ready to use once constructed: the account types and
+        // authorities are initialized
+        onAccountsUpdated(null);
+    }
+
+    public String[] getEnabledAccountTypes() {
+        return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]);
+    }
+
+    public void preloadDrawableForType(final Context context, final String accountType) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                getDrawableForType(context, accountType);
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+
+    /**
+     * Gets an icon associated with a particular account type. If none found, return null.
+     * @param accountType the type of account
+     * @return a drawable for the icon or a default icon returned by
+     * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
+     */
+    public Drawable getDrawableForType(Context context, final String accountType) {
+        Drawable icon = null;
+        synchronized (mAccTypeIconCache) {
+            if (mAccTypeIconCache.containsKey(accountType)) {
+                return mAccTypeIconCache.get(accountType);
+            }
+        }
+        if (mTypeToAuthDescription.containsKey(accountType)) {
+            try {
+                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+                Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+                        mUserHandle);
+                icon = mContext.getPackageManager().getUserBadgedIcon(
+                        authContext.getDrawable(desc.iconId), mUserHandle);
+                synchronized (mAccTypeIconCache) {
+                    mAccTypeIconCache.put(accountType, icon);
+                }
+            } catch (PackageManager.NameNotFoundException|Resources.NotFoundException e) {
+                // Ignore
+            }
+        }
+        if (icon == null) {
+            icon = context.getPackageManager().getDefaultActivityIcon();
+        }
+        return icon;
+    }
+
+    /**
+     * Gets the label associated with a particular account type. If none found, return null.
+     * @param accountType the type of account
+     * @return a CharSequence for the label or null if one cannot be found.
+     */
+    public CharSequence getLabelForType(Context context, final String accountType) {
+        CharSequence label = null;
+        if (mTypeToAuthDescription.containsKey(accountType)) {
+            try {
+                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+                Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+                        mUserHandle);
+                label = authContext.getResources().getText(desc.labelId);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "No label name for account type " + accountType);
+            } catch (Resources.NotFoundException e) {
+                Log.w(TAG, "No label icon for account type " + accountType);
+            }
+        }
+        return label;
+    }
+
+    /**
+     * Gets the package associated with a particular account type. If none found, return null.
+     * @param accountType the type of account
+     * @return the package name or null if one cannot be found.
+     */
+    public String getPackageForType(final String accountType) {
+        if (mTypeToAuthDescription.containsKey(accountType)) {
+            AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+            return desc.packageName;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the resource id of the label associated with a particular account type. If none found,
+     * return -1.
+     * @param accountType the type of account
+     * @return a resource id for the label or -1 if none found;
+     */
+    public int getLabelIdForType(final String accountType) {
+        if (mTypeToAuthDescription.containsKey(accountType)) {
+            AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+            return desc.labelId;
+        }
+        return -1;
+    }
+
+    /**
+     * Updates provider icons. Subclasses should call this in onCreate()
+     * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
+     */
+    public void updateAuthDescriptions(Context context) {
+        AuthenticatorDescription[] authDescs = AccountManager.get(context)
+                .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier());
+        for (int i = 0; i < authDescs.length; i++) {
+            mTypeToAuthDescription.put(authDescs[i].type, authDescs[i]);
+        }
+    }
+
+    public boolean containsAccountType(String accountType) {
+        return mTypeToAuthDescription.containsKey(accountType);
+    }
+
+    public AuthenticatorDescription getAccountTypeDescription(String accountType) {
+        return mTypeToAuthDescription.get(accountType);
+    }
+
+    public boolean hasAccountPreferences(final String accountType) {
+        if (containsAccountType(accountType)) {
+            AuthenticatorDescription desc = getAccountTypeDescription(accountType);
+            if (desc != null && desc.accountPreferencesId != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void onAccountsUpdated(Account[] accounts) {
+        updateAuthDescriptions(mContext);
+        if (accounts == null) {
+            accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier());
+        }
+        mEnabledAccountTypes.clear();
+        mAccTypeIconCache.clear();
+        for (int i = 0; i < accounts.length; i++) {
+            final Account account = accounts[i];
+            if (!mEnabledAccountTypes.contains(account.type)) {
+                mEnabledAccountTypes.add(account.type);
+            }
+        }
+        buildAccountTypeToAuthoritiesMap();
+        if (mListeningToAccountUpdates) {
+            mListener.onAccountsUpdate(mUserHandle);
+        }
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        // TODO: watch for package upgrades to invalidate cache; see http://b/7206643
+        final Account[] accounts = AccountManager.get(mContext)
+                .getAccountsAsUser(mUserHandle.getIdentifier());
+        onAccountsUpdated(accounts);
+    }
+
+    public void listenToAccountUpdates() {
+        if (!mListeningToAccountUpdates) {
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+            // At disk full, certain actions are blocked (such as writing the accounts to storage).
+            // It is useful to also listen for recovery from disk full to avoid bugs.
+            intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+            mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null);
+            mListeningToAccountUpdates = true;
+        }
+    }
+
+    public void stopListeningToAccountUpdates() {
+        if (mListeningToAccountUpdates) {
+            mContext.unregisterReceiver(this);
+            mListeningToAccountUpdates = false;
+        }
+    }
+
+    public ArrayList<String> getAuthoritiesForAccountType(String type) {
+        return mAccountTypeToAuthorities.get(type);
+    }
+
+    private void buildAccountTypeToAuthoritiesMap() {
+        mAccountTypeToAuthorities.clear();
+        SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+                mUserHandle.getIdentifier());
+        for (int i = 0, n = syncAdapters.length; i < n; i++) {
+            final SyncAdapterType sa = syncAdapters[i];
+            ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
+            if (authorities == null) {
+                authorities = new ArrayList<String>();
+                mAccountTypeToAuthorities.put(sa.accountType, authorities);
+            }
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Added authority " + sa.authority + " to accountType "
+                        + sa.accountType);
+            }
+            authorities.add(sa.authority);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e4b1ed8..d994841 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -808,7 +808,12 @@
             // The pairing dialog now warns of phone-book access for paired devices.
             // No separate prompt is displayed after pairing.
             if (getPhonebookPermissionChoice() == CachedBluetoothDevice.ACCESS_UNKNOWN) {
-                setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+                if (mDevice.getBluetoothClass().getDeviceClass()
+                        == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
+                    setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+                } else {
+                    setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
+                }
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index f5fc698..6102bef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -18,6 +18,7 @@
 import android.annotation.LayoutRes;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.support.v4.widget.DrawerLayout;
 import android.util.Pair;
@@ -54,6 +55,13 @@
             return;
         }
         Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+        TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
+        if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
+            toolbar.setVisibility(View.GONE);
+            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            mDrawerLayout = null;
+            return;
+        }
         setActionBar(toolbar);
         mDrawerAdapter = new SettingsDrawerAdapter(this);
         ListView listView = (ListView) findViewById(R.id.left_drawer);
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
new file mode 100644
index 0000000..f03e94d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location;
+
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Retrieves the information of applications which accessed location recently.
+ */
+public class RecentLocationApps {
+    private static final String TAG = RecentLocationApps.class.getSimpleName();
+    private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+
+    private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
+
+    private static final int[] LOCATION_OPS = new int[] {
+            AppOpsManager.OP_MONITOR_LOCATION,
+            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
+    };
+
+    private final PackageManager mPackageManager;
+    private final Context mContext;
+
+    public RecentLocationApps(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+    }
+
+    /**
+     * Fills a list of applications which queried location recently within specified time.
+     */
+    public List<Request> getAppList() {
+        // Retrieve a location usage list from AppOps
+        AppOpsManager aoManager =
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
+
+        // Process the AppOps list and generate a preference list.
+        ArrayList<Request> requests = new ArrayList<>(appOps.size());
+        final long now = System.currentTimeMillis();
+        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        final List<UserHandle> profiles = um.getUserProfiles();
+
+        final int appOpsN = appOps.size();
+        for (int i = 0; i < appOpsN; ++i) {
+            AppOpsManager.PackageOps ops = appOps.get(i);
+            // Don't show the Android System in the list - it's not actionable for the user.
+            // Also don't show apps belonging to background users except managed users.
+            String packageName = ops.getPackageName();
+            int uid = ops.getUid();
+            int userId = UserHandle.getUserId(uid);
+            boolean isAndroidOs =
+                    (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
+            if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
+                continue;
+            }
+            Request request = getRequestFromOps(now, ops);
+            if (request != null) {
+                requests.add(request);
+            }
+        }
+
+        return requests;
+    }
+
+    /**
+     * Creates a Request entry for the given PackageOps.
+     *
+     * This method examines the time interval of the PackageOps first. If the PackageOps is older
+     * than the designated interval, this method ignores the PackageOps object and returns null.
+     * When the PackageOps is fresh enough, this method returns a Request object for the package
+     */
+    private Request getRequestFromOps(long now,
+            AppOpsManager.PackageOps ops) {
+        String packageName = ops.getPackageName();
+        List<AppOpsManager.OpEntry> entries = ops.getOps();
+        boolean highBattery = false;
+        boolean normalBattery = false;
+        // Earliest time for a location request to end and still be shown in list.
+        long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
+        for (AppOpsManager.OpEntry entry : entries) {
+            if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
+                switch (entry.getOp()) {
+                    case AppOpsManager.OP_MONITOR_LOCATION:
+                        normalBattery = true;
+                        break;
+                    case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
+                        highBattery = true;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        if (!highBattery && !normalBattery) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, packageName + " hadn't used location within the time interval.");
+            }
+            return null;
+        }
+
+        // The package is fresh enough, continue.
+
+        int uid = ops.getUid();
+        int userId = UserHandle.getUserId(uid);
+
+        Request request = null;
+        try {
+            IPackageManager ipm = AppGlobals.getPackageManager();
+            ApplicationInfo appInfo =
+                    ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
+            if (appInfo == null) {
+                Log.w(TAG, "Null application info retrieved for package " + packageName
+                        + ", userId " + userId);
+                return null;
+            }
+
+            final UserHandle userHandle = new UserHandle(userId);
+            Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
+            Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle);
+            CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
+            CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
+            if (appLabel.toString().contentEquals(badgedAppLabel)) {
+                // If badged label is not different from original then no need for it as
+                // a separate content description.
+                badgedAppLabel = null;
+            }
+            request = new Request(packageName, userHandle, icon, appLabel, highBattery,
+                    badgedAppLabel);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error while retrieving application info for package " + packageName
+                    + ", userId " + userId, e);
+        }
+
+        return request;
+    }
+
+    public static class Request {
+        public final String packageName;
+        public final UserHandle userHandle;
+        public final Drawable icon;
+        public final CharSequence label;
+        public final boolean isHighBattery;
+        public final CharSequence contentDescription;
+
+        private Request(String packageName, UserHandle userHandle, Drawable icon,
+                CharSequence label, boolean isHighBattery, CharSequence contentDescription) {
+            this.packageName = packageName;
+            this.userHandle = userHandle;
+            this.icon = icon;
+            this.label = label;
+            this.isHighBattery = isHighBattery;
+            this.contentDescription = contentDescription;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java b/packages/SettingsLib/src/com/android/settingslib/net/MobileDataController.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
rename to packages/SettingsLib/src/com/android/settingslib/net/MobileDataController.java
index a7fdadc..642b60e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/MobileDataController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.policy;
-
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-import static android.telephony.TelephonyManager.SIM_STATE_READY;
-import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
-import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+package com.android.settingslib.net;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -42,7 +35,14 @@
 import java.util.Date;
 import java.util.Locale;
 
-public class MobileDataControllerImpl implements NetworkController.MobileDataController {
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+
+public class MobileDataController {
     private static final String TAG = "MobileDataController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -60,9 +60,9 @@
 
     private INetworkStatsSession mSession;
     private Callback mCallback;
-    private NetworkControllerImpl mNetworkController;
+    private NetworkNameProvider mNetworkController;
 
-    public MobileDataControllerImpl(Context context) {
+    public MobileDataController(Context context) {
         mContext = context;
         mTelephonyManager = TelephonyManager.from(context);
         mConnectivityManager = ConnectivityManager.from(context);
@@ -71,7 +71,7 @@
         mPolicyManager = NetworkPolicyManager.from(mContext);
     }
 
-    public void setNetworkController(NetworkControllerImpl networkController) {
+    public void setNetworkController(NetworkNameProvider networkController) {
         mNetworkController = networkController;
     }
 
@@ -161,7 +161,7 @@
             } else {
                 usage.warningLevel = DEFAULT_WARNING_LEVEL;
             }
-            if (usage != null) {
+            if (usage != null && mNetworkController != null) {
                 usage.carrier = mNetworkController.getMobileDataNetworkName();
             }
             return usage;
@@ -231,6 +231,18 @@
         }
     }
 
+    public interface NetworkNameProvider {
+        String getMobileDataNetworkName();
+    }
+
+    public static class DataUsageInfo {
+        public String carrier;
+        public String period;
+        public long limitLevel;
+        public long warningLevel;
+        public long usageLevel;
+    }
+
     public interface Callback {
         void onMobileDataEnabled(boolean enabled);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java
new file mode 100644
index 0000000..f5e39be
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.widget;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedRotateDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+public class AnimatedImageView extends ImageView {
+    private AnimatedRotateDrawable mDrawable;
+    private boolean mAnimating;
+
+    public AnimatedImageView(Context context) {
+        super(context);
+    }
+
+    public AnimatedImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void updateDrawable() {
+        if (isShown() && mDrawable != null) {
+            mDrawable.stop();
+        }
+        final Drawable drawable = getDrawable();
+        if (drawable instanceof AnimatedRotateDrawable) {
+            mDrawable = (AnimatedRotateDrawable) drawable;
+            // TODO: define in drawable xml once we have public attrs.
+            mDrawable.setFramesCount(56);
+            mDrawable.setFramesDuration(32);
+            if (isShown() && mAnimating) {
+                mDrawable.start();
+            }
+        } else {
+            mDrawable = null;
+        }
+    }
+
+    private void updateAnimating() {
+        if (mDrawable != null) {
+            if (isShown() && mAnimating) {
+                mDrawable.start();
+            } else {
+                mDrawable.stop();
+            }
+        }
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        super.setImageDrawable(drawable);
+        updateDrawable();
+    }
+
+    @Override
+    public void setImageResource(int resid) {
+        super.setImageResource(resid);
+        updateDrawable();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateAnimating();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updateAnimating();
+    }
+
+    public void setAnimating(boolean animating) {
+        mAnimating = animating;
+        updateAnimating();
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int vis) {
+        super.onVisibilityChanged(changedView, vis);
+        updateAnimating();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
new file mode 100644
index 0000000..fabae57
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 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.wifi;
+
+import android.content.Intent;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import java.util.List;
+
+public class WifiStatusTracker {
+
+    private final WifiManager mWifiManager;
+    public boolean enabled;
+    public boolean connected;
+    public String ssid;
+    public int rssi;
+    public int level;
+
+    public WifiStatusTracker(WifiManager wifiManager) {
+        mWifiManager = wifiManager;
+    }
+
+    public void handleBroadcast(Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+            enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                    WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
+        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            final NetworkInfo networkInfo = (NetworkInfo)
+                    intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            connected = networkInfo != null && networkInfo.isConnected();
+            // If Connected grab the signal strength and ssid.
+            if (connected) {
+                // try getting it out of the intent first
+                WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+                        ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+                        : mWifiManager.getConnectionInfo();
+                if (info != null) {
+                    ssid = getSsid(info);
+                } else {
+                    ssid = null;
+                }
+            } else if (!connected) {
+                ssid = null;
+            }
+        } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+            // Default to -200 as its below WifiManager.MIN_RSSI.
+            rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
+            level = WifiManager.calculateSignalLevel(rssi, 5);
+        }
+    }
+
+    private String getSsid(WifiInfo info) {
+        String ssid = info.getSSID();
+        if (ssid != null) {
+            return ssid;
+        }
+        // OK, it's not in the connectionInfo; we have to go hunting for it
+        List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
+        int length = networks.size();
+        for (int i = 0; i < length; i++) {
+            if (networks.get(i).networkId == info.getNetworkId()) {
+                return networks.get(i).SSID;
+            }
+        }
+        return null;
+    }
+}
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 71aefad..ba991fb 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,11 +8,12 @@
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
-                 android:icon="@mipmap/ic_launcher_settings">
-                 
-    <!-- todo add: android:neverEncrypt="true" -->
+                 android:icon="@mipmap/ic_launcher_settings"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
 
-        <provider android:name="SettingsProvider" android:authorities="settings"
+        <provider android:name="SettingsProvider"
+                  android:authorities="settings"
                   android:multiprocess="false"
                   android:exported="true"
                   android:singleUser="true"
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index b9a9c24..2e96f18 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -159,6 +159,7 @@
         String key_mgmt = "";
         boolean certUsed = false;
         boolean hasWepKey = false;
+        boolean isEap = false;
         final ArrayList<String> rawLines = new ArrayList<String>();
 
         public static Network readFromStream(BufferedReader in) {
@@ -189,6 +190,9 @@
                 ssid = line;
             } else if (line.startsWith("key_mgmt=")) {
                 key_mgmt = line;
+                if (line.contains("EAP")) {
+                    isEap = true;
+                }
             } else if (line.startsWith("client_cert=")) {
                 certUsed = true;
             } else if (line.startsWith("ca_cert=")) {
@@ -197,6 +201,8 @@
                 certUsed = true;
             } else if (line.startsWith("wep_")) {
                 hasWepKey = true;
+            } else if (line.startsWith("eap=")) {
+                isEap = true;
             }
         }
 
@@ -325,6 +331,13 @@
                                     continue;
                                 }
                             }
+                            // Don't propagate EAP network definitions
+                            if (net.isEap) {
+                                if (DEBUG_BACKUP) {
+                                    Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt);
+                                }
+                                continue;
+                            }
                             if (! mKnownNetworks.contains(net)) {
                                 if (DEBUG_BACKUP) {
                                     Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
@@ -353,6 +366,12 @@
                     continue;
                 }
 
+                if (net.isEap) {
+                    // Similarly, omit EAP network definitions to avoid propagating
+                    // controlled enterprise network definitions.
+                    continue;
+                }
+
                 net.write(w);
             }
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8b1caf9..3971706 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -904,12 +904,12 @@
             }
         }
 
-        // Enforce what the calling package can mutate the system settings.
-        enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, runAsUserId);
-
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
 
+        // Enforce what the calling package can mutate the system settings.
+        enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, callingUserId);
+
         // Determine the owning user as some profile settings are cloned from the parent.
         final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 95d7772..dde9709 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -392,12 +392,9 @@
             if (DEBUG_PERSISTENCE) {
                 Slog.i(LOG_TAG, "[PERSIST END]");
             }
-
-            // Any error while writing is fatal.
         } catch (Throwable t) {
             Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
             destination.failWrite(out);
-            throw new IllegalStateException("Failed to write settings, restoring backup", t);
         } finally {
             IoUtils.closeQuietly(out);
         }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 05591cc..8c39ee6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -108,7 +108,9 @@
     <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
     <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
 
-    <application android:label="@string/app_label">
+    <application android:label="@string/app_label"
+                 android:forceDeviceEncrypted="true"
+                 android:encryptionAware="true">
         <provider
             android:name="android.support.v4.content.FileProvider"
             android:authorities="com.android.shell"
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 3db0848..4469d38 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -33,4 +33,8 @@
 
     <!-- Title for documents backend that offers bugreports. -->
     <string name="bugreport_storage_title">Bug reports</string>
+
+    <!-- Toast message sent when the bugreport file could be read. -->
+    <string name="bugreport_unreadable_text">Bug report file could not be read</string>
+
 </resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index e90a3b5..c264372 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -37,6 +37,7 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Patterns;
+import android.widget.Toast;
 
 import com.google.android.collect.Lists;
 import libcore.io.Streams;
@@ -105,6 +106,13 @@
      */
     private void triggerLocalNotification(final Context context, final File bugreportFile,
             final File screenshotFile) {
+        if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+            Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+                    Toast.LENGTH_LONG).show();
+            return;
+        }
+
         boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
         if (!isPlainText) {
             // Already zipped, send it right away.
@@ -141,10 +149,12 @@
         intent.putExtra(Intent.EXTRA_TEXT, messageBody);
         final ClipData clipData = new ClipData(null, new String[] { mimeType },
                 new ClipData.Item(null, null, null, bugreportUri));
-        clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+        if (screenshotUri != null) {
+            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+            attachments.add(screenshotUri);
+        }
         intent.setClipData(clipData);
-
-        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
 
         final Account sendToAccount = findSendToAccount(context);
@@ -162,8 +172,8 @@
             File screenshotFile) {
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
-        final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
-        final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+        final Uri bugreportUri = getUri(context, bugreportFile);
+        final Uri screenshotUri = getUri(context, screenshotFile);
 
         Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
         Intent notifIntent;
@@ -272,6 +282,10 @@
         return foundAccount;
     }
 
+    private static Uri getUri(Context context, File file) {
+        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+    }
+
     private static File getFileExtra(Intent intent, String key) {
         final String path = intent.getStringExtra(key);
         if (path != null) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index be5c0fe..6fda2c6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -145,7 +145,9 @@
         android:icon="@drawable/icon"
         android:process="com.android.systemui"
         android:supportsRtl="true"
-        android:theme="@style/systemui_theme">
+        android:theme="@style/systemui_theme"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
         <!-- Keep theme in sync with SystemUIApplication.onCreate().
              Setting the theme on the application does not affect views inflated by services.
              The application theme is set again from onCreate to take effect for those views. -->
diff --git a/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
new file mode 100644
index 0000000..69edcc7
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_fast_toggle_app_home_exit.xml
@@ -0,0 +1,27 @@
+<?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.
+*/
+-->
+<!-- Recents Activity -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:duration="250"/>
+</set>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d5f9557e..00b10a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1067,6 +1067,9 @@
     <!-- Name of status bar -->
     <string name="status_bar">Status bar</string>
 
+    <!-- Name of overview -->
+    <string name="overview">Overview</string>
+
     <!-- Name of demo mode (mode with preset icons for screenshots) -->
     <string name="demo_mode">Demo mode</string>
 
@@ -1156,6 +1159,11 @@
     <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
     <string name="qs_paging" translatable="false">Use the new Quick Settings</string>
 
+    <!-- Toggles fast-toggling recents via the recents button -->
+    <string name="overview_fast_toggle_via_button">Enable fast toggle</string>
+    <!-- Description for the toggle for fast-toggling recents via the recents button -->
+    <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+
     <!-- Category in the System UI Tuner settings, where new/experimental
          settings are -->
     <string name="experimental">Experimental</string>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 5980108..585f3c7 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -21,10 +21,6 @@
     <PreferenceScreen
         android:title="@string/quick_settings">
 
-        <Preference
-            android:key="qs_tuner"
-            android:title="@string/qs_rearrange" />
-
         <PreferenceCategory
             android:title="@string/experimental">
 
@@ -87,6 +83,17 @@
 
     </PreferenceScreen>
 
+
+    <PreferenceScreen
+        android:title="@string/overview" >
+
+        <com.android.systemui.tuner.TunerSwitch
+            android:key="overview_fast_toggle"
+            android:title="@string/overview_fast_toggle_via_button"
+            android:summary="@string/overview_fast_toggle_via_button_desc" />
+
+    </PreferenceScreen>
+
     <SwitchPreference
         android:key="battery_pct"
         android:title="@string/show_battery_percentage"
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 3657cf2..9d98772 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -30,7 +30,8 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @StringDef({
-        Key.SEARCH_APP_WIDGET_ID,
+        Key.OVERVIEW_SEARCH_APP_WIDGET_ID,
+        Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
         Key.DEBUG_MODE_ENABLED,
         Key.HOTSPOT_TILE_LAST_USED,
         Key.COLOR_INVERSION_TILE_LAST_USED,
@@ -43,8 +44,8 @@
         Key.DND_FAVORITE_ZEN,
     })
     public @interface Key {
-        String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
-        String SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
+        String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+        String OVERVIEW_SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
         String DEBUG_MODE_ENABLED = "debugModeEnabled";
         String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
         String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 9a4cd93..f5f6acf 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -31,5 +31,20 @@
     /**
      * Docks the top-most task and opens recents.
      */
-    void dockTopTask();
+    void dockTopTask(boolean draggingInRecents);
+
+    /**
+     * Called during a drag-from-navbar-in gesture.
+     *
+     * @param distanceFromTop the distance of the current drag in gesture from the top of the
+     *                        screen
+     */
+    void onDraggingInRecents(float distanceFromTop);
+
+    /**
+     * Called when the gesture to drag in recents ended.
+     *
+     * @param velocity the velocity of the finger when releasing it in pixels per second
+     */
+    void onDraggingInRecentsEnded(float velocity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 949efc5..19e299254 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -48,12 +48,12 @@
             com.android.systemui.keyguard.KeyguardViewMediator.class,
             com.android.systemui.recents.Recents.class,
             com.android.systemui.volume.VolumeUI.class,
+            Divider.class,
             com.android.systemui.statusbar.SystemBars.class,
             com.android.systemui.usb.StorageNotification.class,
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
             com.android.systemui.keyboard.KeyboardUI.class,
-            Divider.class
     };
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2ea1e43..eee685f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -379,6 +379,13 @@
         @Override
         public void onDeviceProvisioned() {
             sendUserPresentBroadcast();
+            synchronized (KeyguardViewMediator.this) {
+                // If system user is provisioned, we might want to lock now to avoid showing launcher
+                if (UserManager.isSplitSystemUser()
+                        && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM) {
+                    doKeyguardLocked(null);
+                }
+            }
         }
 
         @Override
@@ -1054,7 +1061,8 @@
 
         // In split system user mode, we never unlock system user.
         if (!UserManager.isSplitSystemUser()
-                || KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM) {
+                || KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM
+                || !mUpdateMonitor.isDeviceProvisioned()) {
 
             // if the setup wizard hasn't run yet, don't show
             final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 049754e..bb2b8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -77,9 +77,9 @@
     private Record mDetailRecord;
     private Callback mCallback;
     private BrightnessController mBrightnessController;
-    private QSTileHost mHost;
+    protected QSTileHost mHost;
 
-    private QSFooter mFooter;
+    protected QSFooter mFooter;
     private boolean mGridContentVisible = true;
 
     protected LinearLayout mQsContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 5d928d6..7f45545 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -44,8 +44,8 @@
  * state update pass on tile looper.
  */
 public abstract class QSTile<TState extends State> implements Listenable {
-    protected final String TAG = "QSTile." + getClass().getSimpleName();
-    protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
+    protected final String TAG = "Tile." + getClass().getSimpleName();
+    protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
 
     protected final Host mHost;
     protected final Context mContext;
@@ -332,7 +332,7 @@
         Looper getLooper();
         Context getContext();
         Collection<QSTile<?>> getTiles();
-        void setCallback(Callback callback);
+        void addCallback(Callback callback);
         BluetoothController getBluetoothController();
         LocationController getLocationController();
         RotationLockController getRotationLockController();
@@ -453,9 +453,9 @@
     public static class State {
         public boolean visible;
         public Icon icon;
-        public String label;
-        public String contentDescription;
-        public String dualLabelContentDescription;
+        public CharSequence label;
+        public CharSequence contentDescription;
+        public CharSequence dualLabelContentDescription;
         public boolean autoMirrorDrawable = true;
 
         public boolean copyTo(State other) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
new file mode 100644
index 0000000..55f4736
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import android.os.IBinder;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+
+public class QSTileServiceWrapper implements IQSTileService {
+    private static final String TAG = "IQSTileServiceWrapper";
+
+    private final IQSTileService mService;
+    
+    public QSTileServiceWrapper(IQSTileService service) {
+        mService = service;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return mService.asBinder();
+    }
+
+    @Override
+    public void setQSTile(Tile tile) {
+        try {
+            mService.setQSTile(tile);
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onTileAdded() {
+        try {
+            mService.onTileAdded();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onTileRemoved() {
+        try {
+            mService.onTileRemoved();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onStartListening() {
+        try {
+            mService.onStartListening();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onStopListening() {
+        try {
+            mService.onStopListening();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onClick() {
+        try {
+            mService.onClick();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index cc264a0..f32cfdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -24,22 +25,16 @@
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 import android.widget.TextView;
-
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile.AnimationIcon;
-import com.android.systemui.qs.QSTile.State;
 
 import java.util.Objects;
 
@@ -227,6 +222,7 @@
         final ImageView icon = new ImageView(mContext);
         icon.setId(android.R.id.icon);
         icon.setScaleType(ScaleType.CENTER_INSIDE);
+        icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
         return icon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
new file mode 100644
index 0000000..a4ff685
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class BlankCustomTile extends QSTile<QSTile.State> {
+    public static final String PREFIX = "custom(";
+
+    private final ComponentName mComponent;
+
+    private BlankCustomTile(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 custom tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return new BlankCustomTile(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) {
+        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/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index 8866e55..422ae4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -15,16 +15,34 @@
  */
 package com.android.systemui.qs.customize;
 
+import android.app.ActivityManager;
+import android.app.Service;
 import android.content.ClipData;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.service.quicksettings.IQSTileService;
+import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.qs.tiles.CustomTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * A version of QSPanel that allows tiles to be dragged around rather than
@@ -32,8 +50,14 @@
  * and the saving/ordering is handled by the CustomQSTileHost.
  */
 public class CustomQSPanel extends QSPanel {
+    
+    private static final String TAG = "CustomQSPanel";
 
-    private CustomQSTileHost mCustomHost;
+    private List<String> mSavedTiles;
+    private ArrayList<String> mStash;
+    private List<String> mTiles = new ArrayList<>();
+
+    private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
 
     public CustomQSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -41,12 +65,9 @@
                 .inflate(R.layout.qs_customize_layout, mQsContainer, false);
         mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
         ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
-    }
+        removeView(mFooter.getView());
 
-    @Override
-    public void setHost(QSTileHost host) {
-        super.setHost(host);
-        mCustomHost = (CustomQSTileHost) host;
+        TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
     }
 
     @Override
@@ -55,17 +76,16 @@
             // No Brightness for you.
             super.onTuningChanged(key, "0");
         }
-    }
-
-    public CustomQSTileHost getCustomHost() {
-        return mCustomHost;
+        if (QSTileHost.TILES_SETTING.equals(key)) {
+            mSavedTiles = QSTileHost.loadTileSpecs(mContext, newValue);
+        }
     }
 
     public void tileSelected(QSTile<?> tile, ClipData currentClip) {
         String sourceSpec = getSpec(currentClip);
         String destSpec = tile.getTileSpec();
         if (!sourceSpec.equals(destSpec)) {
-            mCustomHost.moveTo(sourceSpec, destSpec);
+            moveTo(sourceSpec, destSpec);
         }
     }
 
@@ -79,4 +99,124 @@
     public String getSpec(ClipData data) {
         return data.getItemAt(0).getText().toString();
     }
+
+    public void setSavedTiles() {
+        setTiles(mSavedTiles);
+    }
+
+    public void saveCurrentTiles() {
+        for (int i = 0; i < mSavedTiles.size(); i++) {
+            String tileSpec = mSavedTiles.get(i);
+            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+            if (!mTiles.contains(tileSpec)) {
+                mContext.bindServiceAsUser(
+                        new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+                        new ServiceConnection() {
+                            @Override
+                            public void onServiceDisconnected(ComponentName name) {
+                            }
+
+                            @Override
+                            public void onServiceConnected(ComponentName name, IBinder service) {
+                                QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+                                        IQSTileService.Stub.asInterface(service));
+                                wrapper.onStopListening();
+                                wrapper.onTileRemoved();
+                                mContext.unbindService(this);
+                            }
+                        }, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+            }
+        }
+        for (int i = 0; i < mTiles.size(); i++) {
+            String tileSpec = mTiles.get(i);
+            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+            if (!mSavedTiles.contains(tileSpec)) {
+                mContext.bindServiceAsUser(
+                        new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+                        new ServiceConnection() {
+                            @Override
+                            public void onServiceDisconnected(ComponentName name) {
+                            }
+
+                            @Override
+                            public void onServiceConnected(ComponentName name, IBinder service) {
+                                QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+                                        IQSTileService.Stub.asInterface(service));
+                                wrapper.onTileAdded();
+                                mContext.unbindService(this);
+                            }
+                        }, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+            }
+        }
+        Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
+                TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
+    }
+
+    public void stashCurrentTiles() {
+        mStash = new ArrayList<>(mTiles);
+    }
+
+    public void unstashTiles() {
+        setTiles(mStash);
+    }
+
+    @Override
+    public void setTiles(Collection<QSTile<?>> tiles) {
+        setTilesInternal();
+    }
+
+    private void setTilesInternal() {
+        for (int i = 0; i < mCurrentTiles.size(); i++) {
+            mCurrentTiles.get(i).destroy();
+        }
+        mCurrentTiles.clear();
+        for (int i = 0; i < mTiles.size(); i++) {
+            if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
+                mCurrentTiles.add(BlankCustomTile.create(mHost, mTiles.get(i)));
+            } else {
+                mCurrentTiles.add(mHost.createTile(mTiles.get(i)));
+            }
+            mCurrentTiles.get(mCurrentTiles.size() - 1).setTileSpec(mTiles.get(i));
+        }
+        super.setTiles(mCurrentTiles);
+    }
+    
+    public void addTile(String spec) {
+        mTiles.add(spec);
+        setTilesInternal();
+    }
+
+    public void moveTo(String from, String to) {
+        int fromIndex = mTiles.indexOf(from);
+        if (fromIndex < 0) {
+            Log.e(TAG, "Unknown from tile " + from);
+            return;
+        }
+        int index = mTiles.indexOf(to);
+        if (index < 0) {
+            Log.e(TAG, "Unknown to tile " + to);
+            return;
+        }
+        mTiles.remove(fromIndex);
+        mTiles.add(index, from);
+        setTilesInternal();
+    }
+
+    public void remove(String spec) {
+        if (!mTiles.remove(spec)) {
+            Log.e(TAG, "Unknown remove spec " + spec);
+        }
+        setTilesInternal();
+    }
+
+    public void setTiles(List<String> tiles) {
+        mTiles = new ArrayList<>(tiles);
+        setTilesInternal();
+    }
+
+    public Collection<QSTile<?>> getTiles() {
+        return mCurrentTiles;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
deleted file mode 100644
index 3f85982..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ /dev/null
@@ -1,194 +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.
- */
-package com.android.systemui.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @see CustomQSPanel
- */
-public class CustomQSTileHost extends QSTileHost {
-
-    private static final String TAG = "CustomHost";
-    private List<String> mTiles;
-    private List<String> mSavedTiles;
-    private ArrayList<String> mStash;
-
-    public CustomQSTileHost(Context context, QSTileHost host) {
-        super(context, null, host.getBluetoothController(), host.getLocationController(),
-                host.getRotationLockController(), host.getNetworkController(),
-                host.getZenModeController(), host.getHotspotController(), host.getCastController(),
-                host.getFlashlightController(), host.getUserSwitcherController(),
-                host.getUserInfoController(), host.getKeyguardMonitor(),
-                new BlankSecurityController(), host.getBatteryController());
-    }
-
-    @Override
-    public QSTile<?> createTile(String tileSpec) {
-        QSTile<?> tile = super.createTile(tileSpec);
-        tile.setTileSpec(tileSpec);
-        return tile;
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        // No Tunings For You.
-        if (TILES_SETTING.equals(key)) {
-            mSavedTiles = super.loadTileSpecs(newValue);
-        }
-    }
-
-    public void setSavedTiles() {
-        setTiles(mSavedTiles);
-    }
-
-    public void saveCurrentTiles() {
-        Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
-                TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
-    }
-
-    public void stashCurrentTiles() {
-        mStash = new ArrayList<>(mTiles);
-    }
-
-    public void unstashTiles() {
-        setTiles(mStash);
-    }
-
-    public void moveTo(String from, String to) {
-        int fromIndex = mTiles.indexOf(from);
-        if (fromIndex < 0) {
-            Log.e(TAG, "Unknown from tile " + from);
-            return;
-        }
-        int index = mTiles.indexOf(to);
-        if (index < 0) {
-            Log.e(TAG, "Unknown to tile " + to);
-            return;
-        }
-        mTiles.remove(fromIndex);
-        mTiles.add(index, from);
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    public void remove(String spec) {
-        if (!mTiles.remove(spec)) {
-            Log.e(TAG, "Unknown remove spec " + spec);
-        }
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    public void setTiles(List<String> tiles) {
-        mTiles = new ArrayList<>(tiles);
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    @Override
-    protected List<String> loadTileSpecs(String tileList) {
-        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;
-        }
-        MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
-                + newTile);
-        List<String> order = new ArrayList<>(mTileSpecs);
-        int index = order.indexOf(oldTile);
-        if (index < 0) {
-            Log.e(TAG, "Can't find " + oldTile);
-            return;
-        }
-        order.remove(newTile);
-        order.add(index, newTile);
-        setTiles(order);
-    }
-
-    /**
-     * Blank so that the customizing QS view doesn't show any security messages in the footer.
-     */
-    private static class BlankSecurityController implements SecurityController {
-        @Override
-        public boolean hasDeviceOwner() {
-            return false;
-        }
-
-        @Override
-        public boolean hasProfileOwner() {
-            return false;
-        }
-
-        @Override
-        public String getDeviceOwnerName() {
-            return null;
-        }
-
-        @Override
-        public String getProfileOwnerName() {
-            return null;
-        }
-
-        @Override
-        public boolean isVpnEnabled() {
-            return false;
-        }
-
-        @Override
-        public boolean isVpnRestricted() {
-            return false;
-        }
-
-        @Override
-        public String getPrimaryVpnName() {
-            return null;
-        }
-
-        @Override
-        public String getProfileVpnName() {
-            return null;
-        }
-
-        @Override
-        public void onUserSwitched(int newUserId) {
-        }
-
-        @Override
-        public void addCallback(SecurityControllerCallback callback) {
-        }
-
-        @Override
-        public void removeCallback(SecurityControllerCallback callback) {
-        }
-    }
-}
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 1669278..d0d5b54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -83,8 +83,8 @@
         record.tileView.setVisibility(View.VISIBLE);
         record.tileView.init(null, null, null);
         record.tileView.setOnTouchListener(this);
-        if (mCurrentClip != null
-                && mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) {
+        if (mCurrentClip != null && mCurrentClip.getItemAt(0)
+                .getText().toString().equals(record.tile.getTileSpec())) {
             record.tileView.setAlpha(.3f);
             mCurrentView = record.tileView;
         }
@@ -180,7 +180,7 @@
             case MotionEvent.ACTION_DOWN:
                 // Stash the current tiles, in case the drop is on info, that we can restore
                 // the previous state.
-                mPanel.getCustomHost().stashCurrentTiles();
+                mPanel.stashCurrentTiles();
                 mCurrentView = v;
                 mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
                 View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
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 b5a885c..baad370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -22,6 +22,7 @@
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnDismissListener;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.DragEvent;
@@ -71,11 +72,11 @@
     private CustomQSPanel mQsPanel;
 
     private boolean isShown;
-    private CustomQSTileHost mHost;
     private DropButton mInfoButton;
     private DropButton mRemoveButton;
     private FloatingActionButton mFab;
     private SystemUIDialog mDialog;
+    private QSTileHost mHost;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -85,11 +86,11 @@
     }
 
     public void setHost(QSTileHost host) {
-        mHost = new CustomQSTileHost(mContext, host);
-        mHost.setCallback(this);
+        mHost = host;
+        mHost.addCallback(this);
         mQsPanel.setTiles(mHost.getTiles());
         mQsPanel.setHost(mHost);
-        mHost.setSavedTiles();
+        mQsPanel.setSavedTiles();
     }
 
     @Override
@@ -129,7 +130,7 @@
 
     public void show(int x, int y) {
         isShown = true;
-        mHost.setSavedTiles();
+        mQsPanel.setSavedTiles();
         mPhoneStatusBar.getStatusBarWindow().addView(this);
         mQsPanel.setListening(true);
         mClipper.animateCircularClip(x, y, true, this);
@@ -150,7 +151,7 @@
         for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) {
             tiles.add(tile);
         }
-        mHost.setTiles(tiles);
+        mQsPanel.setTiles(tiles);
     }
 
     private void setDragging(boolean dragging) {
@@ -158,7 +159,8 @@
     }
 
     private void save() {
-        mHost.saveCurrentTiles();
+        Log.d("CustomQSPanel", "Save!");
+        mQsPanel.saveCurrentTiles();
         // TODO: At save button.
         hide(0, 0);
     }
@@ -167,6 +169,7 @@
     public boolean onMenuItemClick(MenuItem item) {
         switch (item.getItemId()) {
             case MENU_SAVE:
+                Log.d("CustomQSPanel", "Save...");
                 save();
                 break;
             case MENU_RESET:
@@ -179,7 +182,7 @@
     @Override
     public void onTileSelected(String spec) {
         if (mDialog != null) {
-            mHost.addTile(spec);
+            mQsPanel.addTile(spec);
             mDialog.dismiss();
         }
     }
@@ -203,9 +206,9 @@
 
     public void onDrop(View v, ClipData data) {
         if (v == mRemoveButton) {
-            mHost.remove(mQsPanel.getSpec(data));
+            mQsPanel.remove(mQsPanel.getSpec(data));
         } else if (v == mInfoButton) {
-            mHost.unstashTiles();
+            mQsPanel.unstashTiles();
             SystemUIDialog dialog = new SystemUIDialog(mContext);
             dialog.setTitle(mQsPanel.getSpec(data));
             dialog.setPositiveButton(R.string.ok, null);
@@ -220,7 +223,7 @@
                     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);
+            TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
             adapter.setListener(this);
             listView.setDivider(null);
             listView.setDividerHeight(0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 579f58d..144b202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -27,6 +27,7 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
+import android.service.quicksettings.TileService;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -82,8 +83,10 @@
                     continue;
                 }
                 if (tileSpecs.contains(spec)) {
+                    Log.d(TAG, "Skipping " + spec);
                     continue;
                 }
+                Log.d(TAG, "Trying " + spec);
                 final QSTile<?> tile = host.createTile(spec);
                 // Bad, bad, very bad.
                 tile.setListening(true);
@@ -156,7 +159,7 @@
             Log.d(TAG, "Added " + mLabel);
         }
 
-        private void addTile(String spec, Drawable icon, String label) {
+        private void addTile(String spec, Drawable icon, CharSequence label) {
             TileInfo info = new TileInfo();
             info.label = label;
             info.drawable = icon;
@@ -164,7 +167,7 @@
             mTiles.add(info);
         }
 
-        private void addTile(String spec, Icon icon, String label, Context context) {
+        private void addTile(String spec, Icon icon, CharSequence label, Context context) {
             addTile(spec, icon.getDrawable(context), label);
         }
 
@@ -208,19 +211,17 @@
     private static class TileInfo {
         private String spec;
         private Drawable drawable;
-        private String label;
+        private CharSequence 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);
+            List<ResolveInfo> services = pm.queryIntentServices(
+                    new Intent(TileService.ACTION_QS_TILE), 0);
             for (ResolveInfo info : services) {
                 String packageName = info.serviceInfo.packageName;
                 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index fd70d02..7f07ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -133,7 +133,7 @@
                     R.string.accessibility_quick_settings_bluetooth_off);
         }
 
-        String bluetoothName = state.label;
+        CharSequence bluetoothName = state.label;
         if (connected) {
             bluetoothName = state.dualLabelContentDescription = mContext.getString(
                     R.string.accessibility_bluetooth_name, state.label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index e2d2ffb..a0bbbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -23,16 +23,14 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-
 import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.net.MobileDataController;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTileBaseView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
 import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
 
 /** Quick settings tile: Cellular **/
@@ -247,7 +245,7 @@
             final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
                     ? convertView
                     : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
-            final DataUsageInfo info = mDataController.getDataUsageInfo();
+            final MobileDataController.DataUsageInfo info = mDataController.getDataUsageInfo();
             if (info == null) return v;
             v.bind(info);
             return v;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index cf76ed4..b0d885a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -16,36 +16,93 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.app.ActivityManager;
+import android.app.Service;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.statusbar.phone.QSTileHost;
 
 public class CustomTile extends QSTile<QSTile.State> {
     public static final String PREFIX = "custom(";
 
-    private final ComponentName mComponent;
+    // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
+    // So instead we have a period of waiting.
+    private static final long UNBIND_DELAY = 30000;
 
-    private CustomTile(Host host, String action) {
+    private final ComponentName mComponent;
+    private final Tile mTile;
+
+    private QSTileServiceWrapper mService;
+    private boolean mListening;
+    private boolean mBound;
+
+    private CustomTile(QSTileHost host, String action) {
         super(host);
         mComponent = ComponentName.unflattenFromString(action);
+        mTile = new Tile(mComponent, host);
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            mTile.setIcon(android.graphics.drawable.Icon
+                    .createWithResource(mComponent.getPackageName(), info.icon));
+            mTile.setLabel(info.loadLabel(pm));
+        } catch (Exception e) {
+        }
     }
 
-    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);
+    public ComponentName getComponent() {
+        return mComponent;
+    }
+
+    public Tile getQsTile() {
+        return mTile;
+    }
+
+    public void updateState(Tile tile) {
+        Log.d("TileService", "Setting state " + tile.getLabel());
+        mTile.setIcon(tile.getIcon());
+        mTile.setLabel(tile.getLabel());
+        mTile.setContentDescription(tile.getContentDescription());
     }
 
     @Override
     public void setListening(boolean listening) {
+        if (mListening == listening) return;
+        mListening = listening;
+        if (listening) {
+            mHandler.removeCallbacks(mUnbind);
+            if (!mBound) {
+                // TODO: Guarantee re-bind on user-switch.
+                mContext.bindServiceAsUser(new Intent().setComponent(mComponent),
+                        mServiceConnection, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+                mBound = true;
+            }
+        } else {
+            if (mService!= null) {
+                mService.onStopListening();
+            }
+            mHandler.postDelayed(mUnbind, UNBIND_DELAY);
+        }
+    }
+    
+    @Override
+    protected void handleDestroy() {
+        super.handleDestroy();
+        mHandler.removeCallbacks(mUnbind);
+        mUnbind.run();
     }
 
     @Override
@@ -60,6 +117,11 @@
 
     @Override
     protected void handleClick() {
+        if (mService != null) {
+            mService.onClick();
+        } else {
+            Log.e(TAG, "Click with no service " + getTileSpec());
+        }
         MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
     }
 
@@ -69,16 +131,13 @@
 
     @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.visible = true;
+        state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+        state.label = mTile.getLabel();
+        if (mTile.getContentDescription() != null) {
+            state.contentDescription = mTile.getContentDescription();
+        } else {
             state.contentDescription = state.label;
-        } catch (Exception e) {
-            state.visible = false;
         }
     }
 
@@ -86,4 +145,48 @@
     public int getMetricsCategory() {
         return MetricsLogger.QS_INTENT;
     }
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = new QSTileServiceWrapper(IQSTileService.Stub.asInterface(service));
+            if (mListening) {
+                mService.setQSTile(mTile);
+                mService.onStartListening();
+            } else {
+                mService.onStopListening();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    };
+
+    private final Runnable mUnbind = new Runnable() {
+        @Override
+        public void run() {
+            mContext.unbindService(mServiceConnection);
+            mBound = false;
+        }
+    };
+
+    public static ComponentName getComponentFromSpec(String spec) {
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(QSTileHost host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return new CustomTile(host, action);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
index d0ae383..d814b1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -23,11 +23,10 @@
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
-
+import com.android.settingslib.net.MobileDataController;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.DataUsageGraph;
-import com.android.systemui.statusbar.policy.NetworkController;
 
 import java.text.DecimalFormat;
 
@@ -60,7 +59,7 @@
                 R.dimen.qs_data_usage_text_size);
     }
 
-    public void bind(NetworkController.MobileDataController.DataUsageInfo info) {
+    public void bind(MobileDataController.DataUsageInfo info) {
         final Resources res = mContext.getResources();
         final int titleId;
         final long bytes;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 3763618..7f4442a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -166,7 +166,7 @@
         state.contentDescription = mContext.getString(
                 R.string.accessibility_quick_settings_wifi,
                 signalContentDescription);
-        String wifiName = state.label;
+        CharSequence wifiName = state.label;
         if (state.connected) {
             wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a429447..c08fb05 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,34 +29,4 @@
         public static final int DismissSourceHeaderButton = 2;
     }
 
-    // TODO: Move into RecentsDebugFlags
-    public static class DebugFlags {
-
-        public static class App {
-            // Enables debug drawing for the transition thumbnail
-            public static final boolean EnableTransitionThumbnailDebugMode = false;
-            // Enables the filtering of tasks according to their grouping
-            public static final boolean EnableTaskFiltering = false;
-            // Enables dismiss-all
-            public static final boolean EnableDismissAll = false;
-            // Enables fast-toggling by just tapping on the recents button
-            public static final boolean EnableFastToggleRecents = 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
-            public static final boolean EnableSimulatedTaskGroups = false;
-            // Defines the number of mock task affiliations per group
-            public static final int TaskAffiliationsGroupCount = 12;
-            // Enables us to create mock recents tasks
-            public static final boolean EnableSystemServicesProxy = false;
-            // Defines the number of mock recents packages to create
-            public static final int SystemServicesProxyMockPackageCount = 3;
-            // Defines the number of mock recents tasks to create
-            public static final int SystemServicesProxyMockTaskCount = 100;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
index f187178..8ae8c53 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.events.activity;
+package com.android.systemui.recents;
 
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * Event sent when the exit animation is started.
+ *
+ * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example
+ * of that is hiding the tasks when the launched application window becomes visible.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
-    // Simple event
+public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 79eca30d..7cfe38e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -24,8 +24,10 @@
 oneway interface IRecentsNonSystemUserCallbacks {
     void preloadRecents();
     void cancelPreloadingRecents();
-    void showRecents(boolean triggeredFromAltTab);
+    void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents);
     void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecents();
     void onConfigurationChanged();
+    void onDraggingInRecents(float distanceFromTop);
+    void onDraggingInRecentsEnded(float velocity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a58bc58..348bd87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
@@ -61,6 +62,7 @@
     private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
 
     private static SystemServicesProxy sSystemServicesProxy;
+    private static RecentsDebugFlags sDebugFlags;
     private static RecentsTaskLoader sTaskLoader;
     private static RecentsConfiguration sConfiguration;
 
@@ -71,6 +73,7 @@
 
     private Handler mHandler;
     private RecentsImpl mImpl;
+    private int mDraggingInRecentsCurrentUser;
 
     // Only For system user, this is the callbacks instance we return to each secondary user
     private RecentsSystemUser mSystemUserCallbacks;
@@ -147,8 +150,13 @@
         return sConfiguration;
     }
 
+    public static RecentsDebugFlags getDebugFlags() {
+        return sDebugFlags;
+    }
+
     @Override
     public void start() {
+        sDebugFlags = new RecentsDebugFlags(mContext);
         sSystemServicesProxy = new SystemServicesProxy(mContext);
         sTaskLoader = new RecentsTaskLoader(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
@@ -165,6 +173,7 @@
 
         // Register with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+        EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
         EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
 
         // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
@@ -193,20 +202,26 @@
      */
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
             return;
         }
 
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.showRecents(triggeredFromAltTab);
+            mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
         } else {
             if (mSystemUserCallbacks != null) {
                 IRecentsNonSystemUserCallbacks callbacks =
                         mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
                 if (callbacks != null) {
                     try {
-                        callbacks.showRecents(triggeredFromAltTab);
+                        callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Callback failed", e);
                     }
@@ -222,6 +237,12 @@
      */
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
             return;
         }
@@ -251,6 +272,12 @@
      */
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
             return;
         }
@@ -280,6 +307,12 @@
      */
     @Override
     public void preloadRecents() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.preloadRecents();
@@ -302,6 +335,12 @@
 
     @Override
     public void cancelPreloadingRecents() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.cancelPreloadingRecents();
@@ -323,17 +362,78 @@
     }
 
     @Override
-    public void dockTopTask() {
-        mImpl.dockTopTask();
+    public void dockTopTask(boolean draggingInRecents) {
+        mImpl.dockTopTask(draggingInRecents);
+        if (draggingInRecents) {
+            mDraggingInRecentsCurrentUser = sSystemServicesProxy.getCurrentUser();
+        }
+    }
+
+    @Override
+    public void onDraggingInRecents(float distanceFromTop) {
+        if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+            mImpl.onDraggingInRecents(distanceFromTop);
+        } else {
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+                                mDraggingInRecentsCurrentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onDraggingInRecents(distanceFromTop);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: "
+                            + mDraggingInRecentsCurrentUser);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onDraggingInRecentsEnded(float velocity) {
+        if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+            mImpl.onDraggingInRecentsEnded(velocity);
+        } else {
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+                                mDraggingInRecentsCurrentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onDraggingInRecentsEnded(velocity);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: "
+                            + mDraggingInRecentsCurrentUser);
+                }
+            }
+        }
     }
 
     @Override
     public void showNextAffiliatedTask() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         mImpl.showNextAffiliatedTask();
     }
 
     @Override
     public void showPrevAffiliatedTask() {
+        // Ensure the device has been provisioned before allowing the user to interact with
+        // recents
+        if (!isDeviceProvisioned()) {
+            return;
+        }
+
         mImpl.showPrevAffiliatedTask();
     }
 
@@ -386,8 +486,8 @@
      * Handle screen pinning request.
      */
     public final void onBusEvent(final ScreenPinningRequestEvent event) {
-        int processUser = event.systemServicesProxy.getProcessUser();
-        if (event.systemServicesProxy.isSystemUser(processUser)) {
+        int processUser = sSystemServicesProxy.getProcessUser();
+        if (sSystemServicesProxy.isSystemUser(processUser)) {
             mImpl.onStartScreenPinning(event.applicationContext);
         } else {
             postToSystemUser(new Runnable() {
@@ -456,6 +556,14 @@
     }
 
     /**
+     * @return whether this device is provisioned.
+     */
+    private boolean isDeviceProvisioned() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+    }
+
+    /**
      * Attempts to proxy the following action to the override recents package.
      * @return whether the proxying was successful
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 6874247..4e24d54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -37,15 +37,19 @@
 import android.view.View;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import com.android.internal.logging.MetricsLogger;
 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.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -108,34 +112,38 @@
     DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
         @Override
         public void run() {
-            boolean dismissed = dismissRecentsToFocusedTask(false);
+            dismissRecentsToFocusedTask(false);
         }
     });
 
     /**
-     * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
-     * launching Home with some ActivityOptions.  Generally we always launch home when we exit
-     * Recents rather than just finishing the activity since we don't know what is behind Recents in
-     * the task stack.  The only case where we finish() directly is when we are cancelling the full
-     * screen transition from the app.
+     * A common Runnable to finish Recents by launching Home with an animation depending on the
+     * last activity launch state.  Generally we always launch home when we exit Recents rather than
+     * just finishing the activity since we don't know what is behind Recents in the task stack.
      */
     class FinishRecentsRunnable implements Runnable {
         Intent mLaunchIntent;
-        ActivityOptions mLaunchOpts;
 
         /**
-         * Creates a finish runnable that starts the specified intent, using the given
-         * ActivityOptions.
+         * Creates a finish runnable that starts the specified intent.
          */
-        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
+        public FinishRecentsRunnable(Intent launchIntent) {
             mLaunchIntent = launchIntent;
-            mLaunchOpts = opts;
         }
 
         @Override
         public void run() {
             try {
-                startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
+                RecentsActivityLaunchState launchState =
+                        Recents.getConfiguration().getLaunchState();
+                ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
+                        launchState.launchedFromSearchHome ?
+                                R.anim.recents_to_search_launcher_enter :
+                                R.anim.recents_to_launcher_enter,
+                        launchState.launchedFromSearchHome ?
+                                R.anim.recents_to_search_launcher_exit :
+                                R.anim.recents_to_launcher_exit);
+                startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
             } catch (Exception e) {
                 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
             }
@@ -189,18 +197,6 @@
             mRecentsView.setTaskStack(stack);
         }
 
-        // Create the home intent runnable
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
-            ActivityOptions.makeCustomAnimation(this,
-                    launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
-                        R.anim.recents_to_launcher_enter,
-                    launchState.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
-                        R.anim.recents_to_launcher_exit));
-
         // Mark the task that is the launch target
         int launchTaskIndexInStack = 0;
         if (launchState.launchedToTaskId != -1) {
@@ -222,14 +218,14 @@
                 mEmptyView = mEmptyViewStub.inflate();
             }
             mEmptyView.setVisibility(View.VISIBLE);
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 mRecentsView.setSearchBarVisibility(View.GONE);
             }
         } else {
             if (mEmptyView != null) {
                 mEmptyView.setVisibility(View.GONE);
             }
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 if (mRecentsView.hasValidSearchBar()) {
                     mRecentsView.setSearchBarVisibility(View.VISIBLE);
                 } else {
@@ -343,7 +339,7 @@
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
         // Initialize the widget host (the host id is static and does not change)
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
         }
         mPackageMonitor = new RecentsPackageMonitor();
@@ -358,16 +354,25 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
         mScrimViews = new SystemBarScrimViews(this);
+        getWindow().getAttributes().privateFlags |=
+                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
+        // Create the home intent runnable
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
 
         // Bind the search app widget when we first start up
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.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);
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
         }
         registerReceiver(mSystemBroadcastReceiver, filter);
@@ -394,7 +399,7 @@
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
                 !launchState.launchedFromAppWithThumbnail;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
-            EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
+            EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
         }
 
         if (!launchState.launchedHasConfigurationChanged) {
@@ -416,19 +421,23 @@
         if (state.startHidden) {
             state.startHidden = false;
             mRecentsView.setStackViewVisibility(View.INVISIBLE);
+        } else {
+            mRecentsView.setStackViewVisibility(View.VISIBLE);
         }
     }
 
     @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+    }
+
+    @Override
     protected void onPause() {
         super.onPause();
 
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
-            // Stop the fast-toggle dozer
-            mIterateTrigger.stopDozing();
-        }
-
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled()) {
             // Stop the fast-toggle dozer
             mIterateTrigger.stopDozing();
         }
@@ -473,7 +482,7 @@
         mPackageMonitor.unregister();
 
         // Stop listening for widget package changes if there was one bound
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             mAppWidgetHost.stopListening();
         }
 
@@ -569,12 +578,6 @@
     /**** RecentsView.RecentsViewCallbacks Implementation ****/
 
     @Override
-    public void onTaskLaunchFailed() {
-        // Return to Home
-        dismissRecentsToHome(true);
-    }
-
-    @Override
     public void onAllTaskViewsDismissed() {
         mFinishLaunchHomeRunnable.run();
     }
@@ -607,13 +610,13 @@
         }
     }
 
-    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
+    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent 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);
         ctx.postAnimationTrigger.increment();
         if (mSearchWidgetInfo != null) {
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                     @Override
                     public void run() {
@@ -632,8 +635,8 @@
                 // the dozer now
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
-                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
-                        !launchState.launchedWithAltTab) {
+                RecentsDebugFlags flags = Recents.getDebugFlags();
+                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
                     mIterateTrigger.startDozing();
                 }
             }
@@ -647,6 +650,11 @@
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
     }
 
+    public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
+        mRecentsView.setStackViewVisibility(View.INVISIBLE);
+        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+    }
+
     public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         int launchToTaskId = launchState.launchedToTaskId;
@@ -701,10 +709,26 @@
         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
     }
 
+    public final void onBusEvent(LaunchTaskSucceededEvent event) {
+        MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
+    }
+
+    public final void onBusEvent(LaunchTaskFailedEvent event) {
+        // Return to Home
+        dismissRecentsToHome(true);
+
+        MetricsLogger.count(this, "overview_task_launch_failed", 1);
+    }
+
     public final void onBusEvent(ScreenPinningRequestEvent event) {
         MetricsLogger.count(this, "overview_screen_pinned", 1);
     }
 
+    public final void onBusEvent(DebugFlagsChangedEvent event) {
+        // Just finish recents so that we can reload the flags anew on the next instantiation
+        finish();
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
             SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 01ffd2a..7f7dbce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -74,7 +74,8 @@
      * Returns the task to focus given the current launch state.
      */
     public int getInitialFocusTaskIndex(int numTasks) {
-        if (Constants.DebugFlags.App.EnableFastToggleRecents && !launchedWithAltTab) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
             // If we are fast toggling, then focus the next task depending on when you are on home
             // or coming in from another app
             if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index a73f323..8f952be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -154,7 +154,7 @@
             int swInset = getInsetToSmallestWidth(windowBounds.right - windowBounds.left);
             int top = searchBarBounds.isEmpty() ? topInset : 0;
             taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom + top,
-                    windowBounds.right - swInset, windowBounds.bottom);
+                    windowBounds.right - swInset - rightInset, windowBounds.bottom);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
new file mode 100644
index 0000000..6c74a4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -0,0 +1,80 @@
+/*
+ * 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.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
+import com.android.systemui.tuner.TunerService;
+
+/**
+ * Tunable debug flags
+ */
+public class RecentsDebugFlags implements TunerService.Tunable {
+
+    private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+
+    public static class Static {
+        // Enables debug drawing for the transition thumbnail
+        public static final boolean EnableTransitionThumbnailDebugMode = 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
+        public static final boolean EnableSimulatedTaskGroups = false;
+        // Defines the number of mock task affiliations per group
+        public static final int TaskAffiliationsGroupCount = 12;
+        // Enables us to create mock recents tasks
+        public static final boolean EnableSystemServicesProxy = false;
+        // Defines the number of mock recents packages to create
+        public static final int SystemServicesProxyMockPackageCount = 3;
+        // Defines the number of mock recents tasks to create
+        public static final int SystemServicesProxyMockTaskCount = 100;
+    }
+
+    private boolean mForceEnableFreeformWorkspace;
+    private boolean mEnableFastToggleRecents;
+
+    /**
+     * We read the prefs once when we start the activity, then update them as the tuner changes
+     * the flags.
+     */
+    public RecentsDebugFlags(Context context) {
+        // Register all our flags, this will also call onTuningChanged() for each key, which will
+        // initialize the current state of each flag
+        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+    }
+
+    /**
+     * @return whether we are enabling fast toggling.
+     */
+    public boolean isFastToggleRecentsEnabled() {
+        return mEnableFastToggleRecents;
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        switch (key) {
+            case KEY_FAST_TOGGLE:
+                mEnableFastToggleRecents = (newValue != null) &&
+                        (Integer.parseInt(newValue) != 0);
+                break;
+        }
+        EventBus.getDefault().send(new DebugFlagsChangedEvent());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index db65e00..618eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -40,13 +40,14 @@
 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.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 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.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -69,8 +70,8 @@
  * 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, ActivityOptions.OnAnimationFinishedListener {
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
+        ActivityOptions.OnAnimationFinishedListener {
 
     private final static String TAG = "RecentsImpl";
     private final static boolean DEBUG = false;
@@ -140,8 +141,8 @@
     TaskStackListenerImpl mTaskStackListener;
     RecentsAppWidgetHost mAppWidgetHost;
     boolean mBootCompleted;
-    boolean mStartAnimationTriggered;
     boolean mCanReuseTaskStackViews = true;
+    boolean mDraggingInRecents;
 
     // Task launching
     Rect mSearchBarBounds = new Rect();
@@ -166,14 +167,13 @@
         public void run() {
             // When this fires, then the user has not released alt-tab for at least
             // FAST_ALT_TAB_DELAY_MS milliseconds
-            showRecents(mTriggeredFromAltTab);
+            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */);
         }
     });
 
     Bitmap mThumbnailTransitionBitmapCache;
     Task mThumbnailTransitionBitmapCacheKey;
 
-
     public RecentsImpl(Context context) {
         mContext = context;
         mHandler = new Handler();
@@ -250,8 +250,9 @@
     }
 
     @Override
-    public void showRecents(boolean triggeredFromAltTab) {
+    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents) {
         mTriggeredFromAltTab = triggeredFromAltTab;
+        mDraggingInRecents = draggingInRecents;
         if (mFastAltTabTrigger.hasTriggered()) {
             // We are calling this from the doze trigger, so just fall through to show Recents
             mFastAltTabTrigger.resetTrigger();
@@ -317,6 +318,7 @@
             return;
         }
 
+        mDraggingInRecents = false;
         mTriggeredFromAltTab = false;
 
         try {
@@ -326,8 +328,8 @@
             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
-                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
-                        !launchState.launchedWithAltTab) {
+                RecentsDebugFlags flags = Recents.getDebugFlags();
+                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
                     // Notify recents to move onto the next task
                     EventBus.getDefault().post(new IterateRecentsEvent());
                 } else {
@@ -387,6 +389,16 @@
         // Do nothing
     }
 
+    @Override
+    public void onDraggingInRecents(float distanceFromTop) {
+        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
+    }
+
+    @Override
+    public void onDraggingInRecentsEnded(float velocity) {
+        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
+    }
+
     /**
      * Transitions to the next recent task in the stack.
      */
@@ -403,17 +415,22 @@
         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
         // Return early if there is no running task
         if (runningTask == null) return;
-        // Return early if the running task is in the home stack (optimization)
-        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
 
         // Find the task in the recents list
+        boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
         ArrayList<Task> tasks = focusedStack.getTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 1; i--) {
             Task task = tasks.get(i);
-            if (task.key.id == runningTask.id) {
+            if (isTopTaskHome) {
+                toTask = tasks.get(i - 1);
+                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                        R.anim.recents_launch_next_affiliated_task_target,
+                        R.anim.recents_fast_toggle_app_home_exit);
+                break;
+            } else if (task.key.id == runningTask.id) {
                 toTask = tasks.get(i - 1);
                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_launch_prev_affiliated_task_target,
@@ -503,12 +520,7 @@
         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);
-        }
+        ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
     }
 
     public void showNextAffiliatedTask() {
@@ -523,13 +535,13 @@
         showRelativeAffiliatedTask(false);
     }
 
-    public void dockTopTask() {
+    public void dockTopTask(boolean draggingInRecents) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
         if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
             ssp.startTaskInDockedMode(topTask.id,
                     ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
-            showRecents(false /* triggeredFromAltTab */);
+            showRecents(false /* triggeredFromAltTab */, draggingInRecents);
         }
     }
 
@@ -557,7 +569,7 @@
         // Update the configuration for the current state
         config.update(mContext, ssp, ssp.getWindowRect());
 
-        if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
+        if (!RecentsDebugFlags.Static.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
@@ -618,7 +630,7 @@
         final Task toTask = new Task();
         final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
                 topTask.id, toTask);
-        ForegroundThread.getHandler().post(new Runnable() {
+        ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
                 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
@@ -640,7 +652,7 @@
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit,
-                mHandler, this);
+                mHandler, null);
     }
 
     /**
@@ -651,12 +663,12 @@
             return ActivityOptions.makeCustomAnimation(mContext,
                     R.anim.recents_from_search_launcher_enter,
                     R.anim.recents_from_search_launcher_exit,
-                    mHandler, this);
+                    mHandler, null);
         }
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit,
-                mHandler, this);
+                mHandler, null);
     }
 
     /**
@@ -682,7 +694,7 @@
             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
             specs.toArray(specsArray);
             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, this, this);
+                    specsArray, mHandler, null, this);
         } else {
             // Update the destination rect
             Task toTask = new Task();
@@ -693,7 +705,7 @@
             if (thumbnail != null) {
                 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
                         thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
-                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
             }
             // If both the screenshot and thumbnail fails, then just fall back to the default transition
             return getUnknownTransitionActivityOptions();
@@ -760,7 +772,7 @@
                 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
                 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
                         Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
                     thumbnail.eraseColor(0xFFff0000);
                 } else {
                     Canvas c = new Canvas(thumbnail);
@@ -818,11 +830,11 @@
         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) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) {
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 String homeActivityPackage = ssp.getHomeActivityPackageName();
                 String searchWidgetPackage = Prefs.getString(mContext,
-                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+                        Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
 
                 // Determine whether we are coming from a search owned home activity
                 boolean fromSearchHome = (homeActivityPackage != null) &&
@@ -846,8 +858,6 @@
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
               ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
               TaskStackLayoutAlgorithm.VisibilityReport vr) {
-        mStartAnimationTriggered = false;
-
         // Update the configuration based on the launch options
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
@@ -860,7 +870,8 @@
         launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
         launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
         launchState.launchedHasConfigurationChanged = false;
-        launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID;
+        launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
+                || mDraggingInRecents;
 
         Intent intent = new Intent();
         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
@@ -875,16 +886,7 @@
         mCanReuseTaskStackViews = true;
     }
 
-    /**** OnAnimationStartedListener Implementation ****/
-
-    @Override
-    public void onAnimationStarted() {
-        // Notify recents to start the enter animation
-        if (!mStartAnimationTriggered) {
-            mStartAnimationTriggered = true;
-            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
-        }
-    }
+    /**** OnAnimationFinishedListener Implementation ****/
 
     @Override
     public void onAnimationFinished() {
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 b091f05..d4d13f0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -502,6 +502,19 @@
         queueEvent(event);
     }
 
+    /**
+     * If this method is called from the main thread, it will be handled directly. If this method
+     * is not called from the main thread, it will be posted onto the main thread.
+     */
+    public void sendOntoMainThread(Event event) {
+        long callingThreadId = Thread.currentThread().getId();
+        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+            post(event);
+        } else {
+            send(event);
+        }
+    }
+
     /** Prevent post()ing an InterprocessEvent */
     @Deprecated
     public void post(InterprocessEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
index f187178..fe3bf26 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
@@ -19,8 +19,8 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * This is sent when the SystemUI tuner changes a flag.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+public class DebugFlagsChangedEvent extends EventBus.Event {
     // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
index f187178..b31f320 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
@@ -19,8 +19,10 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * This is sent when the window animation into Recents completes.  We use this signal to know when
+ * we can start in-app animations so that they don't conflict with the window transition into
+ * Recents.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+public class EnterRecentsWindowAnimationCompletedEvent extends EventBus.Event {
     // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
similarity index 84%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
index f187178..3a2d58c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
@@ -19,8 +19,8 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * This is sent when we fail to launch a task.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+public class LaunchTaskFailedEvent extends EventBus.Event {
     // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
similarity index 71%
copy from packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
index f187178..ec5089f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
@@ -19,8 +19,13 @@
 import com.android.systemui.recents.events.EventBus;
 
 /**
- * This is sent when the window animation into Recents starts.
+ * This is sent when we successfully launch a task.
  */
-public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
-    // Simple event
+public class LaunchTaskSucceededEvent extends EventBus.Event {
+
+    public final int taskIndexFromStackFront;
+
+    public LaunchTaskSucceededEvent(int taskIndexFromStackFront) {
+        this.taskIndexFromStackFront = taskIndexFromStackFront;
+    }
 }
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
index 5cb4ccf..9d96d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -26,10 +26,8 @@
 public class ScreenPinningRequestEvent extends EventBus.Event {
 
     public final Context applicationContext;
-    public final SystemServicesProxy systemServicesProxy;
 
-    public ScreenPinningRequestEvent(Context context, SystemServicesProxy systemServicesProxy) {
+    public ScreenPinningRequestEvent(Context context) {
         this.applicationContext = context.getApplicationContext();
-        this.systemServicesProxy = systemServicesProxy;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
new file mode 100644
index 0000000..9be8eb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user finished dragging in recents.
+ */
+public class DraggingInRecentsEndedEvent extends Event {
+
+    public final float velocity;
+
+    public DraggingInRecentsEndedEvent(float velocity) {
+        this.velocity = velocity;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
new file mode 100644
index 0000000..5e8bfd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user changed how far they are dragging in recents.
+ */
+public class DraggingInRecentsEvent extends Event {
+
+    public final float distanceFromTop;
+
+    public DraggingInRecentsEvent(float distanceFromTop) {
+        this.distanceFromTop = distanceFromTop;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
index 8dc2983..784ac4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
@@ -28,7 +28,7 @@
     private static Handler sHandler;
 
     private ForegroundThread() {
-        super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        super("recents.fg");
     }
 
     private static void ensureThreadLocked() {
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 0432ac9..16d6929 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -63,7 +63,8 @@
 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.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
 
 import java.io.IOException;
@@ -113,6 +114,7 @@
 
     /** Private constructor */
     public SystemServicesProxy(Context context) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
         mAccm = AccessibilityManager.getInstance(context);
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManagerNative.getDefault();
@@ -124,8 +126,8 @@
         mUm = UserManager.get(context);
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mHasFreeformWorkspaceSupport = false && mPm.hasSystemFeature(
-                PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+        mHasFreeformWorkspaceSupport = false &&
+                mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -143,7 +145,7 @@
         // Resolve the assist intent
         mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
 
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             // Create a dummy icon
             mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
             mDummyIcon.eraseColor(0xFF999999);
@@ -156,13 +158,13 @@
         if (mAm == null) return null;
 
         // If we are mocking, then create some recent tasks
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             ArrayList<ActivityManager.RecentTaskInfo> tasks =
                     new ArrayList<ActivityManager.RecentTaskInfo>();
-            int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount);
+            int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.SystemServicesProxyMockTaskCount);
             for (int i = 0; i < count; i++) {
                 // Create a dummy component name
-                int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
+                int packageIndex = i % RecentsDebugFlags.Static.SystemServicesProxyMockPackageCount;
                 ComponentName cn = new ComponentName("com.android.test" + packageIndex,
                         "com.android.test" + i + ".Activity");
                 String description = "" + i + " - " +
@@ -381,7 +383,7 @@
         if (mAm == null) return null;
 
         // If we are mocking, then just return a dummy thumbnail
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
                     Bitmap.Config.ARGB_8888);
             thumbnail.eraseColor(0xff333333);
@@ -443,7 +445,7 @@
     /** Moves a task to the front with the specified activity options. */
     public void moveTaskToFront(int taskId, ActivityOptions opts) {
         if (mAm == null) return;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
 
         if (opts != null) {
             mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME,
@@ -456,7 +458,7 @@
     /** Removes the task */
     public void removeTask(final int taskId) {
         if (mAm == null) return;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
 
         // Remove the task.
         BackgroundThread.getHandler().post(new Runnable() {
@@ -475,7 +477,7 @@
      */
     public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
         if (mIpm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
 
         try {
             return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
@@ -492,7 +494,7 @@
      */
     public ActivityInfo getActivityInfo(ComponentName cn) {
         if (mPm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
 
         try {
             return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
@@ -507,7 +509,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return "Recent Task";
         }
 
@@ -519,7 +521,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return "Recent Task";
         }
 
@@ -549,7 +551,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return new ColorDrawable(0xFF666666);
         }
 
@@ -580,7 +582,7 @@
     /** Returns the package name of the home activity. */
     public String getHomeActivityPackageName() {
         if (mPm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return null;
 
         ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
         ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
@@ -623,22 +625,22 @@
      * Returns the current search widget id.
      */
     public int getSearchAppWidgetId(Context context) {
-        return Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+        return Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
     }
 
     /**
      * Returns the current search widget info, binding a new one if necessary.
      */
     public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
-        int searchWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+        int searchWidgetId = Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
         AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
         AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
 
         // Return the search widget info if it hasn't changed
         if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
                 searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
-            if (Prefs.getString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null) == null) {
-                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+            if (Prefs.getString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null) == null) {
+                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
                         searchWidgetInfo.provider.getPackageName());
             }
             return searchWidgetInfo;
@@ -654,16 +656,16 @@
             Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
                     resolvedSearchWidgetInfo);
             if (widgetInfo != null) {
-                Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, widgetInfo.first);
-                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+                Prefs.putInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, widgetInfo.first);
+                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
                         widgetInfo.second.provider.getPackageName());
                 return widgetInfo.second;
             }
         }
 
         // If we fall through here, then there is no resolved search widget, so clear the state
-        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_ID);
-        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE);
+        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID);
+        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE);
         return null;
     }
 
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 62493d6..7b04493 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -130,10 +130,9 @@
                     ? t.taskDescription.getIconFilename() : null;
 
             // Add the task to the stack
-            Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
-                    t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
-                    activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
-                    iconFilename, t.bounds);
+            Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel,
+                    contentDescription, activityIcon, activityColor, (i == (taskCount - 1)),
+                    config.lockToAppEnabled, icon, iconFilename, t.bounds);
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
             if (DEBUG) {
                 Log.d(TAG, activityLabel + " bounds: " + t.bounds);
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 bba453a..965e7a67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -30,9 +30,9 @@
 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.RecentsDebugFlags;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
@@ -239,7 +239,8 @@
             SystemServicesProxy ssp, Resources res) {
         Bitmap tdIcon = iconBitmap != null
                 ? iconBitmap
-                : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename);
+                : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename,
+                        taskKey.userId);
         if (tdIcon != null) {
             return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId);
         }
@@ -283,9 +284,9 @@
                 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 :
+        int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
-        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+        int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
         // Create the default assets
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 12bd556b..67e18f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -103,7 +103,6 @@
     public int colorPrimary;
     public boolean useLightOnPrimaryColor;
     public Bitmap thumbnail;
-    public boolean isActive;
     public boolean lockToThisTask;
     public boolean lockToTaskEnabled;
     public Bitmap icon;
@@ -116,7 +115,7 @@
         // Do nothing
     }
 
-    public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
+    public Task(TaskKey key, int taskAffiliation, int taskAffiliationColor,
                 String activityTitle, String contentDescription, Drawable activityIcon,
                 int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
                 String iconFilename, Rect bounds) {
@@ -131,7 +130,6 @@
         this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary;
         this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
                 Color.WHITE) > 3f;
-        this.isActive = isActive;
         this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
         this.lockToTaskEnabled = lockToTaskEnabled;
         this.icon = icon;
@@ -149,7 +147,6 @@
         this.activityIcon = o.activityIcon;
         this.colorPrimary = o.colorPrimary;
         this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
-        this.isActive = o.isActive;
         this.lockToThisTask = o.lockToThisTask;
         this.lockToTaskEnabled = o.lockToTaskEnabled;
     }
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 6734012..f26dcde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -24,8 +24,8 @@
 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.RecentsDebugFlags;
 import com.android.systemui.recents.misc.NamedCounter;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -543,7 +543,7 @@
      * Temporary: This method will simulate affiliation groups by
      */
     public void createAffiliatedGroupings(Context context) {
-        if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
+        if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) {
             HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
             // Sort all tasks by increasing firstActiveTime of the task
             ArrayList<Task> tasks = mTaskList.getTasks();
@@ -559,7 +559,7 @@
             String prevPackage = "";
             int prevAffiliation = -1;
             Random r = new Random();
-            int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+            int groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
                 String packageName = t.key.getComponent().getPackageName();
@@ -574,7 +574,7 @@
                     addGroup(group);
                     prevAffiliation = affiliation;
                     prevPackage = packageName;
-                    groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+                    groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
                 }
                 group.addTask(t);
                 taskMap.put(t.key, t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 21b6bd8..70370ec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -36,7 +36,7 @@
     public AnimateableViewBounds(TaskView source, int cornerRadius) {
         mSourceView = source;
         mCornerRadius = cornerRadius;
-        setClipBottom(getClipBottom());
+        setClipBottom(getClipBottom(), false /* force */);
     }
 
     @Override
@@ -57,8 +57,8 @@
     }
 
     /** Sets the bottom clip. */
-    public void setClipBottom(int bottom) {
-        if (bottom != mClipRect.bottom) {
+    public void setClipBottom(int bottom, boolean force) {
+        if (bottom != mClipRect.bottom || force) {
             mClipRect.bottom = bottom;
             mSourceView.invalidateOutline();
             updateClipBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
new file mode 100644
index 0000000..f3c66a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -0,0 +1,339 @@
+/*
+ * 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.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.WindowManagerGlobal;
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+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.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
+/**
+ * A helper class to create transitions to/from Recents
+ */
+public class RecentsTransitionHelper {
+
+    private static final String TAG = "RecentsTransitionHelper";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
+     * waiting for the specs to be retrieved.
+     */
+    private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
+
+    @GuardedBy("this")
+    private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
+
+    private Context mContext;
+    private Handler mHandler;
+    private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+    private Runnable mStartScreenPinningRunnable = new Runnable() {
+        @Override
+        public void run() {
+            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext));
+        }
+    };
+
+    public RecentsTransitionHelper(Context context, Handler handler) {
+        mContext = context;
+        mHandler = handler;
+    }
+
+    /**
+     * Launches the specified {@link Task}.
+     */
+    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+            final TaskStackView stackView, final TaskView taskView,
+            final boolean lockToTask, final Rect bounds, int destinationStack) {
+        final ActivityOptions opts = ActivityOptions.makeBasic();
+        if (bounds != null) {
+            opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
+        }
+
+        final ActivityOptions.OnAnimationStartedListener animStartedListener;
+        final IAppTransitionAnimationSpecsFuture transitionFuture;
+        if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
+                task.thumbnail.getHeight() > 0) {
+            transitionFuture = getAppTransitionFuture(task, stackView, destinationStack);
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+
+                    if (lockToTask) {
+                        // Request screen pinning after the animation runs
+                        mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+                    }
+                }
+            };
+        } else {
+            // This is only the case if the task is not on screen (scrolled offscreen for example)
+            transitionFuture = null;
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                }
+            };
+        }
+
+        if (taskView == null) {
+            // If there is no task view, then we do not need to worry about animating out occluding
+            // task views, and we can launch immediately
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener);
+        } else {
+            if (task.group != null && !task.group.isFrontMostTask(task)) {
+                stackView.startLaunchTaskAnimation(taskView, new Runnable() {
+                    @Override
+                    public void run() {
+                        startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                                animStartedListener);
+                    }
+                }, lockToTask);
+            } else {
+                stackView.startLaunchTaskAnimation(taskView, null, lockToTask);
+                startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                        animStartedListener);
+            }
+        }
+    }
+
+    /**
+     * Starts the activity for the launch task.
+     *
+     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+     *                 we are toggling recents and the launch-to task is now offscreen.
+     */
+    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+            ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture,
+            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp.startActivityFromRecents(mContext, task.key.id, task.activityLabel, opts)) {
+            // Keep track of the index of the task launch
+            int taskIndexFromFront = 0;
+            int taskIndex = stack.indexOfTask(task);
+            if (taskIndex > -1) {
+                taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+            }
+            EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
+        } else {
+            // Dismiss the task if we fail to launch it
+            EventBus.getDefault().send(new DismissTaskViewEvent(task, taskView));
+
+            // Keep track of failed launches
+            EventBus.getDefault().send(new LaunchTaskFailedEvent());
+        }
+        if (transitionFuture != null) {
+            IRemoteCallback.Stub callback = null;
+            if (animStartedListener != null) {
+                callback = new IRemoteCallback.Stub() {
+                    @Override
+                    public void sendResult(Bundle data) throws RemoteException {
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (animStartedListener != null) {
+                                    animStartedListener.onAnimationStarted();
+                                }
+                            }
+                        });
+                    }
+                };
+            }
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
+                                callback, true /* scaleUp */);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to override transition: " + e);
+            }
+        }
+    }
+
+    /**
+     * Creates a future which will later be queried for animation specs for this current transition.
+     */
+    private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final Task task,
+            final TaskStackView stackView, final int destinationStack) {
+        return new IAppTransitionAnimationSpecsFuture.Stub() {
+            @Override
+            public AppTransitionAnimationSpec[] get() throws RemoteException {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (RecentsTransitionHelper.this) {
+                            mAppTransitionAnimationSpecs = composeAnimationSpecs(task, stackView,
+                                    destinationStack);
+                            RecentsTransitionHelper.this.notifyAll();
+                        }
+                    }
+                });
+                synchronized (RecentsTransitionHelper.this) {
+                    while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
+                        try {
+                            RecentsTransitionHelper.this.wait();
+                        } catch (InterruptedException e) {}
+                    }
+                    if (mAppTransitionAnimationSpecs == null) {
+                        return null;
+                    }
+                    AppTransitionAnimationSpec[] specs
+                            = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
+                    mAppTransitionAnimationSpecs.toArray(specs);
+                    mAppTransitionAnimationSpecs = SPECS_WAITING;
+                    return specs;
+                }
+            }
+        };
+    }
+
+    /**
+     * Composes the animation specs for all the tasks in the target stack.
+     */
+    private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
+            final TaskStackView stackView, final int destinationStack) {
+        // Ensure we have a valid target stack id
+        final int targetStackId = destinationStack != INVALID_STACK_ID ?
+                destinationStack : task.key.stackId;
+        if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
+            return null;
+        }
+
+        // Calculate the offscreen task rect (for tasks that are not backed by views)
+        float stackScroll = stackView.getScroller().getStackScroll();
+        TaskView taskView = stackView.getChildViewForTask(task);
+        TaskStackLayoutAlgorithm layoutAlgorithm = stackView.getStackAlgorithm();
+        Rect offscreenTaskRect = new Rect(layoutAlgorithm.mTaskRect);
+        offscreenTaskRect.offsetTo(offscreenTaskRect.left,
+                layoutAlgorithm.mCurrentStackRect.bottom);
+
+        // If this is a full screen stack, the transition will be towards the single, full screen
+        // task. We only need the transition spec for this task.
+        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+            if (taskView == null) {
+                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+            } else {
+                layoutAlgorithm.getStackTransform(task, stackScroll, mTmpTransform, null);
+                specs.add(composeAnimationSpec(taskView, mTmpTransform, true /* addHeaderBitmap */));
+            }
+            return specs;
+        }
+
+        // Otherwise, for freeform tasks, create a new animation spec for each task we have to
+        // launch
+        TaskStack stack = stackView.getStack();
+        ArrayList<Task> tasks = stack.getTasks();
+        int taskCount = tasks.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            Task t = tasks.get(i);
+            if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                TaskView tv = stackView.getChildViewForTask(t);
+                if (tv == null) {
+                    // TODO: Create a different animation task rect for this case (though it should
+                    //       never happen)
+                    specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
+                } else {
+                    layoutAlgorithm.getStackTransform(task, stackScroll, mTmpTransform, null);
+                    specs.add(composeAnimationSpec(tv, mTmpTransform, true /* addHeaderBitmap */));
+                }
+            }
+        }
+
+        return specs;
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link Task}
+     */
+    private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
+            Rect taskRect) {
+        return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link TaskView}
+     */
+    private static AppTransitionAnimationSpec composeAnimationSpec(TaskView taskView,
+            TaskViewTransform transform, boolean addHeaderBitmap) {
+        // Disable any focused state before we draw the header
+        // Upfront the processing of the thumbnail
+        if (taskView.isFocusedTask()) {
+            taskView.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
+        }
+
+        Bitmap b = null;
+        if (addHeaderBitmap) {
+            float scale = transform.scale;
+            int fromHeaderWidth = (int) (taskView.mHeaderView.getMeasuredWidth() * scale);
+            int fromHeaderHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+                    Bitmap.Config.ARGB_8888);
+
+            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+                b.eraseColor(0xFFff0000);
+            } else {
+                Canvas c = new Canvas(b);
+                c.scale(scale, scale);
+                taskView.mHeaderView.draw(c);
+                c.setBitmap(null);
+            }
+            b = b.createAshmemBitmap();
+        }
+
+        Rect taskRect = new Rect();
+        transform.rect.round(taskRect);
+        return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
+    }
+}
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 5f94fa7..d18389f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,45 +16,33 @@
 
 package com.android.systemui.recents.views;
 
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
-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.os.Handler;
 import android.util.ArraySet;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowManagerGlobal;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
-
 import com.android.internal.annotations.GuardedBy;
 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.RecentsActivityLaunchState;
 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.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -65,8 +53,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
 /**
@@ -78,22 +64,22 @@
     private static final String TAG = "RecentsView";
     private static final boolean DEBUG = false;
 
-    private static final boolean ADD_HEADER_BITMAP = true;
     private int mStackViewVisibility = View.VISIBLE;
 
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
-        public void onTaskLaunchFailed();
         public void onAllTaskViewsDismissed();
     }
 
     LayoutInflater mInflater;
+    Handler mHandler;
 
     ArrayList<TaskStack> mStacks;
     TaskStackView mTaskStackView;
     RecentsAppWidgetHostView mSearchBar;
     RecentsViewCallbacks mCb;
 
+    RecentsTransitionHelper mTransitionHelper;
     RecentsViewTouchHandler mTouchHandler;
     DragView mDragView;
     TaskStack.DockState[] mVisibleDockStates = {
@@ -107,10 +93,6 @@
 
     Rect mSystemInsets = new Rect();
 
-
-    @GuardedBy("this")
-    List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs;
-
     public RecentsView(Context context) {
         super(context);
     }
@@ -127,6 +109,8 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         setWillNotDraw(false);
         mInflater = LayoutInflater.from(context);
+        mHandler = new Handler();
+        mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
         mTouchHandler = new RecentsViewTouchHandler(this);
@@ -191,17 +175,12 @@
     public boolean launchFocusedTask() {
         if (mTaskStackView != null) {
             TaskStack stack = mTaskStackView.getStack();
-            // Iterate the stack views and try and find the focused task
-            List<TaskView> taskViews = mTaskStackView.getTaskViews();
-            int taskViewCount = taskViews.size();
-            for (int j = 0; j < taskViewCount; j++) {
-                TaskView tv = taskViews.get(j);
-                Task task = tv.getTask();
-                if (tv.isFocusedTask()) {
-                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null,
-                            INVALID_STACK_ID);
-                    return true;
-                }
+            Task task = mTaskStackView.getFocusedTask();
+            if (task != null) {
+                TaskView taskView = mTaskStackView.getChildViewForTask(task);
+                onTaskViewClicked(mTaskStackView, taskView, stack, task, false, null,
+                        INVALID_STACK_ID);
+                return true;
             }
         }
         return false;
@@ -217,7 +196,7 @@
             for (int j = 0; j < taskViewCount; j++) {
                 TaskView tv = taskViews.get(j);
                 if (tv.getTask() == task) {
-                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, taskBounds != null,
+                    onTaskViewClicked(mTaskStackView, tv, stack, task, false,
                             taskBounds, destinationStack);
                     return true;
                 }
@@ -422,280 +401,14 @@
         }
     }
 
-    private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final TaskStackView stackView,
-            final TaskView clickedTask, final int offsetX, final int offsetY,
-            final float stackScroll, final int destinationStack) {
-        return new IAppTransitionAnimationSpecsFuture.Stub() {
-            @Override
-            public AppTransitionAnimationSpec[] get() throws RemoteException {
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (RecentsView.this) {
-                            mAppTransitionAnimationSpecs = getAppTransitionAnimationSpecs(stackView,
-                                    clickedTask, offsetX, offsetY, stackScroll, destinationStack);
-                            RecentsView.this.notifyAll();
-                        }
-                    }
-                });
-                synchronized (RecentsView.this) {
-                    while (mAppTransitionAnimationSpecs == null) {
-                        try {
-                            RecentsView.this.wait();
-                        } catch (InterruptedException e) {}
-                    }
-                    if (mAppTransitionAnimationSpecs == null) {
-                        return null;
-                    }
-                    AppTransitionAnimationSpec[] specs
-                            = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
-                    return mAppTransitionAnimationSpecs.toArray(specs);
-                }
-            }
-        };
-    }
-
-    private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
-            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
-            int destinationStack) {
-        final int targetStackId = destinationStack != INVALID_STACK_ID ?
-                destinationStack : clickedTask.getTask().key.stackId;
-        if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
-                && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
-            return null;
-        }
-        // If this is a full screen stack, the transition will be towards the single, full screen
-        // task. We only need the transition spec for this task.
-        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
-        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
-            specs.add(createThumbnailHeaderAnimationSpec(
-                    stackView, offsetX, offsetY, stackScroll, clickedTask,
-                    clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
-            return specs;
-        }
-        // This is a free form stack or full screen stack, so there will be multiple windows
-        // animating from thumbnails. We need transition animation specs for all of them.
-
-        // We will use top and bottom task views as a base for tasks, that aren't visible on the
-        // screen. This is necessary for cascade recents list, where some of the tasks might be
-        // hidden.
-        List<TaskView> taskViews = stackView.getTaskViews();
-        int childCount = taskViews.size();
-        TaskView topChild = taskViews.get(0);
-        TaskView bottomChild = taskViews.get(childCount - 1);
-        SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
-        for (int i = 0; i < childCount; i++) {
-            TaskView taskView = taskViews.get(i);
-            taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
-        }
-
-        TaskStack stack = stackView.getStack();
-        // We go through all tasks now and for each generate transition animation spec. If there is
-        // a view associated with a task, we use that view as a base for the animation. If there
-        // isn't, we use bottom or top view, depending on which one would be closer to the task
-        // view if it existed.
-        ArrayList<Task> tasks = stack.getTasks();
-        boolean passedClickedTask = false;
-        for (int i = 0, n = tasks.size(); i < n; i++) {
-            Task task = tasks.get(i);
-            TaskView taskView = taskViewsByTaskId.get(task.key.id);
-            if (taskView != null) {
-                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
-                        stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
-                if (taskView == clickedTask) {
-                    passedClickedTask = true;
-                }
-            } else {
-                taskView = passedClickedTask ? bottomChild : topChild;
-                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
-                        stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
-            }
-        }
-
-        return specs;
-    }
-
-    private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
-            int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
-            boolean addHeaderBitmap) {
-        // Disable any focused state before we draw the header
-        // Upfront the processing of the thumbnail
-        if (tv.isFocusedTask()) {
-            tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
-        }
-        TaskViewTransform transform = new TaskViewTransform();
-        transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
-                transform, null);
-
-        float scale = tv.getScaleX();
-        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
-        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
-
-        Bitmap b = null;
-        if (addHeaderBitmap) {
-            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
-                    Bitmap.Config.ARGB_8888);
-
-            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                b.eraseColor(0xFFff0000);
-            } else {
-                Canvas c = new Canvas(b);
-                c.scale(tv.getScaleX(), tv.getScaleY());
-                tv.mHeaderView.draw(c);
-                c.setBitmap(null);
-
-            }
-            b = b.createAshmemBitmap();
-        }
-
-        int[] pts = new int[2];
-        tv.getLocationOnScreen(pts);
-
-        final int left = pts[0] + offsetX;
-        final int top = pts[1] + offsetY;
-        final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
-                top + (int) transform.rect.height());
-
-        return new AppTransitionAnimationSpec(taskId, b, rect);
-    }
-
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @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, int destinationStack) {
-
-        // Upfront the processing of the thumbnail
-        TaskViewTransform transform = new TaskViewTransform();
-        View sourceView;
-        int offsetX = 0;
-        int offsetY = 0;
-        float stackScroll = stackView.getScroller().getStackScroll();
-        if (tv == null) {
-            // If there is no actual task view, then use the stack view as the source view
-            // 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 = (int) transform.rect.left;
-            offsetY = getMeasuredHeight();
-        } else {
-            sourceView = tv.mThumbnailView;
-        }
-
-        // Compute the thumbnail to scale up from
-        final SystemServicesProxy ssp = Recents.getSystemServices();
-        boolean screenPinningRequested = false;
-        ActivityOptions opts = ActivityOptions.makeBasic();
-        ActivityOptions.OnAnimationStartedListener animStartedListener = null;
-        final IAppTransitionAnimationSpecsFuture transitionFuture;
-        if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
-                task.thumbnail.getHeight() > 0) {
-            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
-                @Override
-                public void onAnimationStarted() {
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-
-                    if (lockToTask) {
-                        // Request screen pinning after the animation runs
-                        postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                EventBus.getDefault().send(new ScreenPinningRequestEvent(
-                                        getContext(), ssp));
-                            }
-                        }, 350);
-                    }
-                }
-            };
-            transitionFuture = getAppTransitionFuture(stackView, tv, offsetX, offsetY, stackScroll,
-                    destinationStack);
-            screenPinningRequested = true;
-        } else {
-            transitionFuture = null;
-        }
-        if (boundsValid) {
-            opts.setBounds(bounds.isEmpty() ? null : bounds);
-        }
-        final ActivityOptions launchOpts = opts;
-        final boolean finalScreenPinningRequested = screenPinningRequested;
-        final OnAnimationStartedListener finalAnimStartedListener = animStartedListener;
-        final Runnable launchRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (task.isActive) {
-                    // Bring an active task to the foreground
-                    ssp.moveTaskToFront(task.key.id, launchOpts);
-                } else {
-                    if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
-                            launchOpts)) {
-                        if (!finalScreenPinningRequested) {
-                            // If we have not requested this already to be run after the window
-                            // transition, then just run it now
-                            EventBus.getDefault().send(new ScreenPinningRequestEvent(
-                                    getContext(), ssp));
-                        }
-                    } else {
-                        // Dismiss the task and return the user to home if we fail to
-                        // launch the task
-                        EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
-                        if (mCb != null) {
-                            mCb.onTaskLaunchFailed();
-                        }
-
-                        // Keep track of failed launches
-                        MetricsLogger.count(getContext(), "overview_task_launch_failed", 1);
-                    }
-                }
-                if (transitionFuture != null) {
-                    IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
-                        @Override
-                        public void sendResult(Bundle data) throws RemoteException {
-                            post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    if (finalAnimStartedListener != null) {
-                                        finalAnimStartedListener.onAnimationStarted();
-                                    }
-                                }
-                            });
-                        }
-                    };
-                    try {
-                        WindowManagerGlobal.getWindowManagerService()
-                                .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
-                                        callback, true /* scaleUp */);
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to override transition: " + e);
-                    }
-                }
-            }
-        };
-
-        // Keep track of the index of the task launch
-        int taskIndexFromFront = 0;
-        int taskIndex = stack.indexOfTask(task);
-        if (taskIndex > -1) {
-            taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
-        }
-        MetricsLogger.histogram(getContext(), "overview_task_launch_index", taskIndexFromFront);
-
-        // Launch the app right away if there is no task view, otherwise, animate the icon out first
-        if (tv == null) {
-            launchRunnable.run();
-        } else {
-            if (task.group != null && !task.group.isFrontMostTask(task)) {
-                // For affiliated tasks that are behind other tasks, we must animate the front cards
-                // out of view before starting the task transition
-                stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
-            } else {
-                // Otherwise, we can start the task transition immediately
-                stackView.startLaunchTaskAnimation(tv, null, lockToTask);
-                launchRunnable.run();
-            }
-        }
+            final Rect bounds, int destinationStack) {
+        mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
+                destinationStack);
     }
 
     @Override
@@ -831,6 +544,15 @@
         }
     }
 
+    public final void onBusEvent(DraggingInRecentsEvent event) {
+        setStackViewVisibility(View.VISIBLE);
+        setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
+    }
+
+    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
+        animate().translationY(0f);
+    }
+
     /**
      * Updates the dock region to match the specified dock state.
      */
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 c4e2d8a..5a09ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -26,7 +26,7 @@
 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;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -81,21 +81,11 @@
     /**
      * Starts animating the scrim views when entering Recents.
      */
-    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
-        RecentsConfiguration config = Recents.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        int transitionEnterFromAppDelay = mContext.getResources().getInteger(
-                R.integer.recents_enter_from_app_transition_duration);
-        int transitionEnterFromHomeDelay = mContext.getResources().getInteger(
-                R.integer.recents_enter_from_home_transition_duration);
-
+    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
             mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
             mStatusBarScrimView.animate()
                     .translationY(0)
-                    .setStartDelay(launchState.launchedFromHome ?
-                            transitionEnterFromHomeDelay :
-                            transitionEnterFromAppDelay)
                     .setDuration(mNavBarScrimEnterDuration)
                     .setInterpolator(mQuintOutInterpolator)
                     .withStartAction(new Runnable() {
@@ -110,9 +100,6 @@
             mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
             mNavBarScrimView.animate()
                     .translationY(0)
-                    .setStartDelay(launchState.launchedFromHome ?
-                            transitionEnterFromHomeDelay :
-                            transitionEnterFromAppDelay)
                     .setDuration(mNavBarScrimEnterDuration)
                     .setInterpolator(mQuintOutInterpolator)
                     .withStartAction(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 51091c3..b3bd6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -201,7 +201,7 @@
                 taskStackBounds.right - widthPadding,
                 taskStackBounds.bottom);
         // Anchor the task rect to the top-center of the non-freeform stack rect
-        int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
+        int size = mStackRect.width();
         mTaskRect.set(mStackRect.left, mStackRect.top,
                 mStackRect.left + size, mStackRect.top + size);
         mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
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 3ef597f7..7250d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -79,7 +79,7 @@
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
         public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
-                boolean lockToTask, boolean boundsValid, Rect bounds, int destinationStack);
+                boolean lockToTask, Rect bounds, int destinationStack);
         public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
         public void onTaskStackFilterTriggered();
         public void onTaskStackUnfilterTriggered();
@@ -114,8 +114,10 @@
     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     List<TaskView> mImmutableTaskViews = new ArrayList<>();
+    List<TaskView> mTmpTaskViews = new ArrayList<>();
     LayoutInflater mInflater;
     boolean mLayersDisabled;
+    boolean mTouchExplorationEnabled;
 
     Interpolator mFastOutSlowInInterpolator;
 
@@ -183,6 +185,8 @@
 
     @Override
     protected void onAttachedToWindow() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
         super.onAttachedToWindow();
     }
@@ -276,14 +280,9 @@
         }
 
         // Mark each task view for relayout
-        if (mViewPool != null) {
-            Iterator<TaskView> iter = mViewPool.poolViewIterator();
-            if (iter != null) {
-                while (iter.hasNext()) {
-                    TaskView tv = iter.next();
-                    tv.reset();
-                }
-            }
+        List<TaskView> poolViews = mViewPool.getViews();
+        for (TaskView tv : poolViews) {
+            tv.reset();
         }
 
         // Reset the stack state
@@ -425,7 +424,7 @@
                         visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
                     mTmpTaskViewMap.put(task, tv);
                 } else {
-                    if (tv.isFocusedTask()) {
+                    if (mTouchExplorationEnabled && tv.isFocusedTask()) {
                         wasLastFocusedTaskAnimated = tv.isFocusAnimated();
                         lastFocusedTaskIndex = taskIndex;
                         resetFocusedTask();
@@ -538,7 +537,7 @@
     /**
      * Updates the clip for each of the task views from back to front.
      */
-    void clipTaskViews() {
+    void clipTaskViews(boolean forceUpdate) {
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
         TaskView tmpTv = null;
@@ -575,7 +574,7 @@
                     }
                 }
             }
-            tv.getViewBounds().setClipBottom(clipBottom);
+            tv.getViewBounds().setClipBottom(clipBottom, forceUpdate);
         }
         mStackViewsClipDirty = false;
     }
@@ -716,6 +715,16 @@
         mFocusedTaskIndex = -1;
     }
 
+    /**
+     * Returns the focused task.
+     */
+    Task getFocusedTask() {
+        if (mFocusedTaskIndex != -1) {
+            return mStack.getTasks().get(mFocusedTaskIndex);
+        }
+        return null;
+    }
+
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
@@ -792,7 +801,7 @@
         mStackScroller.computeScroll();
         // Synchronize the views
         synchronizeStackViewsWithModel();
-        clipTaskViews();
+        clipTaskViews(false /* forceUpdate */);
         // Notify accessibility
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
     }
@@ -849,10 +858,12 @@
         }
 
         // Measure each of the TaskViews
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
+        mTmpTaskViews.clear();
+        mTmpTaskViews.addAll(getTaskViews());
+        mTmpTaskViews.addAll(mViewPool.getViews());
+        int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
+            TaskView tv = mTmpTaskViews.get(i);
             if (tv.getBackground() != null) {
                 tv.getBackground().getPadding(mTmpRect);
             } else {
@@ -878,10 +889,12 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         // Layout each of the TaskViews
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
+        mTmpTaskViews.clear();
+        mTmpTaskViews.addAll(getTaskViews());
+        mTmpTaskViews.addAll(mViewPool.getViews());
+        int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
+            TaskView tv = mTmpTaskViews.get(i);
             if (tv.getBackground() != null) {
                 tv.getBackground().getPadding(mTmpRect);
             } else {
@@ -896,6 +909,15 @@
             mAwaitingFirstLayout = false;
             onFirstLayout();
         }
+
+        if (changed) {
+            if (mStackScroller.isScrollOutOfBounds()) {
+                mStackScroller.boundScroll();
+            }
+            requestSynchronizeStackViewsWithModel();
+            synchronizeStackViewsWithModel();
+            clipTaskViews(true /* forceUpdate */);
+        }
     }
 
     /** Handler for the first layout. */
@@ -1142,7 +1164,7 @@
             TaskView frontTv = getChildViewForTask(newFrontMostTask);
             if (frontTv != null) {
                 frontTv.onTaskBound(newFrontMostTask);
-                frontTv.fadeInActionButton(0, getResources().getInteger(
+                frontTv.fadeInActionButton(getResources().getInteger(
                         R.integer.recents_task_enter_from_app_duration));
             }
         }
@@ -1327,8 +1349,7 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, false, null,
-                    INVALID_STACK_ID);
+            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, null, INVALID_STACK_ID);
         }
     }
 
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 4f4b91a..2c8f316 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -45,6 +45,7 @@
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -313,10 +314,6 @@
         RecentsActivityLaunchState launchState = config.getLaunchState();
         Resources res = mContext.getResources();
         final TaskViewTransform transform = ctx.currentTaskTransform;
-        final int transitionEnterFromAppDelay = res.getInteger(
-                R.integer.recents_enter_from_app_transition_duration);
-        final int transitionEnterFromHomeDelay = res.getInteger(
-                R.integer.recents_enter_from_home_transition_duration);
         final int taskViewEnterFromAppDuration = res.getInteger(
                 R.integer.recents_task_enter_from_app_duration);
         final int taskViewEnterFromHomeDuration = res.getInteger(
@@ -328,29 +325,13 @@
 
         if (launchState.launchedFromAppWithThumbnail) {
             if (mTask.isLaunchTarget) {
-                // Animate the dim/overlay
-                if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
-                    // Animate the thumbnail alpha before the dim animation (to prevent updating the
-                    // hardware layer)
-                    mThumbnailView.startEnterRecentsAnimation(transitionEnterFromAppDelay,
-                            new Runnable() {
-                                @Override
-                                public void run() {
-                                    animateDimToProgress(0, taskViewEnterFromAppDuration,
-                                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                                }
-                            });
-                } else {
-                    // Immediately start the dim animation
-                    animateDimToProgress(transitionEnterFromAppDelay,
-                            taskViewEnterFromAppDuration,
-                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                }
+                // Immediately start the dim animation
+                animateDimToProgress(taskViewEnterFromAppDuration,
+                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
                 ctx.postAnimationTrigger.increment();
 
                 // Animate the action button in
-                fadeInActionButton(transitionEnterFromAppDelay,
-                        taskViewEnterFromAppDuration);
+                fadeInActionButton(taskViewEnterFromAppDuration);
             } else {
                 // Animate the task up if it was occluding the launch target
                 if (ctx.currentTaskOccludesLaunchTarget) {
@@ -358,7 +339,6 @@
                     setAlpha(0f);
                     animate().alpha(1f)
                             .translationY(transform.translationY)
-                            .setStartDelay(transitionEnterFromAppDelay)
                             .setUpdateListener(null)
                             .setInterpolator(mFastOutSlowInInterpolator)
                             .setDuration(taskViewEnterFromHomeDuration)
@@ -377,8 +357,7 @@
         } else if (launchState.launchedFromHome) {
             // Animate the tasks up
             int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
-            int delay = transitionEnterFromHomeDelay +
-                    frontIndex * taskViewEnterFromHomeStaggerDelay;
+            int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
 
             setScaleX(transform.scale);
             setScaleY(transform.scale);
@@ -404,13 +383,12 @@
         }
     }
 
-    public void fadeInActionButton(int delay, int duration) {
+    public void fadeInActionButton(int duration) {
         // Hide the action button
         mActionButtonView.setAlpha(0f);
 
         // Animate the action button in
         mActionButtonView.animate().alpha(1f)
-                .setStartDelay(delay)
                 .setDuration(duration)
                 .setInterpolator(PhoneStatusBar.ALPHA_IN)
                 .start();
@@ -611,12 +589,11 @@
     }
 
     /** Animates the dim to the task progress. */
-    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
+    void animateDimToProgress(int duration, Animator.AnimatorListener postAnimRunnable) {
         // Animate the dim into view as well
         int toDim = getDimFromTaskProgress();
         if (toDim != getDim()) {
             ObjectAnimator anim = ObjectAnimator.ofInt(TaskView.this, "dim", toDim);
-            anim.setStartDelay(delay);
             anim.setDuration(duration);
             if (postAnimRunnable != null) {
                 anim.addListener(postAnimRunnable);
@@ -646,7 +623,9 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        if (Constants.DebugFlags.App.EnableFastToggleRecents && mIsFocused) {
+
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
             Paint tmpPaint = new Paint();
             Rect tmpRect = new Rect();
             tmpRect.set(0, 0, getWidth(), getHeight());
@@ -687,7 +666,8 @@
                 clearAccessibilityFocus();
             }
         }
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled()) {
             invalidate();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 690c297..bc50846 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -218,13 +218,13 @@
     void onFocusChanged(boolean focused) {
         if (focused) {
             if (Float.compare(getAlpha(), 1f) != 0) {
-                startFadeAnimation(1f, 0, 150, null);
+                startFadeAnimation(1f, 150, null);
             }
         } else {
             float taskViewThumbnailAlpha = getResources().getFloat(
                     R.dimen.recents_task_view_thumbnail_alpha);
             if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
-                startFadeAnimation(taskViewThumbnailAlpha, 0, 150, null);
+                startFadeAnimation(taskViewThumbnailAlpha, 150, null);
             }
         }
     }
@@ -244,10 +244,10 @@
     }
 
     /** Animates this task thumbnail as it enters Recents. */
-    void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
+    void startEnterRecentsAnimation(Runnable postAnimRunnable) {
         float taskViewThumbnailAlpha = getResources().getFloat(
                 R.dimen.recents_task_view_thumbnail_alpha);
-        startFadeAnimation(taskViewThumbnailAlpha, delay,
+        startFadeAnimation(taskViewThumbnailAlpha,
                 getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
                 postAnimRunnable);
     }
@@ -256,14 +256,13 @@
     void startLaunchTaskAnimation(Runnable postAnimRunnable) {
         int taskViewExitToAppDuration = mContext.getResources().getInteger(
                 R.integer.recents_task_exit_to_app_duration);
-        startFadeAnimation(1f, 0, taskViewExitToAppDuration, postAnimRunnable);
+        startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
     }
 
     /** Starts a new thumbnail alpha animation. */
-    void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
+    void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
         Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
         mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
-        mThumbnailAlphaAnimator.setStartDelay(delay);
         mThumbnailAlphaAnimator.setDuration(duration);
         mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
         mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index 12b91af..31fbd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -20,6 +20,7 @@
 
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 
 
 /* A view pool to manage more views than we can visibly handle */
@@ -76,11 +77,10 @@
         return v;
     }
 
-    /** Returns an iterator to the list of the views in the pool. */
-    Iterator<V> poolViewIterator() {
-        if (mPool != null) {
-            return mPool.iterator();
-        }
-        return null;
+    /**
+     * Returns the list of views in the pool.
+     */
+    List<V> getViews() {
+        return mPool;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9e3cf37..e6a291c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -594,7 +594,9 @@
 
         // Setup the animation with the screenshot just taken
         if (mScreenshotAnimation != null) {
-            mScreenshotAnimation.end();
+            if (mScreenshotAnimation.isStarted()) {
+                mScreenshotAnimation.end();
+            }
             mScreenshotAnimation.removeAllListeners();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index dd894ce..50e010f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -32,6 +32,7 @@
     private static final String TAG = "Divider";
     private int mDividerWindowWidth;
     private DividerWindowManager mWindowManager;
+    private DividerView mView;
 
     @Override
     public void start() {
@@ -39,6 +40,7 @@
         mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         update(mContext.getResources().getConfiguration());
+        putComponent(Divider.class, this);
     }
 
     @Override
@@ -47,14 +49,18 @@
         update(newConfig);
     }
 
+    public DividerView getView() {
+        return mView;
+    }
+
     private void addDivider(Configuration configuration) {
-        DividerView view = (DividerView)
+        mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
         final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
         final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
-        mWindowManager.add(view, width, height);
-        view.setWindowManager(mWindowManager);
+        mWindowManager.add(mView, width, height);
+        mView.setWindowManager(mWindowManager);
     }
 
     private void removeDivider() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
index 5f983c5..69e90cc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
@@ -98,10 +98,10 @@
 
         // TODO: Better calculation
         targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
-        targets.add(new SnapTarget((int) (0.35f * dividerMax) - mDividerSize / 2,
+        targets.add(new SnapTarget((int) (0.38f * dividerMax) - mDividerSize / 2,
                 SnapTarget.FLAG_NONE));
         targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE));
-        targets.add(new SnapTarget((int) (0.65f * dividerMax) - mDividerSize / 2,
+        targets.add(new SnapTarget((int) (0.62f * dividerMax) - mDividerSize / 2,
                 SnapTarget.FLAG_NONE));
         targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
         return targets;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 59d4011..3e317ff 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -25,8 +25,10 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -125,6 +127,28 @@
         mWindowManager = windowManager;
     }
 
+    public WindowManagerProxy getWindowManagerProxy() {
+        return mWindowManagerProxy;
+    }
+
+    public boolean startDragging() {
+        mDockSide = mWindowManagerProxy.getDockSide();
+        if (mDockSide != WindowManager.DOCKED_INVALID) {
+            mWindowManagerProxy.setResizing(true);
+            mWindowManager.setSlippery(false);
+            liftBackground();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void stopDragging(int position, float velocity) {
+        fling(position, velocity);
+        mWindowManager.setSlippery(true);
+        releaseBackground();
+    }
+
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         convertToScreenCoordinates(event);
@@ -136,20 +160,13 @@
                 mStartX = (int) event.getX();
                 mStartY = (int) event.getY();
                 getLocationOnScreen(mTempInt2);
-                mDockSide = mWindowManagerProxy.getDockSide();
+                boolean result = startDragging();
                 if (isHorizontalDivision()) {
                     mStartPosition = mTempInt2[1] + mDividerInsets;
                 } else {
                     mStartPosition = mTempInt2[0] + mDividerInsets;
                 }
-                if (mDockSide != WindowManager.DOCKED_INVALID) {
-                    mWindowManagerProxy.setResizing(true);
-                    mWindowManager.setSlippery(false);
-                    liftBackground();
-                    return true;
-                } else {
-                    return false;
-                }
+                return result;
             case MotionEvent.ACTION_MOVE:
                 mVelocityTracker.addMovement(event);
                 int x = (int) event.getX();
@@ -166,10 +183,9 @@
                 y = (int) event.getRawY();
 
                 mVelocityTracker.computeCurrentVelocity(1000);
-                fling(x, y, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
-                mWindowManager.setSlippery(true);
-                releaseBackground();
+                int position = calculatePosition(x, y);
+                stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
+                        : mVelocityTracker.getXVelocity());
                 break;
         }
         return true;
@@ -179,9 +195,7 @@
         event.setLocation(event.getRawX(), event.getRawY());
     }
 
-    private void fling(int x, int y, float xVelocity, float yVelocity) {
-        int position = calculatePosition(x, y);
-        float velocity = isHorizontalDivision() ? yVelocity : xVelocity;
+    private void fling(int position, float velocity) {
         final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
                 mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
 
@@ -262,18 +276,21 @@
     }
 
     private void updateDisplayInfo() {
-        DisplayMetrics info = mContext.getResources().getDisplayMetrics();
-        mDisplayWidth = info.widthPixels;
-        mDisplayHeight = info.heightPixels;
+        final DisplayManager displayManager =
+                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        final DisplayInfo info = new DisplayInfo();
+        display.getDisplayInfo(info);
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
     }
 
     private int calculatePosition(int touchX, int touchY) {
         return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
     }
 
-    private boolean isHorizontalDivision() {
-        return mDockSide == WindowManager.DOCKED_TOP
-                || mDockSide == WindowManager.DOCKED_BOTTOM;
+    public boolean isHorizontalDivision() {
+        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
     }
 
     private int calculateXPosition(int touchX) {
@@ -284,7 +301,7 @@
         return mStartPosition + touchY - mStartY;
     }
 
-    private void resizeStack(int position) {
+    public void resizeStack(int position) {
         mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
         switch (mDockSide) {
             case WindowManager.DOCKED_LEFT:
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 94809bc..0d3f803 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -49,8 +49,29 @@
                 mTmpRect.set(mResizeRect);
             }
             try {
-                ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID,
-                        mTmpRect, true);
+                ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, mTmpRect, true);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to resize stack: " + e);
+            }
+        }
+    };
+
+    private final Runnable mDismissRunnable = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove stack: " + e);
+            }
+        }
+    };
+
+    private final Runnable mMaximizeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, null, true);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -65,27 +86,24 @@
     }
 
     public void dismissDockedStack() {
-        try {
-            ActivityManagerNative.getDefault().removeStack(DOCKED_STACK_ID);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to remove stack: " + e);
-        }
+        mExecutor.execute(mDismissRunnable);
     }
 
     public void maximizeDockedStack() {
-        try {
-            ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, null, true);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to resize stack: " + e);
-        }
+        mExecutor.execute(mMaximizeRunnable);
     }
 
-    public void setResizing(boolean resizing) {
-        try {
-            WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error calling setDockedStackResizing: " + e);
-        }
+    public void setResizing(final boolean resizing) {
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error calling setDockedStackResizing: " + e);
+                }
+            }
+        });
     }
 
     public int getDockSide() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3f0000e..f8ab35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -62,6 +62,7 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -175,6 +176,7 @@
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
+    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
@@ -468,7 +470,6 @@
                         processForRemoteInput(sbn.getNotification());
                         String key = sbn.getKey();
                         boolean isUpdate = mNotificationData.get(key) != null;
-
                         // In case we don't allow child notifications, we ignore children of
                         // notifications that have a summary, since we're not going to show them
                         // anyway. This is true also when the summary is canceled,
@@ -1664,7 +1665,10 @@
                 return;
             }
 
-            final PendingIntent intent = sbn.getNotification().contentIntent;
+            Notification notification = sbn.getNotification();
+            final PendingIntent intent = notification.contentIntent != null
+                    ? notification.contentIntent
+                    : notification.fullScreenIntent;
             final String notificationKey = sbn.getKey();
 
             // Mark notification for one frame.
@@ -1746,8 +1750,8 @@
         }
 
         public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
-            final PendingIntent contentIntent = sbn.getNotification().contentIntent;
-            if (contentIntent != null) {
+            Notification notification = sbn.getNotification();
+            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
                 row.setOnClickListener(this);
             } else {
                 row.setOnClickListener(null);
@@ -2013,9 +2017,12 @@
         Entry entry = mNotificationData.get(key);
         if (entry == null) {
             return;
+        } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
         }
 
         Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
 
         boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
         boolean shouldInterrupt = shouldInterrupt(entry, notification);
@@ -2070,7 +2077,6 @@
             inflateViews(entry, mStackScroller);
         }
         updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
-        mNotificationData.updateRanking(ranking);
         updateNotifications();
 
         // Update the veto button accordingly (and as a result, whether this row is
@@ -2155,20 +2161,17 @@
         boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
         boolean isFullscreen = notification.fullScreenIntent != null;
         boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
-        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
-                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
         boolean accessibilityForcesLaunch = isFullscreen
                 && mAccessibilityManager.isTouchExplorationEnabled();
         boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
-
         boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
-                && isAllowed
                 && !accessibilityForcesLaunch
                 && !justLaunchedFullScreenIntent
                 && mPowerManager.isScreenOn()
                 && (!mStatusBarKeyguardViewManager.isShowing()
                         || mStatusBarKeyguardViewManager.isOccluded())
-                && !mStatusBarKeyguardViewManager.isInputRestricted();
+                && !mStatusBarKeyguardViewManager.isInputRestricted()
+                && !mNotificationData.shouldSuppressPeek(sbn.getKey());
         try {
             interrupt = interrupt && !mDreamManager.isDreaming();
         } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 6a90d8e..83dbde5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -105,36 +105,29 @@
         }
 
         public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
-            boolean cached = false;
+            boolean applyInPlace = false;
             if (updatedNotification != null) {
                 final Notification.Builder updatedNotificationBuilder
                         = Notification.Builder.recoverBuilder(ctx, updatedNotification);
                 final RemoteViews newContentView = updatedNotificationBuilder.makeContentView();
-                if (!compareRemoteViews(cachedContentView, newContentView)) {
-                    cachedContentView = newContentView;
-                    cached |= true;
-                }
                 final RemoteViews newBigContentView =
                         updatedNotificationBuilder.makeBigContentView();
-                if (!compareRemoteViews(cachedBigContentView, newBigContentView)) {
-                    cachedBigContentView = newBigContentView;
-                    cached |= true;
-                }
                 final RemoteViews newHeadsUpContentView =
                         updatedNotificationBuilder.makeHeadsUpContentView();
-                if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) {
-                    cachedHeadsUpContentView = newHeadsUpContentView;
-                    cached |= true;
-                }
                 final Notification updatedPublicNotification = updatedNotification.publicVersion;
                 final RemoteViews newPubContentView = (updatedPublicNotification != null)
                         ? Notification.Builder.recoverBuilder(
                                 ctx, updatedPublicNotification).makeContentView()
                         : null;
-                if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) {
-                    cachedPublicContentView = newPubContentView;
-                    cached |= true;
-                }
+
+                applyInPlace = compareRemoteViews(cachedContentView, newContentView)
+                        && compareRemoteViews(cachedBigContentView, newBigContentView)
+                        && compareRemoteViews(cachedHeadsUpContentView, newHeadsUpContentView)
+                        && compareRemoteViews(cachedPublicContentView, newPubContentView);
+                cachedPublicContentView = newPubContentView;
+                cachedHeadsUpContentView = newHeadsUpContentView;
+                cachedBigContentView = newBigContentView;
+                cachedContentView = newContentView;
             } else {
                 final Notification.Builder builder
                         = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
@@ -150,9 +143,9 @@
                             = Notification.Builder.recoverBuilder(ctx, publicNotification);
                     cachedPublicContentView = publicBuilder.makeContentView();
                 }
-                cached = true;
+                applyInPlace = false;
             }
-            return cached;
+            return applyInPlace;
         }
 
         // Returns true if the RemoteViews are the same.
@@ -292,6 +285,15 @@
         return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
     }
 
+    public boolean shouldSuppressPeek(String key) {
+        if (mRankingMap != null) {
+            mRankingMap.getRanking(key, mTmpRanking);
+            return (mTmpRanking.getSuppressedVisualEffects()
+                    & NotificationListenerService.SUPPRESSED_EFFECT_PEEK) != 0;
+        }
+        return false;
+    }
+
     private void updateRankingAndSort(RankingMap ranking) {
         if (ranking != null) {
             mRankingMap = ranking;
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 a51f62a..58c9722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -1060,7 +1060,7 @@
             ris = packageManager.queryIntentActivitiesAsUser(intentToResolve, 0, userId);
         }
         if (ris == null || ris.size() <= 0) {
-            Slog.e(TAG, "Failed to build intent for " + packageName);
+            Slog.i(TAG, "Failed to build intent for " + packageName);
             return null;
         }
         return new ComponentName(ris.get(0).activityInfo.packageName,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
index d527f29..76a9798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
@@ -179,7 +179,7 @@
             }
         }
 
-        Slog.e(TAG, "Activity doesn't have category Intent.CATEGORY_LAUNCHER " + component);
+        Slog.i(TAG, "Activity doesn't have category Intent.CATEGORY_LAUNCHER " + component);
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
new file mode 100644
index 0000000..0466c14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -0,0 +1,250 @@
+/*
+ * 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.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import static android.view.WindowManager.*;
+
+/**
+ * Class to detect gestures on the navigation bar.
+ */
+public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener {
+
+    private static final String DOCK_WINDOW_GESTURE_ENABLED_PROP = "persist.dock_gesture_enabled";
+
+    /**
+     * When dragging from the navigation bar, we drag in recents.
+     */
+    private static final int DRAG_MODE_RECENTS = 0;
+
+    /**
+     * When dragging from the navigation bar, we drag the divider.
+     */
+    private static final int DRAG_MODE_DIVIDER = 1;
+
+    private RecentsComponent mRecentsComponent;
+    private Divider mDivider;
+    private boolean mIsVertical;
+    private boolean mIsRTL;
+
+    private final GestureDetector mTaskSwitcherDetector;
+    private final int mScrollTouchSlop;
+    private final int mTouchSlop;
+    private final int mMinFlingVelocity;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private VelocityTracker mVelocityTracker;
+
+    private boolean mDockWindowEnabled;
+    private boolean mDockWindowTouchSlopExceeded;
+    private int mDragMode;
+
+    public NavigationBarGestureHelper(Context context) {
+        ViewConfiguration configuration = ViewConfiguration.get(context);
+        Resources r = context.getResources();
+        mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+        mTaskSwitcherDetector = new GestureDetector(context, this);
+        mDockWindowEnabled = SystemProperties.getBoolean(DOCK_WINDOW_GESTURE_ENABLED_PROP, false);
+    }
+
+    public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+        mRecentsComponent = recentsComponent;
+        mDivider = divider;
+    }
+
+    public void setBarState(boolean isVertical, boolean isRTL) {
+        mIsVertical = isVertical;
+        mIsRTL = isRTL;
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        // If we move more than a fixed amount, then start capturing for the
+        // task switcher detector
+        mTaskSwitcherDetector.onTouchEvent(event);
+        int action = event.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                mTouchDownX = (int) event.getX();
+                mTouchDownY = (int) event.getY();
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                int xDiff = Math.abs(x - mTouchDownX);
+                int yDiff = Math.abs(y - mTouchDownY);
+                boolean exceededTouchSlop = !mIsVertical
+                        ? xDiff > mScrollTouchSlop && xDiff > yDiff
+                        : yDiff > mScrollTouchSlop && yDiff > xDiff;
+                if (exceededTouchSlop) {
+                    return true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                break;
+        }
+        return mDockWindowEnabled && interceptDockWindowEvent(event);
+    }
+
+    private boolean interceptDockWindowEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                handleDragActionDownEvent(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                return handleDragActionMoveEvent(event);
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                handleDragActionUpEvent(event);
+                break;
+        }
+        return false;
+    }
+
+    private boolean handleDockWindowEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                handleDragActionDownEvent(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                handleDragActionMoveEvent(event);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                handleDragActionUpEvent(event);
+                break;
+        }
+        return true;
+    }
+
+    private void handleDragActionDownEvent(MotionEvent event) {
+        mVelocityTracker = VelocityTracker.obtain();
+        mVelocityTracker.addMovement(event);
+        mDockWindowTouchSlopExceeded = false;
+        mTouchDownX = (int) event.getX();
+        mTouchDownY = (int) event.getY();
+    }
+
+    private boolean handleDragActionMoveEvent(MotionEvent event) {
+        mVelocityTracker.addMovement(event);
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        int xDiff = Math.abs(x - mTouchDownX);
+        int yDiff = Math.abs(y - mTouchDownY);
+        if (!mDockWindowTouchSlopExceeded) {
+            boolean touchSlopExceeded = !mIsVertical
+                    ? yDiff > mTouchSlop && yDiff > xDiff
+                    : xDiff > mTouchSlop && xDiff > yDiff;
+            if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
+                    == DOCKED_INVALID) {
+                mDragMode = calculateDragMode();
+                mRecentsComponent.dockTopTask(mDragMode == DRAG_MODE_RECENTS);
+                if (mDragMode == DRAG_MODE_DIVIDER) {
+                    mDivider.getView().startDragging();
+                }
+                mDockWindowTouchSlopExceeded = true;
+                return true;
+            }
+        } else {
+            if (mDragMode == DRAG_MODE_DIVIDER) {
+                mDivider.getView().resizeStack(
+                        !mIsVertical ? (int) event.getRawY() : (int) event.getRawX());
+            } else if (mDragMode == DRAG_MODE_RECENTS) {
+                mRecentsComponent.onDraggingInRecents(event.getRawY());
+            }
+        }
+        return false;
+    }
+
+    private void handleDragActionUpEvent(MotionEvent event) {
+        mVelocityTracker.addMovement(event);
+        mVelocityTracker.computeCurrentVelocity(1000);
+        if (mDockWindowTouchSlopExceeded) {
+            if (mDragMode == DRAG_MODE_DIVIDER) {
+                mDivider.getView().stopDragging(!mIsVertical
+                                ? (int) event.getRawY()
+                                : (int) event.getRawX(),
+                        !mIsVertical
+                                ? mVelocityTracker.getXVelocity()
+                                : mVelocityTracker.getYVelocity());
+            } else if (mDragMode == DRAG_MODE_RECENTS) {
+                mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
+            }
+        }
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+    }
+
+    private int calculateDragMode() {
+        if (mIsVertical && !mDivider.getView().isHorizontalDivision()) {
+            return DRAG_MODE_DIVIDER;
+        }
+        if (!mIsVertical && mDivider.getView().isHorizontalDivision()) {
+            return DRAG_MODE_DIVIDER;
+        }
+        return DRAG_MODE_RECENTS;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+        if (mDockWindowEnabled) {
+            result |= handleDockWindowEvent(event);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        float absVelX = Math.abs(velocityX);
+        float absVelY = Math.abs(velocityY);
+        boolean isValidFling = absVelX > mMinFlingVelocity &&
+                mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
+        if (isValidFling) {
+            boolean showNext;
+            if (!mIsRTL) {
+                showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
+            } else {
+                // In RTL, vertical is still the same, but horizontal is flipped
+                showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
+            }
+            if (showNext) {
+                mRecentsComponent.showNextAffiliatedTask();
+            } else {
+                mRecentsComponent.showPrevAffiliatedTask();
+            }
+        }
+        return true;
+    }
+}
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 33e514d..e1aec6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -40,7 +40,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewRootImpl;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
@@ -48,6 +47,8 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 
@@ -78,7 +79,7 @@
     private Drawable mRecentIcon;
     private Drawable mRecentLandIcon;
 
-    private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
+    private NavigationBarGestureHelper mGestureHelper;
     private DeadZone mDeadZone;
     private final NavigationBarTransitions mBarTransitions;
 
@@ -180,7 +181,7 @@
         mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
         mVertical = false;
         mShowMenu = false;
-        mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context);
+        mGestureHelper = new NavigationBarGestureHelper(context);
 
         getIcons(res);
 
@@ -191,8 +192,8 @@
         return mBarTransitions;
     }
 
-    public void setBar(PhoneStatusBar phoneStatusBar) {
-        mTaskSwitchHelper.setBar(phoneStatusBar);
+    public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+        mGestureHelper.setComponents(recentsComponent, divider);
     }
 
     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
@@ -202,7 +203,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (mTaskSwitchHelper.onTouchEvent(event)) {
+        if (mGestureHelper.onTouchEvent(event)) {
             return true;
         }
         if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
@@ -213,7 +214,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        return mTaskSwitchHelper.onInterceptTouchEvent(event);
+        return mGestureHelper.onInterceptTouchEvent(event);
     }
 
     public void abortCurrentGesture() {
@@ -488,7 +489,7 @@
 
     private void updateTaskSwitchHelper() {
         boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
-        mTaskSwitchHelper.setBarState(mVertical, isRtl);
+        mGestureHelper.setBarState(mVertical, isRtl);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
deleted file mode 100644
index fdfcdfb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
+++ /dev/null
@@ -1,114 +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.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-
-public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnGestureListener {
-
-    private BaseStatusBar mBar;
-    private boolean mIsVertical;
-    private boolean mIsRTL;
-
-    private final GestureDetector mTaskSwitcherDetector;
-    private final int mScrollTouchSlop;
-    private final int mMinFlingVelocity;
-    private int mTouchDownX;
-    private int mTouchDownY;
-
-    public NavigationBarViewTaskSwitchHelper(Context context) {
-        ViewConfiguration configuration = ViewConfiguration.get(context);
-        Resources r = context.getResources();
-        mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
-        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
-        mTaskSwitcherDetector = new GestureDetector(context, this);
-    }
-
-    public void setBar(BaseStatusBar phoneStatusBar) {
-        mBar = phoneStatusBar;
-    }
-
-    public void setBarState(boolean isVertical, boolean isRTL) {
-        mIsVertical = isVertical;
-        mIsRTL = isRTL;
-    }
-
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        // If we move more than a fixed amount, then start capturing for the
-        // task switcher detector
-        mTaskSwitcherDetector.onTouchEvent(event);
-        int action = event.getAction();
-        boolean intercepted = false;
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                mTouchDownX = (int) event.getX();
-                mTouchDownY = (int) event.getY();
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int xDiff = Math.abs(x - mTouchDownX);
-                int yDiff = Math.abs(y - mTouchDownY);
-                boolean exceededTouchSlop = !mIsVertical
-                        ? xDiff > mScrollTouchSlop && xDiff > yDiff
-                        : yDiff > mScrollTouchSlop && yDiff > xDiff;
-                if (exceededTouchSlop) {
-                    return true;
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                break;
-        }
-        return intercepted;
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        return mTaskSwitcherDetector.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        float absVelX = Math.abs(velocityX);
-        float absVelY = Math.abs(velocityY);
-        boolean isValidFling = absVelX > mMinFlingVelocity &&
-                mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
-        if (isValidFling) {
-            boolean showNext;
-            if (!mIsRTL) {
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
-            } else {
-                // In RTL, vertical is still the same, but horizontal is flipped
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
-            }
-            if (showNext) {
-                mBar.showNextAffiliatedTask();
-            } else {
-                mBar.showPreviousAffiliatedTask();
-            }
-        }
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index bbef1c0..fbe9730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -84,10 +84,10 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (notif.isGroupSummary()) {
-            group.summary = null;
-        } else {
+        if (notif.isGroupChild()) {
             group.children.remove(removed);
+        } else {
+            group.summary = null;
         }
         if (group.children.isEmpty()) {
             if (group.summary == null) {
@@ -107,17 +107,17 @@
             group = new NotificationGroup();
             mGroupMap.put(groupKey, group);
         }
-        if (notif.isGroupSummary()) {
+        if (notif.isGroupChild()) {
+            group.children.add(added);
+            if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+                group.summary.row.updateNotificationHeader();
+            }
+        } else {
             group.summary = added;
             group.expanded = added.row.areChildrenExpanded();
             if (!group.children.isEmpty()) {
                 mListener.onGroupCreatedFromChildren(group);
             }
-        } else {
-            group.children.add(added);
-            if (group.summary != null && group.children.size() == 1 && !group.expanded) {
-                group.summary.row.updateNotificationHeader();
-            }
         }
     }
 
@@ -169,7 +169,7 @@
      * @return whether a given notification is a summary in a group which has children
      */
     public boolean isSummaryOfGroup(StatusBarNotification sbn) {
-        if (sbn.getNotification().isGroupChild()) {
+        if (!sbn.getNotification().isGroupSummary()) {
             return false;
         }
         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
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 3e52515..781d651 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -117,6 +117,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.BaseStatusBar;
@@ -617,7 +618,6 @@
     };
     private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
             = new HashMap<>();
-    private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
@@ -740,7 +740,7 @@
                             context, R.layout.navigation_bar, null);
                 }
                 mNavigationBarView.setDisabledFlags(mDisabled1);
-                mNavigationBarView.setBar(this);
+                mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
                 mNavigationBarView.setOnVerticalChangedListener(
                         new NavigationBarView.OnVerticalChangedListener() {
                     @Override
@@ -928,7 +928,7 @@
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
             mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
             mHeader.setQSPanel(mQSPanel);
-            qsh.setCallback(new QSTileHost.Callback() {
+            qsh.addCallback(new QSTileHost.Callback() {
                 @Override
                 public void onTilesChanged() {
                     mQSPanel.setTiles(qsh.getTiles());
@@ -1136,7 +1136,7 @@
         @Override
         public boolean onLongClick(View v) {
             if (mRecents != null) {
-                mRecents.dockTopTask();
+                mRecents.dockTopTask(false /* draggingInRecents */);
                 return true;
             }
             return false;
@@ -1262,6 +1262,7 @@
             Entry oldEntry) {
         if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
 
+        mNotificationData.updateRanking(ranking);
         Entry shadeEntry = createNotificationViews(notification);
         if (shadeEntry == null) {
             return;
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 fa9c4bb..83edc96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -383,6 +383,12 @@
                 @Override
                 public void onUserSwitching(int newUserId, IRemoteCallback reply) {
                     mUserInfoController.reloadUserInfo();
+                    if (reply != null) {
+                        try {
+                            reply.sendResult(null);
+                        } catch (RemoteException e) {
+                        }
+                    }
                 }
 
                 @Override
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 96b919e..57c2648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,18 +17,56 @@
 package com.android.systemui.statusbar.phone;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
+import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
+import android.os.RemoteException;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.Tile;
 import android.util.Log;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.tiles.*;
-import com.android.systemui.statusbar.policy.*;
+import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.BatteryTile;
+import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.qs.tiles.CastTile;
+import com.android.systemui.qs.tiles.CellularTile;
+import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.qs.tiles.FlashlightTile;
+import com.android.systemui.qs.tiles.HotspotTile;
+import com.android.systemui.qs.tiles.IntentTile;
+import com.android.systemui.qs.tiles.LocationTile;
+import com.android.systemui.qs.tiles.QAirplaneTile;
+import com.android.systemui.qs.tiles.QBluetoothTile;
+import com.android.systemui.qs.tiles.QFlashlightTile;
+import com.android.systemui.qs.tiles.QLockTile;
+import com.android.systemui.qs.tiles.QRotationLockTile;
+import com.android.systemui.qs.tiles.QWifiTile;
+import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.UserTile;
+import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -40,7 +78,7 @@
 import java.util.Map;
 
 /** Platform implementation of the quick settings tile host **/
-public class QSTileHost implements QSTile.Host, Tunable {
+public final class QSTileHost extends IQSService.Stub implements QSTile.Host, Tunable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -65,7 +103,7 @@
     private final SecurityController mSecurity;
     private final BatteryController mBattery;
 
-    private Callback mCallback;
+    private final List<Callback> mCallbacks = new ArrayList<>();
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
@@ -107,8 +145,8 @@
     }
 
     @Override
-    public void setCallback(Callback callback) {
-        mCallback = callback;
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
     }
 
     @Override
@@ -209,14 +247,14 @@
     public SecurityController getSecurityController() {
         return mSecurity;
     }
-    
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!TILES_SETTING.equals(key)) {
             return;
         }
         if (DEBUG) Log.d(TAG, "Recreating tiles");
-        final List<String> tileSpecs = loadTileSpecs(newValue);
+        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
         if (tileSpecs.equals(mTileSpecs)) return;
         for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
             if (!tileSpecs.contains(tile.getKey())) {
@@ -227,11 +265,15 @@
         final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
         for (String tileSpec : tileSpecs) {
             if (mTiles.containsKey(tileSpec)) {
-                newTiles.put(tileSpec, mTiles.get(tileSpec));
+                QSTile<?> tile = mTiles.get(tileSpec);
+                if (DEBUG) Log.d(TAG, "Adding " + tile);
+                newTiles.put(tileSpec, tile);
             } else {
                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
-                    newTiles.put(tileSpec, createTile(tileSpec));
+                    QSTile<?> tile = createTile(tileSpec);
+                    tile.setTileSpec(tileSpec);
+                    newTiles.put(tileSpec, tile);
                 } catch (Throwable t) {
                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
                 }
@@ -241,11 +283,46 @@
         mTileSpecs.addAll(tileSpecs);
         mTiles.clear();
         mTiles.putAll(newTiles);
-        if (mCallback != null) {
-            mCallback.onTilesChanged();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onTilesChanged();
         }
     }
 
+    @Override
+    public void updateQsTile(Tile tile) throws RemoteException {
+        verifyCaller(tile.getComponentName().getPackageName());
+        CustomTile customTile = getTileForComponent(tile.getComponentName());
+        if (customTile != null) {
+            Log.d("TileService", "Got tile update for " + tile.getComponentName());
+            customTile.updateState(tile);
+            customTile.refreshState();
+        }
+    }
+
+    private void verifyCaller(String packageName) {
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(packageName,
+                    Binder.getCallingUserHandle().getIdentifier());
+            if (Binder.getCallingUid() != uid) {
+                throw new SecurityException("Component outside caller's uid");
+            }
+        } catch (NameNotFoundException e) {
+            throw new SecurityException(e);
+        }
+    }
+
+    private CustomTile getTileForComponent(ComponentName component) {
+        // TODO: Build map for easier lookup.
+        for (QSTile<?> qsTile : mTiles.values()) {
+            if (qsTile instanceof CustomTile) {
+                if (((CustomTile) qsTile).getComponent().equals(component)) {
+                    return (CustomTile) qsTile;
+                }
+            }
+        }
+        return null;
+    }
+
     public QSTile<?> createTile(String tileSpec) {
         if (tileSpec.equals("wifi")) return new WifiTile(this, false);
         else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
@@ -276,8 +353,8 @@
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
     }
 
-    protected List<String> loadTileSpecs(String tileList) {
-        final Resources res = mContext.getResources();
+    public static List<String> loadTileSpecs(Context context, String tileList) {
+        final Resources res = context.getResources();
         final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
         if (tileList == null) {
             tileList = res.getString(R.string.quick_settings_tiles);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 662dbd9..cc9f5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -192,7 +192,7 @@
                 host.getBatteryController());
         mHeaderQsPanel.setHost(myHost);
         mHeaderQsPanel.setTiles(myHost.getTiles());
-        myHost.setCallback(new QSTile.Host.Callback() {
+        myHost.addCallback(new QSTile.Host.Callback() {
             @Override
             public void onTilesChanged() {
                 mHeaderQsPanel.setTiles(myHost.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 18db5b8..512af1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -19,16 +19,13 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Message;
 import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-
 import com.android.keyguard.AlphaOptimizedImageButton;
 
 public class SettingsButton extends AlphaOptimizedImageButton {
@@ -157,6 +154,7 @@
 
     protected void startContinuousSpin() {
         cancelAnimation();
+        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
         mUpToSpeed = true;
         mAnimator = ObjectAnimator.ofFloat(this, View.ROTATION, 0, 360);
         mAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
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 dc9f5e8..5cfd174 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -458,7 +458,10 @@
             mReleaseOnExpandFinish = false;
         } else {
             for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
-                removeHeadsUpEntry(entry);
+                if (isHeadsUp(entry.key)) {
+                    // Maybe the heads-up was removed already
+                    removeHeadsUpEntry(entry);
+                }
             }
         }
         mEntriesToRemoveAfterExpand.clear();
@@ -596,6 +599,9 @@
                 postTime = Math.max(postTime, currentTime);
             }
             removeAutoRemovalCallbacks();
+            if (mEntriesToRemoveAfterExpand.contains(entry)) {
+                mEntriesToRemoveAfterExpand.remove(entry);
+            }
             if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 38656ee..f8c72b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.telephony.SubscriptionInfo;
-
+import com.android.settingslib.net.MobileDataController;
 import com.android.settingslib.wifi.AccessPoint;
 
 import java.util.List;
@@ -85,22 +85,4 @@
             void onSettingsActivityTriggered(Intent settingsIntent);
         }
     }
-
-    /**
-     * Tracks mobile data support and usage.
-     */
-    public interface MobileDataController {
-        boolean isMobileDataSupported();
-        boolean isMobileDataEnabled();
-        void setMobileDataEnabled(boolean enabled);
-        DataUsageInfo getDataUsageInfo();
-
-        public static class DataUsageInfo {
-            public String carrier;
-            public String period;
-            public long limitLevel;
-            public long warningLevel;
-            public long usageLevel;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 2996808..909f497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -39,10 +37,10 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.MathUtils;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.settingslib.net.MobileDataController;
 import com.android.systemui.DemoMode;
 import com.android.systemui.R;
 
@@ -57,9 +55,11 @@
 import java.util.Locale;
 import java.util.Map;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
 /** Platform implementation of the network controller. **/
 public class NetworkControllerImpl extends BroadcastReceiver
-        implements NetworkController, DemoMode {
+        implements NetworkController, DemoMode, MobileDataController.NetworkNameProvider {
     // debug
     static final String TAG = "NetworkController";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -94,7 +94,7 @@
     // SIM for most actions.  This may be null if there aren't any SIMs around.
     private MobileSignalController mDefaultSignalController;
     private final AccessPointControllerImpl mAccessPoints;
-    private final MobileDataControllerImpl mMobileDataController;
+    private final MobileDataController mMobileDataController;
 
     private boolean mInetCondition; // Used for Logging and demo.
 
@@ -139,7 +139,7 @@
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
                 new AccessPointControllerImpl(context, bgLooper),
-                new MobileDataControllerImpl(context),
+                new MobileDataController(context),
                 new SubscriptionDefaults());
         mReceiverHandler.post(mRegisterListeners);
     }
@@ -150,7 +150,7 @@
             SubscriptionManager subManager, Config config, Looper bgLooper,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
-            MobileDataControllerImpl mobileDataController,
+            MobileDataController mobileDataController,
             SubscriptionDefaults defaultsHandler) {
         mContext = context;
         mConfig = config;
@@ -174,7 +174,7 @@
         mMobileDataController = mobileDataController;
         mMobileDataController.setNetworkController(this);
         // TODO: Find a way to move this into MobileDataController.
-        mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
+        mMobileDataController.setCallback(new MobileDataController.Callback() {
             @Override
             public void onMobileDataEnabled(boolean enabled) {
                 mCallbackHandler.setMobileDataEnabled(enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 9b1e72a..eab6e13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -18,20 +18,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.util.Log;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
+import com.android.settingslib.wifi.WifiStatusTracker;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
-import java.util.List;
 import java.util.Objects;
 
 
@@ -40,12 +36,14 @@
     private final WifiManager mWifiManager;
     private final AsyncChannel mWifiChannel;
     private final boolean mHasMobileData;
+    private final WifiStatusTracker mWifiTracker;
 
     public WifiSignalController(Context context, boolean hasMobileData,
             CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
                 callbackHandler, networkController);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mWifiTracker = new WifiStatusTracker(mWifiManager);
         mHasMobileData = hasMobileData;
         Handler handler = new WifiHandler();
         mWifiChannel = new AsyncChannel();
@@ -93,54 +91,15 @@
      * Extract wifi state directly from broadcasts about changes in wifi state.
      */
     public void handleBroadcast(Intent intent) {
-        String action = intent.getAction();
-        if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-            mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                    WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
-        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-            final NetworkInfo networkInfo = (NetworkInfo)
-                    intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
-            mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
-            // If Connected grab the signal strength and ssid.
-            if (mCurrentState.connected) {
-                // try getting it out of the intent first
-                WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
-                        ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
-                        : mWifiManager.getConnectionInfo();
-                if (info != null) {
-                    mCurrentState.ssid = getSsid(info);
-                } else {
-                    mCurrentState.ssid = null;
-                }
-            } else if (!mCurrentState.connected) {
-                mCurrentState.ssid = null;
-            }
-        } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
-            // Default to -200 as its below WifiManager.MIN_RSSI.
-            mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
-            mCurrentState.level = WifiManager.calculateSignalLevel(
-                    mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
-        }
-
+        mWifiTracker.handleBroadcast(intent);
+        mCurrentState.enabled = mWifiTracker.enabled;
+        mCurrentState.connected = mWifiTracker.connected;
+        mCurrentState.ssid = mWifiTracker.ssid;
+        mCurrentState.rssi = mWifiTracker.rssi;
+        mCurrentState.level = mWifiTracker.level;
         notifyListenersIfNecessary();
     }
 
-    private String getSsid(WifiInfo info) {
-        String ssid = info.getSSID();
-        if (ssid != null) {
-            return ssid;
-        }
-        // OK, it's not in the connectionInfo; we have to go hunting for it
-        List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
-        int length = networks.size();
-        for (int i = 0; i < length; i++) {
-            if (networks.get(i).networkId == info.getNetworkId()) {
-                return networks.get(i).SSID;
-            }
-        }
-        return null;
-    }
-
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
deleted file mode 100644
index 05e3fd5..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ /dev/null
@@ -1,547 +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.
- */
-package com.android.systemui.tuner;
-
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnDragListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ScrollView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.QSTile.ResourceIcon;
-import com.android.systemui.qs.QSTileBaseView;
-import com.android.systemui.qs.QSTileView;
-import com.android.systemui.qs.tiles.IntentTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class QsTuner extends Fragment implements Callback {
-
-    private static final String TAG = "QsTuner";
-
-    private static final int MENU_RESET = Menu.FIRST;
-
-    private DraggableQsPanel mQsPanel;
-    private CustomHost mTileHost;
-
-    private FrameLayout mDropTarget;
-
-    private ScrollView mScrollRoot;
-
-    private FrameLayout mAddTarget;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
-    }
-
-    public void onResume() {
-        super.onResume();
-        MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, true);
-    }
-
-    public void onPause() {
-        super.onPause();
-        MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, false);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_RESET:
-                mTileHost.reset();
-                break;
-            case android.R.id.home:
-                getFragmentManager().popBackStack();
-                break;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
-
-        mQsPanel = new DraggableQsPanel(getContext());
-        mTileHost = new CustomHost(getContext());
-        mTileHost.setCallback(this);
-        mQsPanel.setTiles(mTileHost.getTiles());
-        mQsPanel.setHost(mTileHost);
-        mQsPanel.refreshAllTiles();
-        ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
-
-        mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
-        setupDropTarget();
-        mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
-        setupAddTarget();
-        return mScrollRoot;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mTileHost.destroy();
-        super.onDestroyView();
-    }
-
-    private void setupDropTarget() {
-        QSTileView tileView = new QSTileView(getContext());
-        QSTile.State state = new QSTile.State();
-        state.visible = true;
-        state.icon = ResourceIcon.get(R.drawable.ic_delete);
-        state.label = getString(com.android.internal.R.string.delete);
-        tileView.onStateChanged(state);
-        mDropTarget.addView(tileView);
-        mDropTarget.setVisibility(View.GONE);
-        new DragHelper(tileView, new DropListener() {
-            @Override
-            public void onDrop(String sourceText) {
-                mTileHost.remove(sourceText);
-            }
-        });
-    }
-
-    private void setupAddTarget() {
-        QSTileView tileView = new QSTileView(getContext());
-        QSTile.State state = new QSTile.State();
-        state.visible = true;
-        state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
-        state.label = getString(R.string.add_tile);
-        tileView.onStateChanged(state);
-        mAddTarget.addView(tileView);
-        tileView.setClickable(true);
-        tileView.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mTileHost.showAddDialog();
-            }
-        });
-    }
-
-    public void onStartDrag() {
-        mDropTarget.post(new Runnable() {
-            @Override
-            public void run() {
-                mDropTarget.setVisibility(View.VISIBLE);
-                mAddTarget.setVisibility(View.GONE);
-            }
-        });
-    }
-
-    public void stopDrag() {
-        mDropTarget.post(new Runnable() {
-            @Override
-            public void run() {
-                mDropTarget.setVisibility(View.GONE);
-                mAddTarget.setVisibility(View.VISIBLE);
-            }
-        });
-    }
-
-    @Override
-    public void onTilesChanged() {
-        mQsPanel.setTiles(mTileHost.getTiles());
-    }
-
-    private static int getLabelResource(String spec) {
-        if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
-        else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
-        else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
-        else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
-        else if (spec.equals("airplane")) return R.string.airplane_mode;
-        else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
-        else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
-        else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
-        else if (spec.equals("location")) return R.string.quick_settings_location_label;
-        else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
-        else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
-        return 0;
-    }
-
-    private static class CustomHost extends QSTileHost {
-
-        public CustomHost(Context context) {
-            super(context, null, null, null, null, null, null, null, null, null, null,
-                    null, null, new BlankSecurityController(), null);
-        }
-
-        @Override
-        public QSTile<?> createTile(String tileSpec) {
-            return new DraggableTile(this, tileSpec);
-        }
-
-        public void replace(String oldTile, String newTile) {
-            if (oldTile.equals(newTile)) {
-                return;
-            }
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
-                    + newTile);
-            List<String> order = new ArrayList<>(mTileSpecs);
-            int index = order.indexOf(oldTile);
-            if (index < 0) {
-                Log.e(TAG, "Can't find " + oldTile);
-                return;
-            }
-            order.remove(newTile);
-            order.add(index, newTile);
-            setTiles(order);
-        }
-
-        public void remove(String tile) {
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
-            List<String> tiles = new ArrayList<>(mTileSpecs);
-            tiles.remove(tile);
-            setTiles(tiles);
-        }
-
-        public void add(String tile) {
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
-            List<String> tiles = new ArrayList<>(mTileSpecs);
-            tiles.add(tile);
-            setTiles(tiles);
-        }
-
-        public void reset() {
-            Secure.putStringForUser(getContext().getContentResolver(),
-                    TILES_SETTING, "default", ActivityManager.getCurrentUser());
-        }
-
-        private void setTiles(List<String> tiles) {
-            Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
-                    TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
-        }
-
-        public void showAddDialog() {
-            List<String> tiles = mTileSpecs;
-            int numBroadcast = 0;
-            for (int i = 0; i < tiles.size(); i++) {
-                if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
-                    numBroadcast++;
-                }
-            }
-            String[] defaults =
-                getContext().getString(R.string.quick_settings_tiles_default).split(",");
-            final String[] available = new String[defaults.length + 1
-                                                  - (tiles.size() - numBroadcast)];
-            final String[] availableTiles = new String[available.length];
-            int index = 0;
-            for (int i = 0; i < defaults.length; i++) {
-                if (tiles.contains(defaults[i])) {
-                    continue;
-                }
-                int resource = getLabelResource(defaults[i]);
-                if (resource != 0) {
-                    availableTiles[index] = defaults[i];
-                    available[index++] = getContext().getString(resource);
-                } else {
-                    availableTiles[index] = defaults[i];
-                    available[index++] = defaults[i];
-                }
-            }
-            available[index++] = getContext().getString(R.string.broadcast_tile);
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.add_tile)
-                    .setItems(available, new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (which < available.length - 1) {
-                                add(availableTiles[which]);
-                            } else {
-                                showBroadcastTileDialog();
-                            }
-                        }
-                    }).show();
-        }
-
-        public void showBroadcastTileDialog() {
-            final EditText editText = new EditText(getContext());
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.broadcast_tile)
-                    .setView(editText)
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            String action = editText.getText().toString();
-                            if (isValid(action)) {
-                                add(IntentTile.PREFIX + action + ')');
-                            }
-                        }
-                    }).show();
-        }
-
-        private boolean isValid(String action) {
-            for (int i = 0; i < action.length(); i++) {
-                char c = action.charAt(i);
-                if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private static class BlankSecurityController implements SecurityController {
-            @Override
-            public boolean hasDeviceOwner() {
-                return false;
-            }
-
-            @Override
-            public boolean hasProfileOwner() {
-                return false;
-            }
-
-            @Override
-            public String getDeviceOwnerName() {
-                return null;
-            }
-
-            @Override
-            public String getProfileOwnerName() {
-                return null;
-            }
-
-            @Override
-            public boolean isVpnEnabled() {
-                return false;
-            }
-
-            @Override
-            public boolean isVpnRestricted() {
-                return false;
-            }
-
-            @Override
-            public String getPrimaryVpnName() {
-                return null;
-            }
-
-            @Override
-            public String getProfileVpnName() {
-                return null;
-            }
-
-            @Override
-            public void onUserSwitched(int newUserId) {
-            }
-
-            @Override
-            public void addCallback(SecurityControllerCallback callback) {
-            }
-
-            @Override
-            public void removeCallback(SecurityControllerCallback callback) {
-            }
-        }
-    }
-
-    private static class DraggableTile extends QSTile<QSTile.State>
-            implements DropListener {
-        private String mSpec;
-        private QSTileBaseView mView;
-
-        protected DraggableTile(QSTile.Host host, String tileSpec) {
-            super(host);
-            Log.d(TAG, "Creating tile " + tileSpec);
-            mSpec = tileSpec;
-        }
-
-        @Override
-        public QSTileBaseView createTileView(Context context) {
-            mView = super.createTileView(context);
-            return mView;
-        }
-        
-        @Override
-        public int getTileType() {
-            return "wifi".equals(mSpec) || "bt".equals(mSpec) ? QSTileView.QS_TYPE_DUAL
-                    : QSTileView.QS_TYPE_NORMAL;
-        }
-
-        @Override
-        public void setListening(boolean listening) {
-        }
-
-        @Override
-        protected QSTile.State newTileState() {
-            return new QSTile.State();
-        }
-
-        @Override
-        protected void handleClick() {
-        }
-
-        @Override
-        protected void handleUpdateState(QSTile.State state, Object arg) {
-            state.visible = true;
-            state.icon = ResourceIcon.get(getIcon());
-            state.label = getLabel();
-        }
-
-        private String getLabel() {
-            int resource = getLabelResource(mSpec);
-            if (resource != 0) {
-                return mContext.getString(resource);
-            }
-            if (mSpec.startsWith(IntentTile.PREFIX)) {
-                int lastDot = mSpec.lastIndexOf('.');
-                if (lastDot >= 0) {
-                    return mSpec.substring(lastDot + 1, mSpec.length() - 1);
-                } else {
-                    return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1);
-                }
-            }
-            return mSpec;
-        }
-
-        private int getIcon() {
-            if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3;
-            else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected;
-            else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable;
-            else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3;
-            else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
-            else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on;
-            else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
-            else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
-            else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
-            else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
-            else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
-            return R.drawable.android;
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return 20000;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof DraggableTile) {
-                return mSpec.equals(((DraggableTile) o).mSpec);
-            }
-            return false;
-        }
-
-        @Override
-        public void onDrop(String sourceText) {
-            ((CustomHost) mHost).replace(mSpec, sourceText);
-        }
-
-    }
-
-    private class DragHelper implements OnDragListener {
-
-        private final View mView;
-        private final DropListener mListener;
-
-        public DragHelper(View view, DropListener dropListener) {
-            mView = view;
-            mListener = dropListener;
-            mView.setOnDragListener(this);
-        }
-
-        @Override
-        public boolean onDrag(View v, DragEvent event) {
-            switch (event.getAction()) {
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    mView.setBackgroundColor(0x77ffffff);
-                    break;
-                case DragEvent.ACTION_DRAG_ENDED:
-                    stopDrag();
-                case DragEvent.ACTION_DRAG_EXITED:
-                    mView.setBackgroundColor(0x0);
-                    break;
-                case DragEvent.ACTION_DROP:
-                    stopDrag();
-                    String text = event.getClipData().getItemAt(0).getText().toString();
-                    mListener.onDrop(text);
-                    break;
-            }
-            return true;
-        }
-
-    }
-
-    public interface DropListener {
-        void onDrop(String sourceText);
-    }
-
-    private class DraggableQsPanel extends QSPanel implements OnTouchListener {
-        public DraggableQsPanel(Context context) {
-            super(context);
-            mBrightnessView.setVisibility(View.GONE);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            for (TileRecord r : mRecords) {
-                new DragHelper(r.tileView, (DraggableTile) r.tile);
-                r.tileView.setTag(r.tile);
-                r.tileView.setOnTouchListener(this);
-
-                for (int i = 0; i < r.tileView.getChildCount(); i++) {
-                    r.tileView.getChildAt(i).setClickable(false);
-                }
-            }
-        }
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
-                    ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
-                    v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
-                    onStartDrag();
-                    return true;
-            }
-            return false;
-        }
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 911d6a2..b620b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -16,9 +16,10 @@
 package com.android.systemui.tuner;
 
 import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
 import android.app.FragmentTransaction;
 import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
@@ -33,7 +34,6 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
@@ -51,14 +51,14 @@
 
     public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
 
+    private static final String WARNING_TAG = "tuner_warning";
+
     private static final int MENU_REMOVE = Menu.FIRST + 1;
 
     private final SettingObserver mSettingObserver = new SettingObserver();
 
     private SwitchPreference mBatteryPct;
 
-    private Preference mQsTuner;
-
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -66,17 +66,6 @@
         getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
         setHasOptionsMenu(true);
 
-        mQsTuner = findPreference(KEY_QS_TUNER);
-        mQsTuner.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                FragmentTransaction ft = getFragmentManager().beginTransaction();
-                ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
-                ft.addToBackStack(null);
-                ft.commit();
-                return true;
-            }
-        });
         findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
             @Override
             public boolean onPreferenceClick(Preference preference) {
@@ -90,24 +79,10 @@
         mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
         if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
                 0) == 0) {
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.tuner_warning_title)
-                    .setMessage(R.string.tuner_warning)
-                    .setPositiveButton(R.string.got_it, new OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            Settings.Secure.putInt(getContext().getContentResolver(),
-                                    SETTING_SEEN_TUNER_WARNING, 1);
-                        }
-                    }).show();
+            if (getFragmentManager().findFragmentByTag(WARNING_TAG) == null) {
+                new TunerWarningFragment().show(getFragmentManager(), WARNING_TAG);
+            }
         }
-        TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_THE_NEW_QS);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TunerService.get(getContext()).removeTunable(mQsPaging);
     }
 
     @Override
@@ -180,11 +155,19 @@
         }
     };
 
-    private final Tunable mQsPaging = new Tunable() {
+    public static class TunerWarningFragment extends DialogFragment {
         @Override
-        public void onTuningChanged(String key, String newValue) {
-            // Only enable QS rearranging when paging is off, because its very broken.
-            mQsTuner.setEnabled(newValue == null || Integer.parseInt(newValue) == 0);
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            return new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.tuner_warning_title)
+                    .setMessage(R.string.tuner_warning)
+                    .setPositiveButton(R.string.got_it, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            Settings.Secure.putInt(getContext().getContentResolver(),
+                                    SETTING_SEEN_TUNER_WARNING, 1);
+                        }
+                    }).show();
         }
-    };
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 71559389..1e3b0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -116,8 +116,12 @@
 
     public void reloadSetting(Uri uri) {
         String key = mListeningUris.get(uri);
+        List<Tunable> tunables = mTunableLookup.get(key);
+        if (tunables == null) {
+            return;
+        }
         String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
-        for (Tunable tunable : mTunableLookup.get(key)) {
+        for (Tunable tunable : tunables) {
             tunable.onTuningChanged(key, value);
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 30c08cd..13fc47d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.cdma.EriInfo;
+import com.android.settingslib.net.MobileDataController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
@@ -95,7 +96,7 @@
         mCallbackHandler = mock(CallbackHandler.class);
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
-                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class),
+                mock(AccessPointControllerImpl.class), mock(MobileDataController.class),
                 mMockSubDefaults);
         setupNetworkController();
 
@@ -136,7 +137,7 @@
               = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                         mConfig, Looper.getMainLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
-                        mock(MobileDataControllerImpl.class), mMockSubDefaults);
+                        mock(MobileDataController.class), mMockSubDefaults);
 
       setupNetworkController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 0ec8802..587e2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -4,6 +4,7 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.settingslib.net.MobileDataController;
 import org.mockito.Mockito;
 
 @SmallTest
@@ -87,7 +88,7 @@
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 Mockito.mock(AccessPointControllerImpl.class),
-                Mockito.mock(MobileDataControllerImpl.class), mMockSubDefaults);
+                Mockito.mock(MobileDataController.class), mMockSubDefaults);
         setupNetworkController();
 
         setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 660fd9c..760aa9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.settingslib.net.MobileDataController;
 import com.android.systemui.R;
 
 import org.mockito.ArgumentCaptor;
@@ -46,7 +47,7 @@
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
-                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class),
+                mock(AccessPointControllerImpl.class), mock(MobileDataController.class),
                 mMockSubDefaults);
         setupNetworkController();
 
@@ -95,7 +96,7 @@
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
-                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class),
+                mock(AccessPointControllerImpl.class), mock(MobileDataController.class),
                 mMockSubDefaults);
         setupNetworkController();
 
diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml
index bbcd6b9..88f8381 100644
--- a/packages/services/Proxy/AndroidManifest.xml
+++ b/packages/services/Proxy/AndroidManifest.xml
@@ -7,7 +7,9 @@
 
     <application
         android:label="@string/app_label"
-        android:process="com.android.proxyhandler">
+        android:process="com.android.proxyhandler"
+        android:forceDeviceEncrypted="true"
+        android:encryptionAware="true">
 
         <service android:name=".ProxyService"
             android:exported="true">
diff --git a/preloaded-classes b/preloaded-classes
index d6b4ec9..79c0957 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1566,6 +1566,7 @@
 android.security.keystore.AndroidKeyStoreKey
 android.security.keystore.AndroidKeyStoreProvider
 android.security.keystore.KeyStoreCryptoOperation
+android.security.net.config.NetworkSecurityConfigProvider
 android.service.persistentdata.PersistentDataBlockManager
 android.system.ErrnoException
 android.system.GaiException
@@ -3559,11 +3560,6 @@
 org.apache.harmony.security.asn1.BerOutputStream
 org.apache.harmony.security.asn1.DerInputStream
 org.apache.harmony.security.asn1.DerOutputStream
-org.apache.harmony.security.fortress.Engine
-org.apache.harmony.security.fortress.Engine$ServiceCacheEntry
-org.apache.harmony.security.fortress.Engine$SpiAndProvider
-org.apache.harmony.security.fortress.SecurityAccess
-org.apache.harmony.security.fortress.Services
 org.apache.harmony.security.provider.crypto.CryptoProvider
 org.apache.harmony.security.utils.AlgNameMapper
 org.apache.harmony.security.utils.AlgNameMapperSource
diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java
index 54180f4..9bbacbc 100644
--- a/rs/java/android/renderscript/ScriptGroup.java
+++ b/rs/java/android/renderscript/ScriptGroup.java
@@ -278,6 +278,8 @@
             public ValueAndSize(RenderScript rs, Object obj) {
                 if (obj instanceof Allocation) {
                     value = ((Allocation)obj).getID(rs);
+                    // Special value for size to tell the runtime and driver that
+                    // the value is an Allocation
                     size = -1;
                 } else if (obj instanceof Boolean) {
                     value = ((Boolean)obj).booleanValue() ? 1 : 0;
@@ -289,10 +291,10 @@
                     value = ((Long)obj).longValue();
                     size = 8;
                 } else if (obj instanceof Float) {
-                    value = ((Float)obj).longValue();
+                    value = Float.floatToRawIntBits(((Float)obj).floatValue());
                     size = 4;
                 } else if (obj instanceof Double) {
-                    value = ((Double)obj).longValue();
+                    value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
                     size = 8;
                 }
             }
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index be7071e..113241d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -393,7 +393,6 @@
 
   size_t numValues, numDependencies;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
   RsClosure* depClosures;
   RsScriptFieldID* depFieldIDs;
 
@@ -430,15 +429,6 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
   if (depClosures == nullptr) {
       goto exit;
@@ -459,7 +449,7 @@
 
   ret = (jlong)(uintptr_t)rsClosureCreate(
       (RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues,
       depClosures, numDependencies,
       depFieldIDs, numDependencies);
@@ -511,7 +501,6 @@
 
   size_t numValues;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
 
   if (fieldIDs_length != values_length || values_length != sizes_length) {
       ALOGE("Unmatched field IDs, values, and sizes in closure creation.");
@@ -534,18 +523,9 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   ret = (jlong)(uintptr_t)rsInvokeClosureCreate(
       (RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues);
 
 exit:
@@ -561,15 +541,17 @@
 static void
 nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                jint index, jlong value, jint size) {
+  // Size is signed with -1 indicating the value is an Allocation
   rsClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
-                  (uintptr_t)value, (size_t)size);
+                  (uintptr_t)value, size);
 }
 
 static void
 nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                   jlong fieldID, jlong value, jint size) {
+  // Size is signed with -1 indicating the value is an Allocation
   rsClosureSetGlobal((RsContext)con, (RsClosure)closureID,
-                     (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+                     (RsScriptFieldID)fieldID, (int64_t)value, size);
 }
 
 static long
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index b52687a..5f6cbf9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -21,7 +21,6 @@
 import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputFilter;
@@ -103,7 +102,7 @@
 
     private TouchExplorer mTouchExplorer;
 
-    private ScreenMagnifier mScreenMagnifier;
+    private MagnificationGestureHandler mMagnificationGestureHandler;
 
     private AutoclickController mAutoclickController;
 
@@ -363,14 +362,13 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
-            mScreenMagnifier = new ScreenMagnifier(mContext, mUserId,
-                    Display.DEFAULT_DISPLAY, mAms);
-            addFirstEventHandler(mScreenMagnifier);
+            mMagnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms);
+            addFirstEventHandler(mMagnificationGestureHandler);
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
-           mKeyboardInterceptor = new KeyboardInterceptor(mAms);
-           addFirstEventHandler(mKeyboardInterceptor);
+            mKeyboardInterceptor = new KeyboardInterceptor(mAms);
+            addFirstEventHandler(mKeyboardInterceptor);
         }
     }
 
@@ -398,9 +396,9 @@
             mTouchExplorer.onDestroy();
             mTouchExplorer = null;
         }
-        if (mScreenMagnifier != null) {
-            mScreenMagnifier.onDestroy();
-            mScreenMagnifier = null;
+        if (mMagnificationGestureHandler != null) {
+            mMagnificationGestureHandler.onDestroy();
+            mMagnificationGestureHandler = null;
         }
         if (mKeyboardInterceptor != null) {
             mKeyboardInterceptor.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 535a8ef..3d358ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,6 +23,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -63,21 +64,17 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Pools.Pool;
-import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.InputDevice;
-import android.view.InputEventConsistencyVerifier;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -90,6 +87,7 @@
 
 import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.LocalServices;
 
@@ -144,8 +142,6 @@
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
-    private static final int MAX_POOL_SIZE = 10;
-
     private static final int WINDOW_ID_UNKNOWN = -1;
 
     private static int sIdCounter = 0;
@@ -156,9 +152,6 @@
 
     private final Object mLock = new Object();
 
-    private final Pool<PendingEvent> mPendingEventPool =
-            new SimplePool<>(MAX_POOL_SIZE);
-
     private final SimpleStringSplitter mStringColonSplitter =
             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
@@ -181,6 +174,8 @@
 
     private MagnificationController mMagnificationController;
 
+    private boolean mUnregisterMagnificationOnReset;
+
     private InteractionBridge mInteractionBridge;
 
     private AlertDialog mEnableTouchExplorationDialog;
@@ -189,6 +184,8 @@
 
     private boolean mHasInputFilter;
 
+    private KeyEventDispatcher mKeyEventDispatcher;
+
     private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
 
     private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -752,12 +749,33 @@
 
     boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
         synchronized (mLock) {
-            KeyEvent localClone = KeyEvent.obtain(event);
-            boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
-            if (!handled) {
-                handled = notifyKeyEventLocked(localClone, policyFlags, true);
+            List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
+            if (boundServices.isEmpty()) {
+                return false;
             }
-            return handled;
+            return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices);
+        }
+    }
+
+    /**
+     * Called by the MagnificationController when the state of display
+     * magnification changes.
+     *
+     * @param region the new magnified region, may be empty if
+     *               magnification is not enabled (e.g. scale is 1)
+     * @param scale the new scale
+     * @param centerX the new screen-relative center X coordinate
+     * @param centerY the new screen-relative center Y coordinate
+     */
+    void notifyMagnificationChanged(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        synchronized (mLock) {
+            notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+
+            if (mUnregisterMagnificationOnReset && scale == 1.0f) {
+                mUnregisterMagnificationOnReset = false;
+                mMagnificationController.unregister();
+            }
         }
     }
 
@@ -909,31 +927,6 @@
         return false;
     }
 
-    private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
-        // TODO: Now we are giving the key events to the last enabled
-        //       service that can handle them Ideally, the user should
-        //       make the call which service handles key events. However,
-        //       only one service should handle key events to avoid user
-        //       frustration when different behavior is observed from
-        //       different combinations of enabled accessibility services.
-        UserState state = getCurrentUserStateLocked();
-        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-            Service service = state.mBoundServices.get(i);
-            // Key events are handled only by services that declared
-            // this capability and requested to filter key events.
-            if (!service.mRequestFilterKeyEvents ||
-                    (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
-                            .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
-                continue;
-            }
-            if (service.mIsDefault == isDefault) {
-                service.notifyKeyEvent(event, policyFlags);
-                return true;
-            }
-        }
-        return false;
-    }
-
     private void notifyClearAccessibilityCacheLocked() {
         UserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
@@ -942,6 +935,15 @@
         }
     }
 
+    private void notifyMagnificationChangedLocked(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        final UserState state = getCurrentUserStateLocked();
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = state.mBoundServices.get(i);
+            service.notifyMagnificationChanged(region, scale, centerX, centerY);
+        }
+    }
+
     /**
      * Removes an AccessibilityInteractionConnection.
      *
@@ -1363,6 +1365,11 @@
         }
     }
 
+    /**
+     * Called when any property of the user state has changed.
+     *
+     * @param userState the new user state
+     */
     private void onUserStateChangedLocked(UserState userState) {
         // TODO: Remove this hack
         mInitialized = true;
@@ -1374,6 +1381,7 @@
         updateTouchExplorationLocked(userState);
         updateEnhancedWebAccessibilityLocked(userState);
         updateDisplayColorAdjustmentSettingsLocked(userState);
+        updateMagnificationLocked(userState);
         scheduleUpdateInputFilter(userState);
         scheduleUpdateClientsIfNeededLocked(userState);
     }
@@ -1663,6 +1671,44 @@
         DisplayAdjustmentUtils.applyAdjustments(mContext, userState.mUserId);
     }
 
+    private void updateMagnificationLocked(UserState userState) {
+        final int userId = userState.mUserId;
+        if (userId == mCurrentUserId && mMagnificationController != null) {
+            if (userHasMagnificationServicesLocked(userState)) {
+                mMagnificationController.setUserId(userState.mUserId);
+            } else {
+                // If the user no longer has any magnification-controlling
+                // services and is not using magnification gestures, then
+                // reset the state to normal.
+                if (!userState.mIsDisplayMagnificationEnabled
+                        && mMagnificationController.resetIfNeeded(true)) {
+                    // Animations are still running, so wait until we receive a
+                    // callback verifying that we've reset magnification.
+                    mUnregisterMagnificationOnReset = true;
+                } else {
+                    mUnregisterMagnificationOnReset = false;
+                    mMagnificationController.unregister();
+                    mMagnificationController = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether the specified user has any services that are capable of
+     * controlling magnification.
+     */
+    private boolean userHasMagnificationServicesLocked(UserState userState) {
+        final List<Service> services = userState.mBoundServices;
+        for (int i = 0, count = services.size(); i < count; i++) {
+            final Service service = services.get(i);
+            if (mSecurityPolicy.canControlMagnification(service)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
         IBinder windowToken = mGlobalWindowTokens.get(windowId);
         if (windowToken == null) {
@@ -1675,6 +1721,14 @@
         return null;
     }
 
+    private KeyEventDispatcher getKeyEventDispatcher() {
+        if (mKeyEventDispatcher == null) {
+            mKeyEventDispatcher = new KeyEventDispatcher(
+                    mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock);
+        }
+        return mKeyEventDispatcher;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
@@ -1875,22 +1929,6 @@
         }
     }
 
-    private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
-        PendingEvent pendingEvent = mPendingEventPool.acquire();
-        if (pendingEvent == null) {
-            pendingEvent = new PendingEvent();
-        }
-        pendingEvent.event = event;
-        pendingEvent.policyFlags = policyFlags;
-        pendingEvent.sequence = sequence;
-        return pendingEvent;
-    }
-
-    private void recyclePendingEventLocked(PendingEvent pendingEvent) {
-        pendingEvent.clear();
-        mPendingEventPool.release(pendingEvent);
-    }
-
     private int findWindowIdLocked(IBinder token) {
         final int globalIndex = mGlobalWindowTokens.indexOfValue(token);
         if (globalIndex >= 0) {
@@ -1938,10 +1976,14 @@
     }
 
     MagnificationController getMagnificationController() {
-        if (mMagnificationController == null) {
-            mMagnificationController = new MagnificationController(mContext, this);
+        synchronized (mLock) {
+            if (mMagnificationController == null) {
+                mMagnificationController = new MagnificationController(mContext, this);
+                mMagnificationController.register();
+                mMagnificationController.setUserId(mCurrentUserId);
+            }
+            return mMagnificationController;
         }
-        return mMagnificationController;
     }
 
     /**
@@ -1999,8 +2041,6 @@
         final SparseArray<AccessibilityEvent> mPendingEvents =
             new SparseArray<>();
 
-        final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
-
         boolean mWasConnectedAndDied;
 
         // Handler only for dispatching accessibility events since we use event
@@ -2112,7 +2152,7 @@
                 return false;
             }
             UserState userState = getUserStateLocked(mUserId);
-            mKeyEventDispatcher.flush();
+            getKeyEventDispatcher().flush(this);
             if (!mIsAutomation) {
                 mContext.unbindService(this);
             } else {
@@ -2129,7 +2169,7 @@
 
         @Override
         public void setOnKeyEventResult(boolean handled, int sequence) {
-            mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+            getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
         }
 
         @Override
@@ -2625,6 +2665,149 @@
         }
 
         @Override
+        public float getMagnificationScale() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 1.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getScale();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public Region getMagnifiedRegion() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return Region.obtain();
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                final Region region = Region.obtain();
+                getMagnificationController().getMagnifiedRegion(region);
+                return region;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public float getMagnificationCenterX() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 0.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getCenterX();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public float getMagnificationCenterY() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 0.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getCenterY();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public boolean resetMagnification(boolean animate) {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return false;
+                }
+                final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+                if (!permissionGranted) {
+                    return false;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().reset(animate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+                boolean animate) {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return false;
+                }
+                final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+                if (!permissionGranted) {
+                    return false;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().setScaleAndCenter(
+                        scale, centerX, centerY, animate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setMagnificationCallbackEnabled(boolean enabled) {
+            mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+        }
+
+        @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
             synchronized (mLock) {
@@ -2700,7 +2883,7 @@
                     return;
                 }
                 mWasConnectedAndDied = true;
-                mKeyEventDispatcher.flush();
+                getKeyEventDispatcher().flush(this);
                 UserState userState = getUserStateLocked(mUserId);
                 // The death recipient is unregistered in removeServiceLocked
                 removeServiceLocked(this, userState);
@@ -2809,16 +2992,35 @@
                     gestureId, 0).sendToTarget();
         }
 
-        public void notifyKeyEvent(KeyEvent event, int policyFlags) {
-            mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
-                    policyFlags, 0, event).sendToTarget();
-        }
-
         public void notifyClearAccessibilityNodeInfoCache() {
             mInvocationHandler.sendEmptyMessage(
                     InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
         }
 
+        public void notifyMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+        }
+
+        /**
+         * Called by the invocation handler to notify the service that the
+         * state of magnification has changed.
+         */
+        private void notifyMagnificationChangedInternal(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            final IAccessibilityServiceClient listener;
+            synchronized (mLock) {
+                listener = mServiceInterface;
+            }
+            if (listener != null) {
+                try {
+                    listener.onMagnificationChanged(region, scale, centerX, centerY);
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+                }
+            }
+        }
+
         private void notifyGestureInternal(int gestureId) {
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
@@ -2834,10 +3036,6 @@
             }
         }
 
-        private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
-            mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
-        }
-
         private void notifyClearAccessibilityCacheInternal() {
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
@@ -2955,9 +3153,11 @@
 
         private final class InvocationHandler extends Handler {
             public static final int MSG_ON_GESTURE = 1;
-            public static final int MSG_ON_KEY_EVENT = 2;
-            public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
-            public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+            public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
+
+            private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+
+            private boolean mIsMagnificationCallbackEnabled = false;
 
             public InvocationHandler(Looper looper) {
                 super(looper, null, true);
@@ -2972,19 +3172,17 @@
                         notifyGestureInternal(gestureId);
                     } break;
 
-                    case MSG_ON_KEY_EVENT: {
-                        KeyEvent event = (KeyEvent) message.obj;
-                        final int policyFlags = message.arg1;
-                        notifyKeyEventInternal(event, policyFlags);
-                    } break;
-
                     case MSG_CLEAR_ACCESSIBILITY_CACHE: {
                         notifyClearAccessibilityCacheInternal();
                     } break;
 
-                    case MSG_ON_KEY_EVENT_TIMEOUT: {
-                        PendingEvent eventState = (PendingEvent) message.obj;
-                        setOnKeyEventResult(false, eventState.sequence);
+                    case MSG_ON_MAGNIFICATION_CHANGED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region region = (Region) args.arg1;
+                        final float scale = (float) args.arg2;
+                        final float centerX = (float) args.arg3;
+                        final float centerY = (float) args.arg4;
+                        notifyMagnificationChangedInternal(region, scale, centerX, centerY);
                     } break;
 
                     default: {
@@ -2992,142 +3190,29 @@
                     }
                 }
             }
-        }
 
-        private final class KeyEventDispatcher {
-
-            private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
-
-            private PendingEvent mPendingEvents;
-
-            private final InputEventConsistencyVerifier mSentEventsVerifier =
-                    InputEventConsistencyVerifier.isInstrumentationEnabled()
-                            ? new InputEventConsistencyVerifier(
-                                    this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
-
-            public void notifyKeyEvent(KeyEvent event, int policyFlags) {
-                final PendingEvent pendingEvent;
-
-                synchronized (mLock) {
-                    pendingEvent = addPendingEventLocked(event, policyFlags);
+            public void notifyMagnificationChanged(@NonNull Region region, float scale,
+                    float centerX, float centerY) {
+                if (!mIsMagnificationCallbackEnabled) {
+                    // Callback is disabled, don't bother packing args.
+                    return;
                 }
 
-                Message message = mInvocationHandler.obtainMessage(
-                        InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
-                mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+                final SomeArgs args = SomeArgs.obtain();
+                args.arg1 = region;
+                args.arg2 = scale;
+                args.arg3 = centerX;
+                args.arg4 = centerY;
 
-                try {
-                    // Accessibility services are exclusively not in the system
-                    // process, therefore no need to clone the motion event to
-                    // prevent tampering. It will be cloned in the IPC call.
-                    mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
-                } catch (RemoteException re) {
-                    setOnKeyEventResult(false, pendingEvent.sequence);
-                }
+                final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+                msg.sendToTarget();
             }
 
-            public void setOnKeyEventResult(boolean handled, int sequence) {
-                synchronized (mLock) {
-                    PendingEvent pendingEvent = removePendingEventLocked(sequence);
-                    if (pendingEvent != null) {
-                        mInvocationHandler.removeMessages(
-                                InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
-                                pendingEvent);
-                        pendingEvent.handled = handled;
-                        finishPendingEventLocked(pendingEvent);
-                    }
-                }
-            }
-
-            public void flush() {
-                synchronized (mLock) {
-                    cancelAllPendingEventsLocked();
-                    if (mSentEventsVerifier != null) {
-                        mSentEventsVerifier.reset();
-                    }
-                }
-            }
-
-            private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
-                final int sequence = event.getSequenceNumber();
-                PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
-                pendingEvent.next = mPendingEvents;
-                mPendingEvents = pendingEvent;
-                return pendingEvent;
-            }
-
-            private PendingEvent removePendingEventLocked(int sequence) {
-                PendingEvent previous = null;
-                PendingEvent current = mPendingEvents;
-
-                while (current != null) {
-                    if (current.sequence == sequence) {
-                        if (previous != null) {
-                            previous.next = current.next;
-                        } else {
-                            mPendingEvents = current.next;
-                        }
-                        current.next = null;
-                        return current;
-                    }
-                    previous = current;
-                    current = current.next;
-                }
-                return null;
-            }
-
-            private void finishPendingEventLocked(PendingEvent pendingEvent) {
-                if (!pendingEvent.handled) {
-                    sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
-                }
-                // Nullify the event since we do not want it to be
-                // recycled yet. It will be sent to the input filter.
-                pendingEvent.event = null;
-                recyclePendingEventLocked(pendingEvent);
-            }
-
-            private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Injecting event: " + event);
-                }
-                if (mSentEventsVerifier != null) {
-                    mSentEventsVerifier.onKeyEvent(event, 0);
-                }
-                policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-                mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
-                        policyFlags, 0, event).sendToTarget();
-            }
-
-            private void cancelAllPendingEventsLocked() {
-                while (mPendingEvents != null) {
-                    PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
-                    pendingEvent.handled = false;
-                    mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
-                            pendingEvent);
-                    finishPendingEventLocked(pendingEvent);
-                }
+            public void setMagnificationCallbackEnabled(boolean enabled) {
+                mIsMagnificationCallbackEnabled = enabled;
             }
         }
-    }
 
-    private static final class PendingEvent {
-        PendingEvent next;
-
-        KeyEvent event;
-        int policyFlags;
-        int sequence;
-        boolean handled;
-
-        public void clear() {
-            if (event != null) {
-                event.recycle();
-                event = null;
-            }
-            next = null;
-            policyFlags = 0;
-            sequence = 0;
-            handled = false;
-        }
     }
 
     final class WindowsForAccessibilityCallback implements
@@ -3660,6 +3745,11 @@
                     & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
         }
 
+        public boolean canControlMagnification(Service service) {
+            return (service.mAccessibilityServiceInfo.getCapabilities()
+                    & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
+        }
+
         private int resolveProfileParentLocked(int userId) {
             if (userId != mCurrentUserId) {
                 final long identity = Binder.clearCallingIdentity();
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
new file mode 100644
index 0000000..3469565
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
@@ -0,0 +1,285 @@
+/*
+ ** 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.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Pools;
+import android.util.Pools.Pool;
+import android.util.Slog;
+import android.view.InputEventConsistencyVerifier;
+import android.view.KeyEvent;
+import android.view.WindowManagerPolicy;
+
+import com.android.server.accessibility.AccessibilityManagerService.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dispatcher to send KeyEvents to all accessibility services that are able to process them.
+ * Events that are handled by one or more services are consumed. Events that are not processed
+ * by any service (or time out before a service reports them as handled) are passed along to
+ * the rest of the system.
+ *
+ * The class assumes that services report their return values in order, which is valid because
+ * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so
+ * don't see the N+1th event until they have processed the Nth event.
+ */
+public class KeyEventDispatcher {
+    // Debugging
+    private static final String LOG_TAG = "KeyEventDispatcher";
+    private static final boolean DEBUG = false;
+    /* KeyEvents must be processed in this time interval */
+    private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+    private static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
+    private static final int MAX_POOL_SIZE = 10;
+
+    private final Pool<PendingKeyEvent> mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE);
+    private final Object mLock;
+
+    /*
+     * Track events sent to each service. If a KeyEvent is to be sent to at least one service,
+     * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in
+     * the list for each service its KeyEvent is sent to. It is removed from the list when
+     * the service calls setOnKeyEventResult, or when we time out waiting for the service to
+     * respond.
+     */
+    private final Map<Service, ArrayList<PendingKeyEvent>> mPendingEventsMap = new ArrayMap<>();
+
+    private final InputEventConsistencyVerifier mSentEventsVerifier;
+    private final Handler mHandlerToSendKeyEventsToInputFilter;
+    private final int mMessageTypeForSendKeyEvent;
+    private final Handler mKeyEventTimeoutHandler;
+
+    /**
+     * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s
+     * that have not been handled by any accessibility service.
+     * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the
+     * message that carries a {@code KeyEvent} to be sent to the input filter
+     * @param lock The lock used for all synchronization in this package. This lock must be held
+     * when calling {@code notifyKeyEventLocked}
+     */
+    public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
+                              int messageTypeForSendKeyEvent, Object lock) {
+        if (InputEventConsistencyVerifier.isInstrumentationEnabled()) {
+            mSentEventsVerifier = new InputEventConsistencyVerifier(
+                    this, 0, KeyEventDispatcher.class.getSimpleName());
+        } else {
+            mSentEventsVerifier = null;
+        }
+        mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter;
+        mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent;
+        mKeyEventTimeoutHandler =
+                new Handler(mHandlerToSendKeyEventsToInputFilter.getLooper(), new Callback());
+        mLock = lock;
+    }
+
+    /**
+     * Notify that a new KeyEvent is available to accessibility services. Must be called with the
+     * lock used to construct this object held. The boundServices list must also be protected
+     * by a lock.
+     *
+     * @param event The new key event
+     * @param policyFlags Flags for the event
+     * @param boundServices A list of currently bound AccessibilityServices
+     *
+     * @return {@code true} if the event was passed to at least one AccessibilityService,
+     * {@code false} otherwise.
+     */
+    // TODO: The locking policy for boundServices needs some thought.
+    public boolean notifyKeyEventLocked(
+            KeyEvent event, int policyFlags, List<Service> boundServices) {
+        PendingKeyEvent pendingKeyEvent = null;
+        KeyEvent localClone = KeyEvent.obtain(event);
+        for (int i = 0; i < boundServices.size(); i++) {
+            Service service = boundServices.get(i);
+            // Key events are handled only by services that declared
+            // this capability and requested to filter key events.
+            if (!service.mRequestFilterKeyEvents) {
+                continue;
+            }
+            int filterKeyEventBit = service.mAccessibilityServiceInfo.getCapabilities()
+                    & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+            if (filterKeyEventBit == 0) {
+                continue;
+            }
+
+            try {
+                // The event will be cloned in the IPC call, so it doesn't need to be here.
+                service.mServiceInterface.onKeyEvent(localClone, localClone.getSequenceNumber());
+            } catch (RemoteException re) {
+                continue;
+            }
+
+            if (pendingKeyEvent == null) {
+                pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
+            }
+            ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(service);
+            if (pendingEventList == null) {
+                pendingEventList = new ArrayList<>();
+                mPendingEventsMap.put(service, pendingEventList);
+            }
+            pendingEventList.add(pendingKeyEvent);
+            pendingKeyEvent.referenceCount++;
+        }
+
+        if (pendingKeyEvent == null) {
+            localClone.recycle();
+            return false;
+        }
+
+        Message message = mKeyEventTimeoutHandler.obtainMessage(
+                MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
+        mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+        return true;
+    }
+
+    /**
+     * Set the result from onKeyEvent from one service.
+     *
+     * @param service The service setting the result
+     * @param handled {@code true} if the service handled the {@code KeyEvent}
+     * @param sequence The sequence number of the {@code KeyEvent}
+     */
+    public void setOnKeyEventResult(Service service, boolean handled, int sequence) {
+        synchronized (mLock) {
+            PendingKeyEvent pendingEvent =
+                    removeEventFromListLocked(mPendingEventsMap.get(service), sequence);
+            if (pendingEvent != null) {
+                pendingEvent.handled |= handled;
+                removeReferenceToPendingEventLocked(pendingEvent);
+            }
+        }
+    }
+
+    /**
+     * Flush all pending key events for a service, treating all of them as unhandled
+     *
+     * @param service The service for which to flush events
+     */
+    public void flush(Service service) {
+        synchronized (mLock) {
+            List<PendingKeyEvent> pendingEvents = mPendingEventsMap.get(service);
+            if (pendingEvents != null) {
+                for (int i = 0; i < pendingEvents.size(); i++) {
+                    PendingKeyEvent pendingEvent = pendingEvents.get(i);
+                    removeReferenceToPendingEventLocked(pendingEvent);
+                }
+                mPendingEventsMap.remove(service);
+            }
+        }
+    }
+
+    private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) {
+        PendingKeyEvent pendingEvent = mPendingEventPool.acquire();
+        if (pendingEvent == null) {
+            pendingEvent = new PendingKeyEvent();
+        }
+        pendingEvent.event = event;
+        pendingEvent.policyFlags = policyFlags;
+        pendingEvent.referenceCount = 0;
+        pendingEvent.handled = false;
+        return pendingEvent;
+    }
+
+    private static PendingKeyEvent removeEventFromListLocked(
+            List<PendingKeyEvent> listOfEvents, int sequence) {
+        /* In normal operation, the event should be first */
+        for (int i = 0; i < listOfEvents.size(); i++) {
+            PendingKeyEvent pendingKeyEvent = listOfEvents.get(i);
+            if (pendingKeyEvent.event.getSequenceNumber() == sequence) {
+                    /*
+                     * Removing the first element of the ArrayList can be slow if there are a lot
+                     * of events backed up, but for a handful of events it's better than incurring
+                     * the fixed overhead of LinkedList. An ArrayList optimized for removing the
+                     * first element (by treating the underlying array as a circular buffer) would
+                     * be ideal.
+                     */
+                listOfEvents.remove(pendingKeyEvent);
+                return pendingKeyEvent;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param pendingEvent The event whose reference count should be decreased
+     * @return {@code true} if the event was release, {@code false} if not.
+     */
+    private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
+        if (--pendingEvent.referenceCount > 0) {
+            return false;
+        }
+        mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+        if (!pendingEvent.handled) {
+                /* Pass event to input filter */
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event);
+            }
+            if (mSentEventsVerifier != null) {
+                mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
+            }
+            int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER;
+            mHandlerToSendKeyEventsToInputFilter
+                    .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event)
+                            .sendToTarget();
+        } else {
+            pendingEvent.event.recycle();
+        }
+        mPendingEventPool.release(pendingEvent);
+        return true;
+    }
+
+    private static final class PendingKeyEvent {
+        /* Event and policyFlag provided in notifyKeyEventLocked */
+        KeyEvent event;
+        int policyFlags;
+        /*
+         * The referenceCount optimizes the process of determining the number of services
+         * still holding a KeyEvent. It must be equal to the number of times the PendingEvent
+         * appears in mPendingEventsMap, or PendingEvents will leak.
+         */
+        int referenceCount;
+        /* Whether or not at least one service had handled this event */
+        boolean handled;
+    }
+
+    private class Callback implements Handler.Callback {
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
+                throw new IllegalArgumentException("Unknown message: " + message.what);
+            }
+            PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
+            synchronized (mLock) {
+                for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
+                    if (listForService.remove(pendingKeyEvent)) {
+                        if(removeReferenceToPendingEventLocked(pendingKeyEvent)) {
+                            break;
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index b4411cf..a093d92 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -17,20 +17,36 @@
 package com.android.server.accessibility;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
 
 import android.animation.ObjectAnimator;
 import android.animation.TypeEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.MathUtils;
 import android.util.Property;
 import android.util.Slog;
 import android.view.MagnificationSpec;
+import android.view.View;
 import android.view.WindowManagerInternal;
 import android.view.animation.DecelerateInterpolator;
 
+import java.util.Locale;
+
 /**
  * This class is used to control and query the state of display magnification
  * from the accessibility manager and related classes. It is responsible for
@@ -38,37 +54,71 @@
  * communication between the accessibility manager and window manager.
  */
 class MagnificationController {
-    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+    private static final String LOG_TAG = "MagnificationController";
 
     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
-    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
 
-    private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
 
-    private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+
+    private static final float MIN_SCALE = 1.0f;
+    private static final float MAX_SCALE = 5.0f;
+
+    /**
+     * The minimum scaling factor that can be persisted to secure settings.
+     * This must be > 1.0 to ensure that magnification is actually set to an
+     * enabled state when the scaling factor is restored from settings.
+     */
+    private static final float MIN_PERSISTED_SCALE = 2.0f;
+
+    private final Object mLock = new Object();
+
+    /**
+     * The current magnification spec. If an animation is running, this
+     * reflects the end state.
+     */
     private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
 
-    private final Region mMagnifiedBounds = new Region();
+    private final Region mMagnifiedRegion = Region.obtain();
+    private final Region mAvailableRegion = Region.obtain();
+    private final Rect mMagnifiedBounds = new Rect();
+
     private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
 
     private final AccessibilityManagerService mAms;
-    private final WindowManagerInternal mWindowManager;
-    private final ValueAnimator mTransformationAnimator;
+    private final ContentResolver mContentResolver;
+
+    private final ScreenStateObserver mScreenStateObserver;
+    private final WindowStateObserver mWindowStateObserver;
+
+    private final SpecAnimationBridge mSpecAnimationBridge;
+
+    private int mUserId;
 
     public MagnificationController(Context context, AccessibilityManagerService ams) {
         mAms = ams;
-        mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+        mContentResolver = context.getContentResolver();
+        mScreenStateObserver = new ScreenStateObserver(context, this);
+        mWindowStateObserver = new WindowStateObserver(context, this);
+        mSpecAnimationBridge = new SpecAnimationBridge(context);
+    }
 
-        final Property<MagnificationController, MagnificationSpec> property =
-                Property.of(MagnificationController.class, MagnificationSpec.class,
-                        PROPERTY_NAME_MAGNIFICATION_SPEC);
-        final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
-        final long animationDuration = context.getResources().getInteger(
-                R.integer.config_longAnimTime);
-        mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
-                mSentMagnificationSpec, mCurrentMagnificationSpec);
-        mTransformationAnimator.setDuration(animationDuration);
-        mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+    /**
+     * Registers magnification-related observers.
+     */
+    public void register() {
+        mScreenStateObserver.register();
+        mWindowStateObserver.register();
+    }
+
+    /**
+     * Unregisters magnification-related observers.
+     */
+    public void unregister() {
+        mScreenStateObserver.unregister();
+        mWindowStateObserver.unregister();
     }
 
     /**
@@ -80,26 +130,33 @@
     }
 
     /**
-     * Sets the magnified region.
+     * Sets the magnified and available regions.
      *
-     * @param region the region to set
-     *  @param updateSpec {@code true} to update the scale and center based on
-     *                    the region bounds, {@code false} to leave them as-is
+     * @param magnified the magnified region
+     * @param available the region available for magnification
+     * @param updateSpec {@code true} to update the scale and center based on
+     *                   the region bounds, {@code false} to leave them as-is
      */
-    public void setMagnifiedRegion(Region region, boolean updateSpec) {
-        mMagnifiedBounds.set(region);
+    public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) {
+        synchronized (mLock) {
+            mMagnifiedRegion.set(magnified);
+            mMagnifiedRegion.getBounds(mMagnifiedBounds);
+            mAvailableRegion.set(available);
 
-        if (updateSpec) {
-            final Rect magnifiedFrame = mTempRect;
-            region.getBounds(magnifiedFrame);
-            final float scale = mSentMagnificationSpec.scale;
-            final float offsetX = mSentMagnificationSpec.offsetX;
-            final float offsetY = mSentMagnificationSpec.offsetY;
-            final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
-            final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
-            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
-        } else {
-            mAms.onMagnificationStateChanged();
+            final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
+            final float scale = sentSpec.scale;
+            final float offsetX = sentSpec.offsetX;
+            final float offsetY = sentSpec.offsetY;
+
+            // Compute the new center and update spec as needed.
+            final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale;
+            final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale;
+            if (updateSpec) {
+                setScaleAndCenter(scale, centerX, centerY, false);
+            } else {
+                mAms.onMagnificationStateChanged();
+                mAms.notifyMagnificationChanged(mMagnifiedRegion, scale, centerX, centerY);
+            }
         }
     }
 
@@ -113,18 +170,51 @@
      *         magnified region, or {@code false} otherwise
      */
     public boolean magnifiedRegionContains(float x, float y) {
-        return mMagnifiedBounds.contains((int) x, (int) y);
+        synchronized (mLock) {
+            return mMagnifiedRegion.contains((int) x, (int) y);
+        }
     }
 
     /**
-     * Populates the specified rect with the bounds of the magnified
-     * region.
+     * Returns whether the region available for magnification contains the
+     * specified screen-relative coordinates.
+     *
+     * @param x the screen-relative X coordinate to check
+     * @param y the screen-relative Y coordinate to check
+     * @return {@code true} if the coordinate is contained within the
+     *         region available for magnification, or {@code false} otherwise
+     */
+    private boolean availableRegionContains(float x, float y) {
+        synchronized (mLock) {
+            return mAvailableRegion.contains((int) x, (int) y);
+        }
+    }
+
+    /**
+     * Populates the specified rect with the screen-relative bounds of the
+     * magnified region. If magnification is not enabled, the returned
+     * bounds will be empty.
      *
      * @param outBounds rect to populate with the bounds of the magnified
      *                  region
      */
-    public void getMagnifiedBounds(Rect outBounds) {
-        mMagnifiedBounds.getBounds(outBounds);
+    public void getMagnifiedBounds(@NonNull Rect outBounds) {
+        synchronized (mLock) {
+            outBounds.set(mMagnifiedBounds);
+        }
+    }
+
+    /**
+     * Populates the specified region with the screen-relative magnified
+     * region. If magnification is not enabled, then the returned region
+     * will be empty.
+     *
+     * @param outRegion the region to populate
+     */
+    public void getMagnifiedRegion(@NonNull Region outRegion) {
+        synchronized (mLock) {
+            outRegion.set(mMagnifiedRegion);
+        }
     }
 
     /**
@@ -147,6 +237,19 @@
         return mCurrentMagnificationSpec.offsetX;
     }
 
+
+    /**
+     * Returns the screen-relative X coordinate of the center of the
+     * magnification viewport.
+     *
+     * @return the X coordinate
+     */
+    public float getCenterX() {
+        synchronized (mLock) {
+            return  (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale();
+        }
+    }
+
     /**
      * Returns the Y offset of the magnification viewport. If an animation
      * is in progress, this reflects the end state of the animation.
@@ -158,6 +261,18 @@
     }
 
     /**
+     * Returns the screen-relative Y coordinate of the center of the
+     * magnification viewport.
+     *
+     * @return the Y coordinate
+     */
+    public float getCenterY() {
+        synchronized (mLock) {
+            return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale();
+        }
+    }
+
+    /**
      * Returns the scale currently used by the window manager. If an
      * animation is in progress, this reflects the current state of the
      * animation.
@@ -165,7 +280,7 @@
      * @return the scale currently used by the window manager
      */
     public float getSentScale() {
-        return mSentMagnificationSpec.scale;
+        return mSpecAnimationBridge.mSentMagnificationSpec.scale;
     }
 
     /**
@@ -176,7 +291,7 @@
      * @return the X offset currently used by the window manager
      */
     public float getSentOffsetX() {
-        return mSentMagnificationSpec.offsetX;
+        return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
     }
 
     /**
@@ -187,7 +302,7 @@
      * @return the Y offset currently used by the window manager
      */
     public float getSentOffsetY() {
-        return mSentMagnificationSpec.offsetY;
+        return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
     }
 
     /**
@@ -196,21 +311,24 @@
      *
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void reset(boolean animate) {
-        if (mTransformationAnimator.isRunning()) {
-            mTransformationAnimator.cancel();
+    public boolean reset(boolean animate) {
+        synchronized (mLock) {
+            return resetLocked(animate);
         }
-        mCurrentMagnificationSpec.clear();
-        if (animate) {
-            animateMagnificationSpec(mSentMagnificationSpec,
-                    mCurrentMagnificationSpec);
-        } else {
-            setMagnificationSpec(mCurrentMagnificationSpec);
+    }
+
+    private boolean resetLocked(boolean animate) {
+        final MagnificationSpec spec = mCurrentMagnificationSpec;
+        final boolean changed = !spec.isNop();
+        if (changed) {
+            spec.clear();
         }
-        final Rect bounds = mTempRect;
-        bounds.setEmpty();
-        mAms.onMagnificationStateChanged();
+
+        mSpecAnimationBridge.updateSentSpec(spec, animate);
+        return changed;
     }
 
     /**
@@ -219,23 +337,32 @@
      * transition is immediate.
      *
      * @param scale the target scale, must be >= 1
+     * @param pivotX the screen-relative X coordinate around which to scale
+     * @param pivotY the screen-relative Y coordinate around which to scale
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final MagnificationSpec spec = mCurrentMagnificationSpec;
-        final float oldScale = spec.scale;
-        final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
-        final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
-        final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
-        final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
-        final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
-        final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
-        final float centerX = normPivotX + offsetX;
-        final float centerY = normPivotY + offsetY;
-        setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+    public boolean setScale(float scale, float pivotX, float pivotY, boolean animate) {
+        synchronized (mLock) {
+            // Constrain scale immediately for use in the pivot calculations.
+            scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+            final Rect viewport = mTempRect;
+            mMagnifiedRegion.getBounds(viewport);
+            final MagnificationSpec spec = mCurrentMagnificationSpec;
+            final float oldScale = spec.scale;
+            final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale;
+            final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale;
+            final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+            final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+            final float centerX = normPivotX + offsetX;
+            final float centerY = normPivotY + offsetY;
+            return setScaleAndCenterLocked(scale, centerX, centerY, animate);
+        }
     }
 
     /**
@@ -248,10 +375,13 @@
      *                center
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
-        setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
-                animate);
+    public boolean setCenter(float centerX, float centerY, boolean animate) {
+        synchronized (mLock) {
+            return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate);
+        }
     }
 
     /**
@@ -259,35 +389,27 @@
      * animating the transition. If animation is disabled, the transition
      * is immediate.
      *
-     * @param scale the target scale, must be >= 1
+     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to
-     *                center and scale
+     *                center and scale, or {@link Float#NaN} to leave unchanged
      * @param centerY the screen-relative Y coordinate around which to
-     *                center and scale
+     *                center and scale, or {@link Float#NaN} to leave unchanged
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
-            boolean animate) {
-        if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
-                && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
-                && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
-            return;
+    public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) {
+        synchronized (mLock) {
+            return setScaleAndCenterLocked(scale, centerX, centerY, animate);
         }
-        if (mTransformationAnimator.isRunning()) {
-            mTransformationAnimator.cancel();
-        }
-        if (DEBUG_MAGNIFICATION_CONTROLLER) {
-            Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
-        }
-        updateMagnificationSpec(scale, centerX, centerY);
-        if (animate) {
-            animateMagnificationSpec(mSentMagnificationSpec,
-                    mCurrentMagnificationSpec);
-        } else {
-            setMagnificationSpec(mCurrentMagnificationSpec);
-        }
-        mAms.onMagnificationStateChanged();
+    }
+
+    private boolean setScaleAndCenterLocked(
+            float scale, float centerX, float centerY, boolean animate) {
+        final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+        mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+        return changed;
     }
 
     /**
@@ -297,71 +419,504 @@
      * @param offsetY the amount in pixels to offset the Y center
      */
     public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
-        final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
-        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                getMinOffsetX()), 0);
-        final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                getMinOffsetY()), 0);
-        setMagnificationSpec(mCurrentMagnificationSpec);
+        synchronized (mLock) {
+            final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+            final float nonNormOffsetX = currSpec.offsetX - offsetX;
+            currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+            final float nonNormOffsetY = currSpec.offsetY - offsetY;
+            currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+            mSpecAnimationBridge.updateSentSpec(currSpec, false);
+        }
     }
 
-    private void updateMagnificationSpec(float scale, float magnifiedCenterX,
-            float magnifiedCenterY) {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        mCurrentMagnificationSpec.scale = scale;
-        final int viewportWidth = magnifiedFrame.width();
-        final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
-        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                getMinOffsetX()), 0);
-        final int viewportHeight = magnifiedFrame.height();
-        final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
-        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                getMinOffsetY()), 0);
+    /**
+     * Persists the current magnification scale to the current user's settings.
+     */
+    public void persistScale() {
+        final float scale = mCurrentMagnificationSpec.scale;
+        final int userId = mUserId;
+
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                Settings.Secure.putFloatForUser(mContentResolver,
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+                return null;
+            }
+        }.execute();
     }
 
-    private float getMinOffsetX() {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final float viewportWidth = magnifiedFrame.width();
+    /**
+     * Retrieves a previously persisted magnification scale from the current
+     * user's settings.
+     *
+     * @return the previously persisted magnification scale, or the default
+     *         scale if none is available
+     */
+    public float getPersistedScale() {
+        return Settings.Secure.getFloatForUser(mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                DEFAULT_MAGNIFICATION_SCALE, mUserId);
+    }
+
+    /**
+     * Updates the current magnification spec.
+     *
+     * @param scale the magnification scale
+     * @param centerX the unscaled, screen-relative X coordinate of the center
+     *                of the viewport, or {@link Float#NaN} to leave unchanged
+     * @param centerY the unscaled, screen-relative Y coordinate of the center
+     *                of the viewport, or {@link Float#NaN} to leave unchanged
+     * @return {@code true} if the magnification spec changed or {@code false}
+     *         otherwise
+     */
+    private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+        if (!availableRegionContains(centerX, centerY)) {
+            return false;
+        }
+
+        boolean changed = false;
+
+        final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+
+        // Handle scale.
+        if (Float.isNaN(scale)) {
+            scale = getScale();
+        }
+
+        final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+        if (Float.compare(currSpec.scale, normScale) != 0) {
+            currSpec.scale = normScale;
+            changed = true;
+        }
+
+        // Handle X offset.
+        if (Float.isNaN(centerX)) {
+            centerX = getCenterX();
+        }
+
+        final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale;
+        final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+        if (Float.compare(currSpec.offsetX, offsetX) != 0) {
+            currSpec.offsetX = offsetX;
+            changed = true;
+        }
+
+        // Handle Y offset.
+        if (Float.isNaN(centerY)) {
+            centerY = getCenterY();
+        }
+
+        final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale;
+        final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+        if (Float.compare(currSpec.offsetY, offsetY) != 0) {
+            currSpec.offsetY = offsetY;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    private float getMinOffsetXLocked() {
+        final float viewportWidth = mMagnifiedBounds.width();
         return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
     }
 
-    private float getMinOffsetY() {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final float viewportHeight = magnifiedFrame.height();
+    private float getMinOffsetYLocked() {
+        final float viewportHeight = mMagnifiedBounds.height();
         return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
     }
 
-    private void animateMagnificationSpec(MagnificationSpec fromSpec,
-            MagnificationSpec toSpec) {
-        mTransformationAnimator.setObjectValues(fromSpec, toSpec);
-        mTransformationAnimator.start();
-    }
+    /**
+     * Sets the currently active user ID.
+     *
+     * @param userId the currently active user ID
+     */
+    public void setUserId(int userId) {
+        if (mUserId != userId) {
+            mUserId = userId;
 
-    private void setMagnificationSpec(MagnificationSpec spec) {
-        if (DEBUG_SET_MAGNIFICATION_SPEC) {
-            Slog.i(LOG_TAG, "Sending: " + spec);
+            synchronized (mLock) {
+                if (isMagnifying()) {
+                    reset(false);
+                }
+            }
         }
-        mSentMagnificationSpec.scale = spec.scale;
-        mSentMagnificationSpec.offsetX = spec.offsetX;
-        mSentMagnificationSpec.offsetY = spec.offsetY;
-        mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
     }
 
-    private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
-        private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+    private boolean isScreenMagnificationAutoUpdateEnabled() {
+        return (Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
+    }
+
+    /**
+     * Resets magnification if magnification and auto-update are both enabled.
+     *
+     * @param animate whether the animate the transition
+     * @return {@code true} if magnification was reset to the disabled state,
+     *         {@code false} if magnification is still active
+     */
+    boolean resetIfNeeded(boolean animate) {
+        synchronized (mLock) {
+            if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+                reset(animate);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
+        final float scale = getSentScale();
+        final float offsetX = getSentOffsetX();
+        final float offsetY = getSentOffsetY();
+        getMagnifiedBounds(outFrame);
+        outFrame.offset((int) -offsetX, (int) -offsetY);
+        outFrame.scale(1.0f / scale);
+    }
+
+    private void requestRectangleOnScreen(int left, int top, int right, int bottom) {
+        synchronized (mLock) {
+            final Rect magnifiedFrame = mTempRect;
+            getMagnifiedBounds(magnifiedFrame);
+            if (!magnifiedFrame.intersects(left, top, right, bottom)) {
+                return;
+            }
+
+            final Rect magnifFrameInScreenCoords = mTempRect1;
+            getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
+
+            final float scrollX;
+            final float scrollY;
+            if (right - left > magnifFrameInScreenCoords.width()) {
+                final int direction = TextUtils
+                        .getLayoutDirectionFromLocale(Locale.getDefault());
+                if (direction == View.LAYOUT_DIRECTION_LTR) {
+                    scrollX = left - magnifFrameInScreenCoords.left;
+                } else {
+                    scrollX = right - magnifFrameInScreenCoords.right;
+                }
+            } else if (left < magnifFrameInScreenCoords.left) {
+                scrollX = left - magnifFrameInScreenCoords.left;
+            } else if (right > magnifFrameInScreenCoords.right) {
+                scrollX = right - magnifFrameInScreenCoords.right;
+            } else {
+                scrollX = 0;
+            }
+
+            if (bottom - top > magnifFrameInScreenCoords.height()) {
+                scrollY = top - magnifFrameInScreenCoords.top;
+            } else if (top < magnifFrameInScreenCoords.top) {
+                scrollY = top - magnifFrameInScreenCoords.top;
+            } else if (bottom > magnifFrameInScreenCoords.bottom) {
+                scrollY = bottom - magnifFrameInScreenCoords.bottom;
+            } else {
+                scrollY = 0;
+            }
+
+            final float scale = getScale();
+            offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+        }
+    }
+
+    /**
+     * Class responsible for animating spec on the main thread and sending spec
+     * updates to the window manager.
+     */
+    private static class SpecAnimationBridge {
+        private static final int ACTION_UPDATE_SPEC = 1;
+
+        private final Handler mHandler;
+        private final WindowManagerInternal mWindowManager;
+
+        /**
+         * The magnification spec that was sent to the window manager. This should
+         * only be accessed and modified on the main (e.g. animation) thread.
+         */
+        private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+
+        /**
+         * The animator that updates the sent spec. This should only be accessed
+         * and modified on the main (e.g. animation) thread.
+         */
+        private final ValueAnimator mTransformationAnimator;
+
+        private final long mMainThreadId;
+
+        private SpecAnimationBridge(Context context) {
+            final Looper mainLooper = context.getMainLooper();
+            mMainThreadId = mainLooper.getThread().getId();
+
+            mHandler = new UpdateHandler(context);
+            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+            final MagnificationSpecProperty property = new MagnificationSpecProperty();
+            final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+            final long animationDuration = context.getResources().getInteger(
+                    R.integer.config_longAnimTime);
+            mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+                    mSentMagnificationSpec);
+            mTransformationAnimator.setDuration(animationDuration);
+            mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+        }
+
+        public void updateSentSpec(MagnificationSpec spec, boolean animate) {
+            if (Thread.currentThread().getId() == mMainThreadId) {
+                // Already on the main thread, don't bother proxying.
+                updateSentSpecInternal(spec, animate);
+            } else {
+                mHandler.obtainMessage(ACTION_UPDATE_SPEC,
+                        animate ? 1 : 0, 0, spec).sendToTarget();
+            }
+        }
+
+        /**
+         * Updates the sent spec.
+         */
+        private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
+            if (mTransformationAnimator.isRunning()) {
+                mTransformationAnimator.cancel();
+            }
+
+            // If the current and sent specs don't match, update the sent spec.
+            final boolean changed = !mSentMagnificationSpec.equals(spec);
+            if (changed) {
+                if (animate) {
+                    animateMagnificationSpec(spec);
+                } else {
+                    setMagnificationSpec(spec);
+                }
+            }
+        }
+
+        private void animateMagnificationSpec(MagnificationSpec toSpec) {
+            mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
+            mTransformationAnimator.start();
+        }
+
+        private void setMagnificationSpec(MagnificationSpec spec) {
+            if (DEBUG_SET_MAGNIFICATION_SPEC) {
+                Slog.i(LOG_TAG, "Sending: " + spec);
+            }
+
+            mSentMagnificationSpec.setTo(spec);
+            mWindowManager.setMagnificationSpec(spec);
+        }
+
+        private class UpdateHandler extends Handler {
+            public UpdateHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ACTION_UPDATE_SPEC:
+                        final boolean animate = msg.arg1 == 1;
+                        final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+                        updateSentSpecInternal(spec, animate);
+                        break;
+                }
+            }
+        }
+
+        private static class MagnificationSpecProperty
+                extends Property<SpecAnimationBridge, MagnificationSpec> {
+            public MagnificationSpecProperty() {
+                super(MagnificationSpec.class, "spec");
+            }
+
+            @Override
+            public MagnificationSpec get(SpecAnimationBridge object) {
+                return object.mSentMagnificationSpec;
+            }
+
+            @Override
+            public void set(SpecAnimationBridge object, MagnificationSpec value) {
+                object.setMagnificationSpec(value);
+            }
+        }
+
+        private static class MagnificationSpecEvaluator
+                implements TypeEvaluator<MagnificationSpec> {
+            private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
+
+            @Override
+            public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+                    MagnificationSpec toSpec) {
+                final MagnificationSpec result = mTempSpec;
+                result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+                result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+                result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+                return result;
+            }
+        }
+    }
+
+    private static class ScreenStateObserver extends BroadcastReceiver {
+        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
+
+        private final Context mContext;
+        private final MagnificationController mController;
+        private final Handler mHandler;
+
+        public ScreenStateObserver(Context context, MagnificationController controller) {
+            mContext = context;
+            mController = controller;
+            mHandler = new StateChangeHandler(context);
+        }
+
+        public void register() {
+            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+        }
+
+        public void unregister() {
+            mContext.unregisterReceiver(this);
+        }
 
         @Override
-        public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
-                MagnificationSpec toSpec) {
-            final MagnificationSpec result = mTempTransformationSpec;
-            result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
-            result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
-            result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
-            return result;
+        public void onReceive(Context context, Intent intent) {
+            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
+                    intent.getAction()).sendToTarget();
+        }
+
+        private void handleOnScreenStateChange() {
+            mController.resetIfNeeded(false);
+        }
+
+        private class StateChangeHandler extends Handler {
+            public StateChangeHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_ON_SCREEN_STATE_CHANGE:
+                        handleOnScreenStateChange();
+                        break;
+                }
+            }
+        }
+    }
+
+    /**
+     * This class handles the screen magnification when accessibility is enabled.
+     */
+    private static class WindowStateObserver
+            implements WindowManagerInternal.MagnificationCallbacks {
+        private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
+        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+        private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
+        private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+
+        private final Rect mTempRect = new Rect();
+        private final Rect mTempRect1 = new Rect();
+
+        private final MagnificationController mController;
+        private final WindowManagerInternal mWindowManager;
+        private final Handler mHandler;
+
+        private boolean mSpecIsDirty;
+
+        public WindowStateObserver(Context context, MagnificationController controller) {
+            mController = controller;
+            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+            mHandler = new CallbackHandler(context);
+        }
+
+        public void register() {
+            mWindowManager.setMagnificationCallbacks(this);
+        }
+
+        public void unregister() {
+            mWindowManager.setMagnificationCallbacks(null);
+        }
+
+        @Override
+        public void onMagnifiedBoundsChanged(Region magnified, Region available) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = Region.obtain(magnified);
+            args.arg2 = Region.obtain(available);
+            mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+        }
+
+        private void handleOnMagnifiedBoundsChanged(Region magnified, Region available) {
+            mController.setMagnifiedRegion(magnified, available, mSpecIsDirty);
+            mSpecIsDirty = false;
+        }
+
+        @Override
+        public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = left;
+            args.argi2 = top;
+            args.argi3 = right;
+            args.argi4 = bottom;
+            mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
+        }
+
+        private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+            mController.requestRectangleOnScreen(left, top, right, bottom);
+        }
+
+        @Override
+        public void onRotationChanged(int rotation) {
+            mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+        }
+
+        private void handleOnRotationChanged() {
+            // If there was a rotation and magnification is still enabled,
+            // we'll need to rewrite the spec to reflect the new screen
+            // configuration. Conveniently, we'll receive a callback from
+            // the window manager with updated bounds for the magnified
+            // region.
+            mSpecIsDirty = !mController.resetIfNeeded(true);
+        }
+
+        @Override
+        public void onUserContextChanged() {
+            mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+        }
+
+        private void handleOnUserContextChanged() {
+            mController.resetIfNeeded(true);
+        }
+
+        private class CallbackHandler extends Handler {
+            public CallbackHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region magnifiedBounds = (Region) args.arg1;
+                        final Region availableBounds = (Region) args.arg2;
+                        handleOnMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+                        magnifiedBounds.recycle();
+                        availableBounds.recycle();
+                    } break;
+                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final int left = args.argi1;
+                        final int top = args.argi2;
+                        final int right = args.argi3;
+                        final int bottom = args.argi4;
+                        handleOnRectangleOnScreenRequested(left, top, right, bottom);
+                        args.recycle();
+                    } break;
+                    case MESSAGE_ON_USER_CONTEXT_CHANGED: {
+                        handleOnUserContextChanged();
+                    } break;
+                    case MESSAGE_ON_ROTATION_CHANGED: {
+                        handleOnRotationChanged();
+                    } break;
+                }
+            }
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
similarity index 65%
rename from services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
rename to services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 8feb167..51c8ab5 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,18 +16,10 @@
 
 package com.android.server.accessibility;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.GestureDetector;
@@ -39,18 +31,12 @@
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.internal.os.SomeArgs;
-import com.android.server.LocalServices;
-
-import java.util.Locale;
-
 /**
- * This class handles the screen magnification when accessibility is enabled.
+ * This class handles magnification in response to touch events.
+ *
  * The behavior is as follows:
  *
  * 1. Triple tap toggles permanent screen magnification which is magnifying
@@ -88,10 +74,8 @@
  *
  * 6. The magnification scale will be persisted in settings and in the cloud.
  */
-public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks,
-        EventStreamTransformation {
-
-    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+class MagnificationGestureHandler implements EventStreamTransformation {
+    private static final String LOG_TAG = "MagnificationEventHandler";
 
     private static final boolean DEBUG_STATE_TRANSITIONS = false;
     private static final boolean DEBUG_DETECTING = false;
@@ -103,40 +87,19 @@
     private static final int STATE_VIEWPORT_DRAGGING = 3;
     private static final int STATE_MAGNIFIED_INTERACTION = 4;
 
-    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+    private static final float MIN_SCALE = 2.0f;
+    private static final float MAX_SCALE = 5.0f;
 
-    private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
-    private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
-    private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
-    private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
-
-    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
-
-    private static final int MY_PID = android.os.Process.myPid();
-
-    private final Rect mTempRect = new Rect();
-    private final Rect mTempRect1 = new Rect();
-
-    private final Context mContext;
-    private final WindowManagerInternal mWindowManager;
     private final MagnificationController mMagnificationController;
-    private final ScreenStateObserver mScreenStateObserver;
-
     private final DetectingStateHandler mDetectingStateHandler;
-    private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+    private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
     private final StateViewportDraggingHandler mStateViewportDraggingHandler;
 
-    private final int mUserId;
-
-    private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
-    private final int mMultiTapTimeSlop;
-    private final int mTapDistanceSlop;
-    private final int mMultiTapDistanceSlop;
-
     private EventStreamTransformation mNext;
 
     private int mCurrentState;
     private int mPreviousState;
+
     private boolean mTranslationEnabledBeforePan;
 
     private PointerCoords[] mTempPointerCoords;
@@ -144,189 +107,44 @@
 
     private long mDelegatingStateDownTime;
 
-    private boolean mUpdateMagnificationSpecOnNextBoundsChange;
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
-                    Region bounds = (Region) message.obj;
-                    handleOnMagnifiedBoundsChanged(bounds);
-                    bounds.recycle();
-                } break;
-                case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
-                    SomeArgs args = (SomeArgs) message.obj;
-                    final int left = args.argi1;
-                    final int top = args.argi2;
-                    final int right = args.argi3;
-                    final int bottom = args.argi4;
-                    handleOnRectangleOnScreenRequested(left, top, right, bottom);
-                    args.recycle();
-                } break;
-                case MESSAGE_ON_USER_CONTEXT_CHANGED: {
-                    handleOnUserContextChanged();
-                } break;
-                case MESSAGE_ON_ROTATION_CHANGED: {
-                    final int rotation = message.arg1;
-                    handleOnRotationChanged(rotation);
-                } break;
-            }
-        }
-    };
-
-    public ScreenMagnifier(Context context, int userId, int displayId,
-            AccessibilityManagerService service) {
-        mContext = context;
-        mUserId = userId;
-        mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
-        mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
-                + mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
-        mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
-        mDetectingStateHandler = new DetectingStateHandler();
+    public MagnificationGestureHandler(Context context, AccessibilityManagerService ams) {
+        mMagnificationController = ams.getMagnificationController();
+        mDetectingStateHandler = new DetectingStateHandler(context);
         mStateViewportDraggingHandler = new StateViewportDraggingHandler();
-        mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
-                context);
-
-        mMagnificationController = service.getMagnificationController();
-        mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
-
-        mWindowManager.setMagnificationCallbacks(this);
+        mMagnifiedContentInteractionStateHandler =
+                new MagnifiedContentInteractionStateHandler(context);
 
         transitionToState(STATE_DETECTING);
     }
 
     @Override
-    public void onMagnifedBoundsChanged(Region bounds) {
-        Region newBounds = Region.obtain(bounds);
-        mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
-        if (MY_PID != Binder.getCallingPid()) {
-            bounds.recycle();
-        }
-    }
-
-    private void handleOnMagnifiedBoundsChanged(Region bounds) {
-        // If there was a rotation we have to update the center of the magnified
-        // region since the old offset X/Y may be out of its acceptable range for
-        // the new display width and height.
-        mMagnificationController.setMagnifiedRegion(
-                bounds, mUpdateMagnificationSpecOnNextBoundsChange);
-        mUpdateMagnificationSpecOnNextBoundsChange = false;
-    }
-
-    @Override
-    public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-        SomeArgs args = SomeArgs.obtain();
-        args.argi1 = left;
-        args.argi2 = top;
-        args.argi3 = right;
-        args.argi4 = bottom;
-        mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
-    }
-
-    private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-        Rect magnifiedFrame = mTempRect;
-        mMagnificationController.getMagnifiedBounds(magnifiedFrame);
-        if (!magnifiedFrame.intersects(left, top, right, bottom)) {
-            return;
-        }
-        Rect magnifFrameInScreenCoords = mTempRect1;
-        getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
-        final float scrollX;
-        final float scrollY;
-        if (right - left > magnifFrameInScreenCoords.width()) {
-            final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
-            if (direction == View.LAYOUT_DIRECTION_LTR) {
-                scrollX = left - magnifFrameInScreenCoords.left;
-            } else {
-                scrollX = right - magnifFrameInScreenCoords.right;
-            }
-        } else if (left < magnifFrameInScreenCoords.left) {
-            scrollX = left - magnifFrameInScreenCoords.left;
-        } else if (right > magnifFrameInScreenCoords.right) {
-            scrollX = right - magnifFrameInScreenCoords.right;
-        } else {
-            scrollX = 0;
-        }
-        if (bottom - top > magnifFrameInScreenCoords.height()) {
-            scrollY = top - magnifFrameInScreenCoords.top;
-        } else if (top < magnifFrameInScreenCoords.top) {
-            scrollY = top - magnifFrameInScreenCoords.top;
-        } else if (bottom > magnifFrameInScreenCoords.bottom) {
-            scrollY = bottom - magnifFrameInScreenCoords.bottom;
-        } else {
-            scrollY = 0;
-        }
-        final float scale = mMagnificationController.getScale();
-        mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
-    }
-
-    @Override
-    public void onRotationChanged(int rotation) {
-        mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
-    }
-
-    private void handleOnRotationChanged(int rotation) {
-        resetMagnificationIfNeeded();
-        if (mMagnificationController.isMagnifying()) {
-            mUpdateMagnificationSpecOnNextBoundsChange = true;
-        }
-    }
-
-    @Override
-    public void onUserContextChanged() {
-        mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
-    }
-
-    private void handleOnUserContextChanged() {
-        resetMagnificationIfNeeded();
-    }
-
-    private void getMagnifiedFrameInContentCoords(Rect rect) {
-        final float scale = mMagnificationController.getSentScale();
-        final float offsetX = mMagnificationController.getSentOffsetX();
-        final float offsetY = mMagnificationController.getSentOffsetY();
-        mMagnificationController.getMagnifiedBounds(rect);
-        rect.offset((int) -offsetX, (int) -offsetY);
-        rect.scale(1.0f / scale);
-    }
-
-    private void resetMagnificationIfNeeded() {
-        if (mMagnificationController.isMagnifying()
-                && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-            mMagnificationController.reset(true);
-        }
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
-            int policyFlags) {
+    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
             if (mNext != null) {
                 mNext.onMotionEvent(event, rawEvent, policyFlags);
             }
             return;
         }
-        mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
+        mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
         switch (mCurrentState) {
             case STATE_DELEGATING: {
                 handleMotionEventStateDelegating(event, rawEvent, policyFlags);
-            } break;
+            }
+            break;
             case STATE_DETECTING: {
                 mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            } break;
+            }
+            break;
             case STATE_VIEWPORT_DRAGGING: {
-                mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
-            } break;
+                mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
+            }
+            break;
             case STATE_MAGNIFIED_INTERACTION: {
                 // mMagnifiedContentInteractonStateHandler handles events only
                 // if this is the current state since it uses ScaleGestureDetecotr
                 // and a GestureDetector which need well formed event stream.
-            } break;
+            }
+            break;
             default: {
                 throw new IllegalStateException("Unknown state: " + mCurrentState);
             }
@@ -336,7 +154,7 @@
     @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
         if (mNext != null) {
-          mNext.onKeyEvent(event, policyFlags);
+            mNext.onKeyEvent(event, policyFlags);
         }
     }
 
@@ -366,15 +184,13 @@
     @Override
     public void onDestroy() {
         clear();
-        mScreenStateObserver.destroy();
-        mWindowManager.setMagnificationCallbacks(null);
     }
 
     private void clear() {
         mCurrentState = STATE_DETECTING;
         mDetectingStateHandler.clear();
         mStateViewportDraggingHandler.clear();
-        mMagnifiedContentInteractonStateHandler.clear();
+        mMagnifiedContentInteractionStateHandler.clear();
     }
 
     private void handleMotionEventStateDelegating(MotionEvent event,
@@ -382,12 +198,14 @@
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 mDelegatingStateDownTime = event.getDownTime();
-            } break;
+            }
+            break;
             case MotionEvent.ACTION_UP: {
                 if (mDetectingStateHandler.mDelayedEventQueue == null) {
                     transitionToState(STATE_DETECTING);
                 }
-            } break;
+            }
+            break;
         }
         if (mNext != null) {
             // If the event is within the magnified portion of the screen we have
@@ -402,7 +220,8 @@
                 final float scaledOffsetY = mMagnificationController.getOffsetY();
                 final int pointerCount = event.getPointerCount();
                 PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
-                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(
+                        pointerCount);
                 for (int i = 0; i < pointerCount; i++) {
                     event.getPointerCoords(i, coords[i]);
                     coords[i].x = (coords[i].x - scaledOffsetX) / scale;
@@ -441,12 +260,14 @@
     }
 
     private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
-        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
+        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
+                : 0;
         if (oldSize < size) {
             PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
             mTempPointerProperties = new PointerProperties[size];
             if (oldTempPointerProperties != null) {
-                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
+                        oldSize);
             }
         }
         for (int i = oldSize; i < size; i++) {
@@ -460,16 +281,20 @@
             switch (state) {
                 case STATE_DELEGATING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
-                } break;
+                }
+                break;
                 case STATE_DETECTING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
-                } break;
+                }
+                break;
                 case STATE_VIEWPORT_DRAGGING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
-                } break;
+                }
+                break;
                 case STATE_MAGNIFIED_INTERACTION: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown state: " + state);
                 }
@@ -479,20 +304,30 @@
         mCurrentState = state;
     }
 
-    private final class MagnifiedContentInteractonStateHandler
-            extends SimpleOnGestureListener implements OnScaleGestureListener {
-        private static final float MIN_SCALE = 1.3f;
-        private static final float MAX_SCALE = 5.0f;
+    private interface MotionEventHandler {
+
+        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+        void clear();
+    }
+
+    /**
+     * This class determines if the user is performing a scale or pan gesture.
+     */
+    private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
+            implements OnScaleGestureListener, MotionEventHandler {
 
         private final ScaleGestureDetector mScaleGestureDetector;
+
         private final GestureDetector mGestureDetector;
 
         private final float mScalingThreshold;
 
         private float mInitialScaleFactor = -1;
+
         private boolean mScaling;
 
-        public MagnifiedContentInteractonStateHandler(Context context) {
+        public MagnifiedContentInteractionStateHandler(Context context) {
             final TypedValue scaleValue = new TypedValue();
             context.getResources().getValue(
                     com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -503,7 +338,8 @@
             mGestureDetector = new GestureDetector(context, this);
         }
 
-        public void onMotionEvent(MotionEvent event) {
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             mScaleGestureDetector.onTouchEvent(event);
             mGestureDetector.onTouchEvent(event);
             if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
@@ -511,11 +347,7 @@
             }
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                 clear();
-                final float scale = Math.min(Math.max(mMagnificationController.getScale(),
-                        MIN_SCALE), MAX_SCALE);
-                if (scale != getPersistedScale()) {
-                    persistScale(scale);
-                }
+                mMagnificationController.persistScale();
                 if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
                     transitionToState(STATE_VIEWPORT_DRAGGING);
                 } else {
@@ -552,14 +384,29 @@
                 }
                 return false;
             }
-            final float newScale = mMagnificationController.getScale()
-                    * detector.getScaleFactor();
-            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
-            if (DEBUG_SCALING) {
-                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
+
+            final float initialScale = mMagnificationController.getScale();
+            final float targetScale = initialScale * detector.getScaleFactor();
+
+            // Don't allow a gesture to move the user further outside the
+            // desired bounds for gesture-controlled scaling.
+            final float scale;
+            if (targetScale > MAX_SCALE && targetScale > initialScale) {
+                // The target scale is too big and getting bigger.
+                scale = MAX_SCALE;
+            } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
+                // The target scale is too small and getting smaller.
+                scale = MIN_SCALE;
+            } else {
+                // The target scale may be outside our bounds, but at least
+                // it's moving in the right direction. This avoids a "jump" if
+                // we're at odds with some other service's desired bounds.
+                scale = targetScale;
             }
-            mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
-                    detector.getFocusY(), false);
+
+            final float pivotX = detector.getFocusX();
+            final float pivotY = detector.getFocusY();
+            mMagnificationController.setScale(scale, pivotX, pivotY, false);
             return true;
         }
 
@@ -573,16 +420,24 @@
             clear();
         }
 
-        private void clear() {
+        @Override
+        public void clear() {
             mInitialScaleFactor = -1;
             mScaling = false;
         }
     }
 
-    private final class StateViewportDraggingHandler {
+    /**
+     * This class handles motion events when the event dispatcher has
+     * determined that the user is performing a single-finger drag of the
+     * magnification viewport.
+     */
+    private final class StateViewportDraggingHandler implements MotionEventHandler {
+
         private boolean mLastMoveOutsideMagnifiedRegion;
 
-        private void onMotionEvent(MotionEvent event, int policyFlags) {
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             final int action = event.getActionMasked();
             switch (action) {
                 case MotionEvent.ACTION_DOWN: {
@@ -591,7 +446,8 @@
                 case MotionEvent.ACTION_POINTER_DOWN: {
                     clear();
                     transitionToState(STATE_MAGNIFIED_INTERACTION);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_MOVE: {
                     if (event.getPointerCount() != 1) {
                         throw new IllegalStateException("Should have one pointer down.");
@@ -601,35 +457,43 @@
                     if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
                         if (mLastMoveOutsideMagnifiedRegion) {
                             mLastMoveOutsideMagnifiedRegion = false;
-                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                            mMagnificationController.setCenter(eventX,
                                     eventY, true);
                         } else {
-                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                            mMagnificationController.setCenter(eventX,
                                     eventY, false);
                         }
                     } else {
                         mLastMoveOutsideMagnifiedRegion = true;
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_UP: {
                     if (!mTranslationEnabledBeforePan) {
                         mMagnificationController.reset(true);
                     }
                     clear();
                     transitionToState(STATE_DETECTING);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_UP: {
-                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
+                    throw new IllegalArgumentException(
+                            "Unexpected event type: ACTION_POINTER_UP");
                 }
             }
         }
 
+        @Override
         public void clear() {
             mLastMoveOutsideMagnifiedRegion = false;
         }
     }
 
-    private final class DetectingStateHandler {
+    /**
+     * This class handles motion events when the event dispatch has not yet
+     * determined what the user is doing. It watches for various tap events.
+     */
+    private final class DetectingStateHandler implements MotionEventHandler {
 
         private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
 
@@ -637,12 +501,30 @@
 
         private static final int ACTION_TAP_COUNT = 3;
 
+        private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
+
+        private final int mMultiTapTimeSlop;
+
+        private final int mTapDistanceSlop;
+
+        private final int mMultiTapDistanceSlop;
+
         private MotionEventInfo mDelayedEventQueue;
 
         private MotionEvent mLastDownEvent;
+
         private MotionEvent mLastTapUpEvent;
+
         private int mTapCount;
 
+        public DetectingStateHandler(Context context) {
+            mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+                    + context.getResources().getInteger(
+                    com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
+            mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+            mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        }
+
         private final Handler mHandler = new Handler() {
             @Override
             public void handleMessage(Message message) {
@@ -652,12 +534,14 @@
                         MotionEvent event = (MotionEvent) message.obj;
                         final int policyFlags = message.arg1;
                         onActionTapAndHold(event, policyFlags);
-                    } break;
+                    }
+                    break;
                     case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
                         transitionToState(STATE_DELEGATING);
                         sendDelayedMotionEvents();
                         clear();
-                    } break;
+                    }
+                    break;
                     default: {
                         throw new IllegalArgumentException("Unknown message type: " + type);
                     }
@@ -665,6 +549,7 @@
             }
         };
 
+        @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             cacheDelayedMotionEvent(event, rawEvent, policyFlags);
             final int action = event.getActionMasked();
@@ -678,7 +563,7 @@
                     }
                     if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
                             && GestureUtils.isMultiTap(mLastDownEvent, event,
-                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+                            mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
                         Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
                                 policyFlags, 0, event);
                         mHandler.sendMessageDelayed(message,
@@ -690,7 +575,8 @@
                     }
                     clearLastDownEvent();
                     mLastDownEvent = MotionEvent.obtain(event);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_DOWN: {
                     if (mMagnificationController.isMagnifying()) {
                         transitionToState(STATE_MAGNIFIED_INTERACTION);
@@ -698,7 +584,8 @@
                     } else {
                         transitionToDelegatingStateAndClear();
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_MOVE: {
                     if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
                         final double distance = GestureUtils.computeDistance(mLastDownEvent,
@@ -707,7 +594,8 @@
                             transitionToDelegatingStateAndClear();
                         }
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_UP: {
                     if (mLastDownEvent == null) {
                         return;
@@ -715,8 +603,8 @@
                     mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
                     if (!mMagnificationController.magnifiedRegionContains(
                             event.getX(), event.getY())) {
-                         transitionToDelegatingStateAndClear();
-                         return;
+                        transitionToDelegatingStateAndClear();
+                        return;
                     }
                     if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
                             mTapDistanceSlop, 0)) {
@@ -739,13 +627,16 @@
                     }
                     clearLastTapUpEvent();
                     mLastTapUpEvent = MotionEvent.obtain(event);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_UP: {
                     /* do nothing */
-                } break;
+                }
+                break;
             }
         }
 
+        @Override
         public void clear() {
             mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
             mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
@@ -792,7 +683,7 @@
             while (mDelayedEventQueue != null) {
                 MotionEventInfo info = mDelayedEventQueue;
                 mDelayedEventQueue = info.mNext;
-                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
+                MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent,
                         info.mPolicyFlags);
                 info.recycle();
             }
@@ -816,9 +707,11 @@
             if (DEBUG_DETECTING) {
                 Slog.i(LOG_TAG, "onActionTap()");
             }
+
             if (!mMagnificationController.isMagnifying()) {
-                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
-                        up.getX(), up.getY(), true);
+                final float targetScale = mMagnificationController.getPersistedScale();
+                final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+                mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true);
             } else {
                 mMagnificationController.reset(true);
             }
@@ -828,50 +721,36 @@
             if (DEBUG_DETECTING) {
                 Slog.i(LOG_TAG, "onActionTapAndHold()");
             }
+
             clear();
             mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
-            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
-                    down.getX(), down.getY(), true);
+
+            final float targetScale = mMagnificationController.getPersistedScale();
+            final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+            mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true);
+
             transitionToState(STATE_VIEWPORT_DRAGGING);
         }
     }
 
-    private void persistScale(final float scale) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
-                return null;
-            }
-        }.execute();
-    }
-
-    private float getPersistedScale() {
-        return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                DEFAULT_MAGNIFICATION_SCALE, mUserId);
-    }
-
-    private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
-        return (Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
-                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
-    }
-
     private static final class MotionEventInfo {
 
         private static final int MAX_POOL_SIZE = 10;
 
         private static final Object sLock = new Object();
+
         private static MotionEventInfo sPool;
+
         private static int sPoolSize;
 
         private MotionEventInfo mNext;
+
         private boolean mInPool;
 
         public MotionEvent mEvent;
+
         public MotionEvent mRawEvent;
+
         public int mPolicyFlags;
 
         public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
@@ -922,47 +801,4 @@
             mPolicyFlags = 0;
         }
     }
-
-    private final class ScreenStateObserver extends BroadcastReceiver {
-        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
-        private final Context mContext;
-        private final MagnificationController mMagnificationController;
-
-        private final Handler mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message message) {
-                 switch (message.what) {
-                    case MESSAGE_ON_SCREEN_STATE_CHANGE: {
-                        String action = (String) message.obj;
-                        handleOnScreenStateChange(action);
-                    } break;
-                }
-            }
-        };
-
-        public ScreenStateObserver(Context context,
-                MagnificationController magnificationController) {
-            mContext = context;
-            mMagnificationController = magnificationController;
-            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
-        }
-
-        public void destroy() {
-            mContext.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
-                    intent.getAction()).sendToTarget();
-        }
-
-        private void handleOnScreenStateChange(String action) {
-            if (mMagnificationController.isMagnifying()
-                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-                mMagnificationController.reset(false);
-            }
-        }
-    }
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 02b5b8a..25fef18 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -79,10 +79,11 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
-
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
+
 import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -1954,8 +1955,13 @@
                 if (period < MIN_UPDATE_PERIOD) {
                     period = MIN_UPDATE_PERIOD;
                 }
-                mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                        SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+                final long oldId = Binder.clearCallingIdentity();
+                try {
+                    mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                            SystemClock.elapsedRealtime() + period, period, provider.broadcast);
+                } finally {
+                    Binder.restoreCallingIdentity(oldId);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a5ddc12..c5ce2fd 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -25,6 +25,7 @@
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
+import android.app.IUidObserver;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -115,6 +117,7 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
@@ -458,7 +461,7 @@
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
-            for (int i = 0; i < alarms.size(); ) {
+            for (int i = alarms.size()-1; i >= 0; i--) {
                 Alarm alarm = alarms.get(i);
                 if (alarm.matches(packageName)) {
                     alarms.remove(i);
@@ -474,7 +477,42 @@
                         newEnd = alarm.maxWhenElapsed;
                     }
                     newFlags |= alarm.flags;
-                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+                flags = newFlags;
+            }
+            return didRemove;
+        }
+
+        boolean removeForStopped(final int uid) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            int newFlags = 0;
+            for (int i = alarms.size()-1; i >= 0; i--) {
+                Alarm alarm = alarms.get(i);
+                try {
+                    if (alarm.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                            uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                        alarms.remove(i);
+                        didRemove = true;
+                        if (alarm.alarmClock != null) {
+                            mNextAlarmClockMayChange = true;
+                        }
+                    } else {
+                        if (alarm.whenElapsed > newStart) {
+                            newStart = alarm.whenElapsed;
+                        }
+                        if (alarm.maxWhenElapsed < newEnd) {
+                            newEnd = alarm.maxWhenElapsed;
+                        }
+                        newFlags |= alarm.flags;
+                    }
+                } catch (RemoteException e) {
                 }
             }
             if (didRemove) {
@@ -889,6 +927,13 @@
             Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
         }
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(new UidObserver(),
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
 
@@ -897,6 +942,8 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             mConstants.start(getContext().getContentResolver());
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+            mLocalDeviceIdleController
+                    = LocalServices.getService(DeviceIdleController.LocalService.class);
         }
     }
 
@@ -1032,6 +1079,15 @@
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                 callingUid, callingPackage);
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(callingUid, callingPackage)
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
         removeLocked(operation, directReceiver);
         setImplLocked(a, false, doValidate);
     }
@@ -1838,6 +1894,37 @@
         }
     }
 
+    void removeForStoppedLocked(int uid) {
+        boolean didRemove = false;
+        for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+            Batch b = mAlarmBatches.get(i);
+            didRemove |= b.removeForStopped(uid);
+            if (b.size() == 0) {
+                mAlarmBatches.remove(i);
+            }
+        }
+        for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            try {
+                if (a.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                        uid, a.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    mPendingWhileIdleAlarms.remove(i);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(package) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
+        }
+    }
+
     void removeUserLocked(int userHandle) {
         boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2468,10 +2555,9 @@
 
     private class AlarmHandler extends Handler {
         public static final int ALARM_EVENT = 1;
-        public static final int MINUTE_CHANGE_EVENT = 2;
-        public static final int DATE_CHANGE_EVENT = 3;
-        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
-        public static final int LISTENER_TIMEOUT = 5;
+        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
+        public static final int LISTENER_TIMEOUT = 3;
+        public static final int REPORT_ALARMS_ACTIVE = 4;
         
         public AlarmHandler() {
         }
@@ -2511,6 +2597,12 @@
                     mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
                     break;
 
+                case REPORT_ALARMS_ACTIVE:
+                    if (mLocalDeviceIdleController != null) {
+                        mLocalDeviceIdleController.setAlarmsActive(msg.arg1 != 0);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -2665,7 +2757,24 @@
             }
         }
     }
-    
+
+    final class UidObserver extends IUidObserver.Stub {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            synchronized (mLock) {
+                removeForStoppedLocked(uid);
+            }
+        }
+    };
+
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
@@ -2740,6 +2849,7 @@
             }
             mBroadcastRefCount--;
             if (mBroadcastRefCount == 0) {
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
                 mWakeLock.release();
                 if (mInFlight.size() > 0) {
                     mLog.w("Finished all dispatches with " + mInFlight.size()
@@ -2873,6 +2983,7 @@
                         alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
                         true);
                 mWakeLock.acquire();
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
             }
             final InFlight inflight = new InFlight(AlarmManagerService.this,
                     alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 96c1e2a..a5cef1a 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -47,14 +47,15 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.os.storage.MountServiceInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -557,12 +558,12 @@
                 ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
                 try {
                     if (reportedPackageNames == null) {
-                        callback.mCallback.opChanged(code, null);
+                        callback.mCallback.opChanged(code, uid, null);
                     } else {
                         final int reportedPackageCount = reportedPackageNames.size();
                         for (int j = 0; j < reportedPackageCount; j++) {
                             String reportedPackageName = reportedPackageNames.valueAt(j);
-                            callback.mCallback.opChanged(code, reportedPackageName);
+                            callback.mCallback.opChanged(code, uid, reportedPackageName);
                         }
                     }
                 } catch (RemoteException e) {
@@ -620,7 +621,7 @@
             try {
                 for (int i = 0; i < repCbs.size(); i++) {
                     try {
-                        repCbs.get(i).mCallback.opChanged(code, packageName);
+                        repCbs.get(i).mCallback.opChanged(code, uid, packageName);
                     } catch (RemoteException e) {
                     }
                 }
@@ -630,39 +631,51 @@
         }
     }
 
-    private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
-            HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
-            String packageName, int op, ArrayList<Callback> cbs) {
+    private static HashMap<Callback, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<Callback, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, ArrayList<Callback> cbs) {
         if (cbs == null) {
             return callbacks;
         }
         if (callbacks == null) {
-            callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+            callbacks = new HashMap<>();
         }
         boolean duplicate = false;
         for (int i=0; i<cbs.size(); i++) {
             Callback cb = cbs.get(i);
-            ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
             if (reports == null) {
-                reports = new ArrayList<Pair<String, Integer>>();
+                reports = new ArrayList<>();
                 callbacks.put(cb, reports);
             } else {
                 final int reportCount = reports.size();
                 for (int j = 0; j < reportCount; j++) {
-                    Pair<String, Integer> report = reports.get(j);
-                    if (report.second == op && report.first.equals(packageName)) {
+                    ChangeRec report = reports.get(j);
+                    if (report.op == op && report.pkg.equals(packageName)) {
                         duplicate = true;
                         break;
                     }
                 }
             }
             if (!duplicate) {
-                reports.add(new Pair<>(packageName, op));
+                reports.add(new ChangeRec(op, uid, packageName));
             }
         }
         return callbacks;
     }
 
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+
+        ChangeRec(int _op, int _uid, String _pkg) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+        }
+    }
+
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
         final int callingPid = Binder.getCallingPid();
@@ -682,7 +695,7 @@
             }
         }
 
-        HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
+        HashMap<Callback, ArrayList<ChangeRec>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -699,9 +712,9 @@
                                 uidState.opModes = null;
                             }
                             for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mOpModeWatchers.get(code));
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mPackageModeWatchers.get(packageName));
                             }
                         }
@@ -734,9 +747,9 @@
                                 && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
                             curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
                             changed = true;
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mOpModeWatchers.get(curOp.op));
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mPackageModeWatchers.get(packageName));
                             if (curOp.time == 0 && curOp.rejectTime == 0) {
                                 pkgOps.removeAt(j);
@@ -757,13 +770,13 @@
             }
         }
         if (callbacks != null) {
-            for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+            for (Map.Entry<Callback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
                 Callback cb = ent.getKey();
-                ArrayList<Pair<String, Integer>> reports = ent.getValue();
+                ArrayList<ChangeRec> reports = ent.getValue();
                 for (int i=0; i<reports.size(); i++) {
-                    Pair<String, Integer> rep = reports.get(i);
+                    ChangeRec rep = reports.get(i);
                     try {
-                        cb.mCallback.opChanged(rep.second, rep.first);
+                        cb.mCallback.opChanged(rep.op, rep.uid, rep.pkg);
                     } catch (RemoteException e) {
                     }
                 }
@@ -1163,8 +1176,10 @@
                     if (pkgUid != uid) {
                         // Oops!  The package name is not valid for the uid they are calling
                         // under.  Abort.
+                        RuntimeException ex = new RuntimeException("here");
+                        ex.fillInStackTrace();
                         Slog.w(TAG, "Bad call: specified package " + packageName
-                                + " under uid " + uid + " but it is really " + pkgUid);
+                                + " under uid " + uid + " but it is really " + pkgUid, ex);
                         return null;
                     }
                 } finally {
@@ -1541,15 +1556,300 @@
         }
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  [-h] [CMD]");
-        pw.println("  -h: print this help text.");
-        pw.println("Commands:");
+    static class Shell extends ShellCommand {
+        final IAppOpsService mInterface;
+        final AppOpsService mInternal;
+
+        int userId = UserHandle.USER_SYSTEM;
+        String packageName;
+        String opStr;
+        int op;
+        int packageUid;
+
+        Shell(IAppOpsService iface, AppOpsService internal) {
+            mInterface = iface;
+            mInternal = internal;
+        }
+
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            dumpCommandHelp(pw);
+        }
+
+        private int strOpToOp(String op, PrintWriter err) {
+            try {
+                return AppOpsManager.strOpToOp(op);
+            } catch (IllegalArgumentException e) {
+            }
+            try {
+                return Integer.parseInt(op);
+            } catch (NumberFormatException e) {
+            }
+            try {
+                return AppOpsManager.strDebugOpToOp(op);
+            } catch (IllegalArgumentException e) {
+                err.println("Error: " + e.getMessage());
+                return -1;
+            }
+        }
+
+        int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException {
+            userId = UserHandle.USER_CURRENT;
+            packageName = null;
+            opStr = null;
+            for (String argument; (argument = getNextArg()) != null;) {
+                if ("--user".equals(argument)) {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                } else {
+                    if (packageName == null) {
+                        packageName = argument;
+                    } else if (opStr == null) {
+                        opStr = argument;
+                        break;
+                    }
+                }
+            }
+            if (packageName == null) {
+                err.println("Error: Package name not specified.");
+                return -1;
+            } else if (opStr == null && reqOp) {
+                err.println("Error: Operation not specified.");
+                return -1;
+            }
+            if (opStr != null) {
+                op = strOpToOp(opStr, err);
+                if (op < 0) {
+                    return -1;
+                }
+            } else {
+                op = AppOpsManager.OP_NONE;
+            }
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = ActivityManager.getCurrentUser();
+            }
+            if ("root".equals(packageName)) {
+                packageUid = 0;
+            } else {
+                packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, userId);
+            }
+            if (packageUid < 0) {
+                err.println("Error: No UID for " + packageName + " in user " + userId);
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    static void dumpCommandHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set [--user <USER_ID>] <PACKAGE> <OP> <MODE>");
+        pw.println("    Set the mode for a particular application and operation.");
+        pw.println("  get [--user <USER_ID>] <PACKAGE> [<OP>]");
+        pw.println("    Return the mode for a particular application and optional operation.");
+        pw.println("  reset [--user <USER_ID>] [<PACKAGE>]");
+        pw.println("    Reset the given application or all applications to default modes.");
         pw.println("  write-settings");
         pw.println("    Immediately write pending changes to storage.");
         pw.println("  read-settings");
         pw.println("    Read the last written settings, replacing current state in RAM.");
+        pw.println("  options:");
+        pw.println("    <PACKAGE> an Android package name.");
+        pw.println("    <OP>      an AppOps operation.");
+        pw.println("    <MODE>    one of allow, ignore, deny, or default");
+        pw.println("    <USER_ID> the user id under which the package is installed. If --user is not");
+        pw.println("              specified, the current user is assumed.");
+    }
+
+    static int onShellCommand(Shell shell, String cmd) {
+        if (cmd == null) {
+            return shell.handleDefaultCommands(cmd);
+        }
+        PrintWriter pw = shell.getOutPrintWriter();
+        PrintWriter err = shell.getErrPrintWriter();
+        try {
+            switch (cmd) {
+                case "set": {
+                    int res = shell.parseUserPackageOp(true, err);
+                    if (res < 0) {
+                        return res;
+                    }
+                    String modeStr = shell.getNextArg();
+                    if (modeStr == null) {
+                        err.println("Error: Mode not specified.");
+                        return -1;
+                    }
+
+                    final int mode;
+                    switch (modeStr) {
+                        case "allow":
+                            mode = AppOpsManager.MODE_ALLOWED;
+                            break;
+                        case "deny":
+                            mode = AppOpsManager.MODE_ERRORED;
+                            break;
+                        case "ignore":
+                            mode = AppOpsManager.MODE_IGNORED;
+                            break;
+                        case "default":
+                            mode = AppOpsManager.MODE_DEFAULT;
+                            break;
+                        default:
+                            err.println("Error: Mode " + modeStr + " is not valid,");
+                            return -1;
+                    }
+
+                    shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode);
+                    return 0;
+                }
+                case "get": {
+                    int res = shell.parseUserPackageOp(false, err);
+                    if (res < 0) {
+                        return res;
+                    }
+
+                    List<AppOpsManager.PackageOps> ops = shell.mInterface.getOpsForPackage(
+                            shell.packageUid, shell.packageName,
+                            shell.op != AppOpsManager.OP_NONE ? new int[] {shell.op} : null);
+                    if (ops == null || ops.size() <= 0) {
+                        pw.println("No operations.");
+                        return 0;
+                    }
+                    final long now = System.currentTimeMillis();
+                    for (int i=0; i<ops.size(); i++) {
+                        List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+                        for (int j=0; j<entries.size(); j++) {
+                            AppOpsManager.OpEntry ent = entries.get(j);
+                            pw.print(AppOpsManager.opToName(ent.getOp()));
+                            pw.print(": ");
+                            switch (ent.getMode()) {
+                                case AppOpsManager.MODE_ALLOWED:
+                                    pw.print("allow");
+                                    break;
+                                case AppOpsManager.MODE_IGNORED:
+                                    pw.print("ignore");
+                                    break;
+                                case AppOpsManager.MODE_ERRORED:
+                                    pw.print("deny");
+                                    break;
+                                case AppOpsManager.MODE_DEFAULT:
+                                    pw.print("default");
+                                    break;
+                                default:
+                                    pw.print("mode=");
+                                    pw.print(ent.getMode());
+                                    break;
+                            }
+                            if (ent.getTime() != 0) {
+                                pw.print("; time=");
+                                TimeUtils.formatDuration(now - ent.getTime(), pw);
+                                pw.print(" ago");
+                            }
+                            if (ent.getRejectTime() != 0) {
+                                pw.print("; rejectTime=");
+                                TimeUtils.formatDuration(now - ent.getRejectTime(), pw);
+                                pw.print(" ago");
+                            }
+                            if (ent.getDuration() == -1) {
+                                pw.print(" (running)");
+                            } else if (ent.getDuration() != 0) {
+                                pw.print("; duration=");
+                                TimeUtils.formatDuration(ent.getDuration(), pw);
+                            }
+                            pw.println();
+                        }
+                    }
+                    return 0;
+                }
+                case "reset": {
+                    String packageName = null;
+                    int userId = UserHandle.USER_CURRENT;
+                    for (String argument; (argument = shell.getNextArg()) != null;) {
+                        if ("--user".equals(argument)) {
+                            String userStr = shell.getNextArgRequired();
+                            userId = UserHandle.parseUserArg(userStr);
+                        } else {
+                            if (packageName == null) {
+                                packageName = argument;
+                            } else {
+                                err.println("Error: Unsupported argument: " + argument);
+                                return -1;
+                            }
+                        }
+                    }
+
+                    if (userId == UserHandle.USER_CURRENT) {
+                        userId = ActivityManager.getCurrentUser();
+                    }
+
+                    shell.mInterface.resetAllModes(userId, packageName);
+                    pw.print("Reset all modes for: ");
+                    if (userId == UserHandle.USER_ALL) {
+                        pw.print("all users");
+                    } else {
+                        pw.print("user "); pw.print(userId);
+                    }
+                    pw.print(", ");
+                    if (packageName == null) {
+                        pw.println("all packages");
+                    } else {
+                        pw.print("package "); pw.println(packageName);
+                    }
+                    return 0;
+                }
+                case "write-settings": {
+                    shell.mInternal.mContext.enforcePermission(
+                            android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        synchronized (shell.mInternal) {
+                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
+                        }
+                        shell.mInternal.writeState();
+                        pw.println("Current settings written.");
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    return 0;
+                }
+                case "read-settings": {
+                    shell.mInternal.mContext.enforcePermission(
+                            android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        shell.mInternal.readState();
+                        pw.println("Last settings read.");
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    return 0;
+                }
+                default:
+                    return shell.handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  none");
     }
 
     @Override
@@ -1570,27 +1870,6 @@
                     return;
                 } else if ("-a".equals(arg)) {
                     // dump all data
-                } else if ("write-settings".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        synchronized (this) {
-                            mHandler.removeCallbacks(mWriteRunner);
-                        }
-                        writeState();
-                        pw.println("Current settings written.");
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
-                } else if ("read-settings".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        readState();
-                        pw.println("Last settings read.");
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index fd67b41..f329cff 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -95,8 +95,6 @@
     private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
     private static final int MESSAGE_TIMEOUT_BIND =100;
     private static final int MESSAGE_TIMEOUT_UNBIND =101;
-    private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
-    private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
     private static final int MESSAGE_USER_SWITCHED = 300;
     private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
     private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
@@ -587,15 +585,6 @@
         }
     }
 
-    /** @hide*/
-    public void getNameAndAddress() {
-        if (DBG) {
-            Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
-                  " mBinding = " + mBinding);
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-        mHandler.sendMessage(msg);
-    }
     public boolean enableNoAutoConnect()
     {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
@@ -681,14 +670,13 @@
             if (mUnbinding) return;
             mUnbinding = true;
             if (mBluetooth != null) {
-                if (!mConnection.isGetNameAddressOnly()) {
-                    //Unregister callback object
-                    try {
-                        mBluetooth.unregisterCallback(mBluetoothCallback);
-                    } catch (RemoteException re) {
-                        Log.e(TAG, "Unable to unregister BluetoothCallback",re);
-                    }
+                //Unregister callback object
+                try {
+                    mBluetooth.unregisterCallback(mBluetoothCallback);
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Unable to unregister BluetoothCallback",re);
                 }
+
                 if (DBG) Log.d(TAG, "Sending unbind request.");
                 mBluetooth = null;
                 //Unbind
@@ -780,11 +768,6 @@
             if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
             sendEnableMsg(mQuietEnableExternal);
         }
-        if (!isNameAndAddressSet()) {
-            // Sync the Bluetooth name and address from the Bluetooth Adapter
-            if (DBG) Log.d(TAG, "Retrieving Bluetooth Adapter name and address...");
-            getNameAndAddress();
-        }
     }
 
     /**
@@ -957,42 +940,38 @@
      * Inform BluetoothAdapter instances that Adapter service is up
      */
     private void sendBluetoothServiceUpCallback() {
-        if (!mConnection.isGetNameAddressOnly()) {
-            if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
-            try {
-                int n = mCallbacks.beginBroadcast();
-                Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
-                for (int i=0; i <n;i++) {
-                    try {
-                        mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
-                    }  catch (RemoteException e) {
-                        Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
-                    }
+        if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
+        try {
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
                 }
-            } finally {
-                mCallbacks.finishBroadcast();
             }
+        } finally {
+            mCallbacks.finishBroadcast();
         }
     }
     /**
      * Inform BluetoothAdapter instances that Adapter service is down
      */
     private void sendBluetoothServiceDownCallback() {
-        if (!mConnection.isGetNameAddressOnly()) {
-            if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
-            try {
-                int n = mCallbacks.beginBroadcast();
-                Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
-                for (int i=0; i <n;i++) {
-                    try {
-                        mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
-                    }  catch (RemoteException e) {
-                        Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
-                    }
+        if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
+        try {
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
                 }
-            } finally {
-                mCallbacks.finishBroadcast();
             }
+        } finally {
+            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1052,17 +1031,6 @@
     }
 
     private class BluetoothServiceConnection implements ServiceConnection {
-
-        private boolean mGetNameAddressOnly;
-
-        public void setGetNameAddressOnly(boolean getOnly) {
-            mGetNameAddressOnly = getOnly;
-        }
-
-        public boolean isGetNameAddressOnly() {
-            return mGetNameAddressOnly;
-        }
-
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
             Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
@@ -1108,104 +1076,6 @@
         public void handleMessage(Message msg) {
             if (DBG) Log.d (TAG, "Message: " + msg.what);
             switch (msg.what) {
-                case MESSAGE_GET_NAME_AND_ADDRESS: {
-                    if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS");
-                    synchronized(mConnection) {
-                        //Start bind request
-                        if ((mBluetooth == null) && (!mBinding)) {
-                            if (DBG) Log.d(TAG, "Binding to service to get name and address");
-                            mConnection.setGetNameAddressOnly(true);
-                            //Start bind timeout and bind
-                            Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
-                            mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
-                            Intent i = new Intent(IBluetooth.class.getName());
-                            if (!doBind(i, mConnection,
-                                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
-                                    UserHandle.CURRENT)) {
-                                mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
-                            } else {
-                                mBinding = true;
-                            }
-                        }
-                        else {
-                            Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
-                            saveMsg.arg1 = 0;
-                            if (mBluetooth != null) {
-                                mHandler.sendMessage(saveMsg);
-                            } else {
-                                // if enable is also called to bind the service
-                                // wait for MESSAGE_BLUETOOTH_SERVICE_CONNECTED
-                                mHandler.sendMessageDelayed(saveMsg, TIMEOUT_SAVE_MS);
-                            }
-                        }
-                    }
-                    break;
-                }
-                case MESSAGE_SAVE_NAME_AND_ADDRESS: {
-                    boolean unbind = false;
-                    if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS");
-                    synchronized(mConnection) {
-                        if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
-                            try {
-                                mBluetooth.enable();
-                            } catch (RemoteException e) {
-                                Log.e(TAG,"Unable to call enable()",e);
-                            }
-                        }
-                    }
-                    if (mBluetooth != null && !mConnection.isGetNameAddressOnly()) waitForOnOff(true, false);
-                    synchronized(mConnection) {
-                        if (mBluetooth != null) {
-                            String name =  null;
-                            String address = null;
-                            try {
-                                name =  mBluetooth.getName();
-                                address = mBluetooth.getAddress();
-                            } catch (RemoteException re) {
-                                Log.e(TAG,"",re);
-                            }
-
-                            if (name != null && address != null) {
-                                storeNameAndAddress(name,address);
-                                if (mConnection.isGetNameAddressOnly()) {
-                                    unbind = true;
-                                }
-                            } else {
-                                if (msg.arg1 < MAX_SAVE_RETRIES) {
-                                    Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
-                                    retryMsg.arg1= 1+msg.arg1;
-                                    if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1);
-                                    mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS);
-                                } else {
-                                    Log.w(TAG,"Maximum name/address remote retrieval retry exceeded");
-                                    if (mConnection.isGetNameAddressOnly()) {
-                                        unbind = true;
-                                    }
-                                }
-                            }
-                            if (!mEnable && !mConnection.isGetNameAddressOnly()) {
-                                try {
-                                    mBluetooth.disable();
-                                } catch (RemoteException e) {
-                                    Log.e(TAG,"Unable to call disable()",e);
-                                }
-                            }
-                        } else {
-                            // rebind service by Request GET NAME AND ADDRESS
-                            // if service is unbinded by disable or
-                            // MESSAGE_BLUETOOTH_SERVICE_CONNECTED is not received
-                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-                            mHandler.sendMessage(getMsg);
-                        }
-                    }
-                    if (!mEnable && mBluetooth != null && !mConnection.isGetNameAddressOnly()) {
-                        waitForOnOff(false, true);
-                    }
-                    if (unbind) {
-                        unbindAndFinish();
-                    }
-                    break;
-                }
                 case MESSAGE_ENABLE:
                     if (DBG) {
                         Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
@@ -1308,14 +1178,6 @@
                             Log.e(TAG,"Unable to call configHciSnoopLog", e);
                         }
 
-                        if (mConnection.isGetNameAddressOnly()) {
-                            //Request GET NAME AND ADDRESS
-                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
-                            mHandler.sendMessage(getMsg);
-                            if (!mEnable) return;
-                        }
-
-                        mConnection.setGetNameAddressOnly(false);
                         //Register callback object
                         try {
                             mBluetooth.registerCallback(mBluetoothCallback);
@@ -1412,25 +1274,23 @@
                             SERVICE_RESTART_TIME_MS);
                     }
 
-                    if (!mConnection.isGetNameAddressOnly()) {
-                        sendBluetoothServiceDownCallback();
+                    sendBluetoothServiceDownCallback();
 
-                        // Send BT state broadcast to update
-                        // the BT icon correctly
-                        if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
-                            (mState == BluetoothAdapter.STATE_ON)) {
-                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
-                                                        BluetoothAdapter.STATE_TURNING_OFF);
-                            mState = BluetoothAdapter.STATE_TURNING_OFF;
-                        }
-                        if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
-                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
-                                                        BluetoothAdapter.STATE_OFF);
-                        }
-
-                        mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
-                        mState = BluetoothAdapter.STATE_OFF;
+                    // Send BT state broadcast to update
+                    // the BT icon correctly
+                    if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
+                        (mState == BluetoothAdapter.STATE_ON)) {
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+                                                    BluetoothAdapter.STATE_TURNING_OFF);
+                        mState = BluetoothAdapter.STATE_TURNING_OFF;
                     }
+                    if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+                                                    BluetoothAdapter.STATE_OFF);
+                    }
+
+                    mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+                    mState = BluetoothAdapter.STATE_OFF;
                     break;
                 }
                 case MESSAGE_RESTART_BLUETOOTH_SERVICE:
@@ -1539,7 +1399,6 @@
                 //Start bind timeout and bind
                 Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
                 mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
-                mConnection.setGetNameAddressOnly(false);
                 Intent i = new Intent(IBluetooth.class.getName());
                 if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
                         UserHandle.CURRENT)) {
@@ -1548,21 +1407,6 @@
                     mBinding = true;
                 }
             } else if (mBluetooth != null) {
-                if (mConnection.isGetNameAddressOnly()) {
-                    // if GetNameAddressOnly is set, we can clear this flag,
-                    // so the service won't be unbind
-                    // after name and address are saved
-                    mConnection.setGetNameAddressOnly(false);
-                    //Register callback object
-                    try {
-                        mBluetooth.registerCallback(mBluetoothCallback);
-                    } catch (RemoteException re) {
-                        Log.e(TAG, "Unable to register BluetoothCallback",re);
-                    }
-                    //Inform BluetoothAdapter instances that service is up
-                    sendBluetoothServiceUpCallback();
-                }
-
                 //Enable bluetooth
                 try {
                     if (!mQuietEnable) {
@@ -1594,9 +1438,7 @@
 
     private void handleDisable() {
         synchronized(mConnection) {
-            // don't need to disable if GetNameAddressOnly is set,
-            // service will be unbinded after Name and Address are saved
-            if ((mBluetooth != null) && (!mConnection.isGetNameAddressOnly())) {
+            if (mBluetooth != null) {
                 if (DBG) Log.d(TAG,"Sending off request.");
 
                 try {
diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java
index 73e8c52..019d03d 100644
--- a/services/core/java/com/android/server/BluetoothService.java
+++ b/services/core/java/com/android/server/BluetoothService.java
@@ -31,14 +31,15 @@
 
     @Override
     public void onStart() {
-        Log.d(TAG, "onStart: publishing BluetoothManagerService");
-        publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
+            publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, mBluetoothManagerService);
+        } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            Log.d(TAG, "onBootPhase: PHASE_ACTIVITY_MANAGER_READY");
             mBluetoothManagerService.handleOnBootPhase();
         }
     }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 927b995..f5ed83e 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -48,6 +48,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
@@ -106,6 +107,8 @@
 
     private static final boolean COMPRESS_TIME = false;
 
+    private static final int EVENT_BUFFER_SIZE = 40;
+
     private static final String ACTION_STEP_IDLE_STATE =
             "com.android.server.device_idle.STEP_IDLE_STATE";
 
@@ -195,6 +198,14 @@
     private long mNextIdlePendingDelay;
     private long mNextIdleDelay;
     private long mNextLightAlarmTime;
+    private long mCurIdleBudget;
+    private long mMaintenanceStartTime;
+
+    private int mActiveIdleOpCount;
+    private IBinder mDownloadServiceActive;
+    private boolean mSyncActive;
+    private boolean mJobsActive;
+    private boolean mAlarmsActive;
 
     public final AtomicFile mConfigFile;
 
@@ -267,6 +278,25 @@
      */
     private int[] mTempWhitelistAppIdArray = new int[0];
 
+    private static final int EVENT_NULL = 0;
+    private static final int EVENT_NORMAL = 1;
+    private static final int EVENT_LIGHT_IDLE = 2;
+    private static final int EVENT_LIGHT_MAINTENANCE = 3;
+    private static final int EVENT_FULL_IDLE = 4;
+    private static final int EVENT_FULL_MAINTENANCE = 5;
+
+    private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
+    private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
+
+    private void addEvent(int cmd) {
+        if (mEventCmds[0] != cmd) {
+            System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1);
+            System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1);
+            mEventCmds[0] = cmd;
+            mEventTimes[0] = SystemClock.elapsedRealtime();
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
@@ -282,16 +312,22 @@
                 }
             } else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:alarm");
                 }
             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:alarm");
                 }
             }
         }
     };
 
+    private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            decActiveIdleOps();
+        }
+    };
+
     private final DisplayManager.DisplayListener mDisplayListener
             = new DisplayManager.DisplayListener() {
         @Override public void onDisplayAdded(int displayId) {
@@ -411,7 +447,10 @@
     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_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
+                = "light_idle_maintenance_min_budget";
+        private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+                = "light_idle_maintenance_max_budget";
         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";
@@ -441,12 +480,24 @@
         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.
+         * This is the minimum amount of time we want to make available for maintenance mode
+         * when lightly idling.  That is, we will always have at least this amount of time
+         * available maintenance before timing out and cutting off maintenance mode.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
-         * @see #KEY_LIGHT_IDLE_PENDING_TIMEOUT
+         * @see #KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
          */
-        public long LIGHT_IDLE_PENDING_TIMEOUT;
+        public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+
+        /**
+         * This is the maximum amount of time we want to make available for maintenance mode
+         * when lightly idling.  That is, if the system isn't using up its minimum maintenance
+         * budget and this time is being added to the budget reserve, this is the maximum
+         * reserve size we will allow to grow and thus the maximum amount of time we will
+         * allow for the maintenance window.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+         */
+        public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
 
         /**
          * This is the time, after becoming inactive, at which we start looking at the
@@ -606,8 +657,12 @@
 
                 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,
+                LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
+                        KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
                         !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L);
+                LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getLong(
+                        KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
+                        !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -649,8 +704,12 @@
             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.print("    "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, pw);
             pw.println();
 
             pw.print("    "); pw.print(KEY_INACTIVE_TIMEOUT); pw.print("=");
@@ -733,7 +792,7 @@
                 // If we are currently sensing, it is time to move to locating.
                 synchronized (this) {
                     mNotMoving = true;
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:stationary");
                 }
             } else if (mState == STATE_LOCATING) {
                 // If we are currently locating, note that we are not moving and step
@@ -741,7 +800,7 @@
                 synchronized (this) {
                     mNotMoving = true;
                     if (mLocated) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:stationary");
                     }
                 }
             }
@@ -804,11 +863,18 @@
                     } catch (RemoteException e) {
                     }
                     if (fullChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
+                    // Always start with one active op for the message being sent here.
+                    // Now we we done!
+                    decActiveIdleOps();
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
                 case MSG_REPORT_ACTIVE: {
@@ -913,11 +979,23 @@
         }
 
         @Override public void exitIdle(String reason) {
-            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
                     null);
             exitIdleInternal(reason);
         }
 
+        @Override public void downloadServiceActive(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceActive(token);
+        }
+
+        @Override public void downloadServiceInactive() {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceInactive();
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -937,6 +1015,19 @@
         public void setNetworkPolicyTempWhitelistCallback(Runnable callback) {
             setNetworkPolicyTempWhitelistCallbackInternal(callback);
         }
+
+        public void setSyncActive(boolean active) {
+            DeviceIdleController.this.setSyncActive(active);
+        }
+
+        public void setJobsActive(boolean active) {
+            DeviceIdleController.this.setJobsActive(active);
+        }
+
+        // Up-call from alarm manager.
+        public void setAlarmsActive(boolean active) {
+            DeviceIdleController.this.setAlarmsActive(active);
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1390,8 +1481,11 @@
             mState = STATE_ACTIVE;
             mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+            mCurIdleBudget = 0;
+            mMaintenanceStartTime = 0;
             resetIdleManagementLocked();
             resetLightIdleManagementLocked();
+            addEvent(EVENT_NORMAL);
         }
     }
 
@@ -1439,7 +1533,7 @@
         }
     }
 
-    void stepLightIdleStateLocked() {
+    void stepLightIdleStateLocked(String reason) {
         if (mLightState == LIGHT_STATE_OVERRIDE) {
             // If we are already in full device idle mode, then
             // there is nothing left to do for light mode.
@@ -1451,26 +1545,49 @@
 
         switch (mLightState) {
             case LIGHT_STATE_INACTIVE:
+                mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                mMaintenanceStartTime = 0;
             case LIGHT_STATE_IDLE_MAINTENANCE:
+                if (mMaintenanceStartTime != 0) {
+                    long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
+                    if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                        // We didn't use up all of our minimum budget; add this to the reserve.
+                        mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
+                    } else {
+                        // We used more than our minimum budget; this comes out of the reserve.
+                        mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
+                    }
+                }
+                mMaintenanceStartTime = 0;
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
                 if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                 mLightState = LIGHT_STATE_IDLE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                addEvent(EVENT_LIGHT_IDLE);
                 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);
+                mActiveIdleOpCount = 1;
+                mMaintenanceStartTime = SystemClock.elapsedRealtime();
+                if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                    mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
+                    mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
+                }
+                mMaintenanceStartTime = SystemClock.elapsedRealtime();
+                scheduleLightAlarmLocked(mCurIdleBudget);
                 if (DEBUG) Slog.d(TAG,
                         "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                 mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                addEvent(EVENT_LIGHT_MAINTENANCE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
-    void stepIdleStateLocked() {
+    void stepIdleStateLocked(String reason) {
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
 
@@ -1494,12 +1611,12 @@
                 mNextIdleDelay = mConstants.IDLE_TIMEOUT;
                 mState = STATE_IDLE_PENDING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 break;
             case STATE_IDLE_PENDING:
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
@@ -1511,7 +1628,7 @@
             case STATE_SENSING:
                 mState = STATE_LOCATING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
                 if (mLocationManager != null
                         && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -1553,23 +1670,103 @@
                     mLightState = LIGHT_STATE_OVERRIDE;
                     cancelLightAlarmLocked();
                 }
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
+                addEvent(EVENT_FULL_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
             case STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
                 scheduleAlarmLocked(mNextIdlePendingDelay, false);
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                         "Next alarm in " + mNextIdlePendingDelay + " ms.");
                 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                 mState = STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
+                addEvent(EVENT_FULL_MAINTENANCE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
+    void incActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount++;
+        }
+    }
+
+    void decActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount--;
+            if (mActiveIdleOpCount <= 0) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void downloadServiceActive(IBinder token) {
+        synchronized (this) {
+            mDownloadServiceActive = token;
+            try {
+                token.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override public void binderDied() {
+                        downloadServiceInactive();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                mDownloadServiceActive = null;
+            }
+        }
+    }
+
+    void downloadServiceInactive() {
+        synchronized (this) {
+            mDownloadServiceActive = null;
+            exitMaintenanceEarlyIfNeededLocked();
+        }
+    }
+
+    void setSyncActive(boolean active) {
+        synchronized (this) {
+            mSyncActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setJobsActive(boolean active) {
+        synchronized (this) {
+            mJobsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setAlarmsActive(boolean active) {
+        synchronized (this) {
+            mAlarmsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void exitMaintenanceEarlyIfNeededLocked() {
+        if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
+            if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
+                    && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+                if (mState == STATE_IDLE_MAINTENANCE) {
+                    stepIdleStateLocked("s:early");
+                } else {
+                    stepLightIdleStateLocked("s:early");
+                }
+            }
+        }
+    }
+
     void motionLocked() {
         if (DEBUG) Slog.d(TAG, "motionLocked()");
         // The motion sensor will have been disabled at this point
@@ -1585,7 +1782,10 @@
             scheduleReportActiveLocked(type, Process.myUid());
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
+            mCurIdleBudget = 0;
+            mMaintenanceStartTime = 0;
             EventLogTags.writeDeviceIdle(mState, type);
+            addEvent(EVENT_NORMAL);
             becomeInactive = true;
         }
         if (mLightState == LIGHT_STATE_OVERRIDE) {
@@ -1612,7 +1812,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:location");
         }
     }
 
@@ -1628,7 +1828,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:gps");
         }
     }
 
@@ -1892,6 +2092,8 @@
         pw.println("    Print this help text.");
         pw.println("  step");
         pw.println("    Immediately step to next state, without waiting for alarm.");
+        pw.println("  light-step");
+        pw.println("    Immediately step to next light idle state, without waiting for alarm.");
         pw.println("  force-idle");
         pw.println("    Force directly into idle mode, regardless of other device state.");
         pw.println("    Use \"step\" to get out.");
@@ -1933,7 +2135,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:shell");
                     pw.print("Stepped to: ");
                     pw.println(stateToString(mState));
                 } finally {
@@ -1947,7 +2149,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:shell");
                     pw.print("Stepped to: "); pw.println(lightStateToString(mLightState));
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -1967,7 +2169,7 @@
                     becomeInactiveIfAppropriateLocked();
                     int curState = mState;
                     while (curState != STATE_IDLE) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:shell");
                         if (curState == mState) {
                             pw.print("Unable to go idle; stopped at ");
                             pw.println(stateToString(mState));
@@ -2138,6 +2340,31 @@
         synchronized (this) {
             mConstants.dump(pw);
 
+            if (mEventCmds[0] != EVENT_NULL) {
+                pw.println("  Idling history:");
+                long now = SystemClock.elapsedRealtime();
+                for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) {
+                    int cmd = mEventCmds[i];
+                    if (cmd == EVENT_NULL) {
+                        continue;
+                    }
+                    String label;
+                    switch (mEventCmds[i]) {
+                        case EVENT_NORMAL:              label = "     normal"; break;
+                        case EVENT_LIGHT_IDLE:          label = " light-idle"; break;
+                        case EVENT_LIGHT_MAINTENANCE:   label = "light-maint"; break;
+                        case EVENT_FULL_IDLE:           label = "  full-idle"; break;
+                        case EVENT_FULL_MAINTENANCE:    label = " full-maint"; break;
+                        default:                        label = "         ??"; break;
+                    }
+                    pw.print("    ");
+                    pw.print(label);
+                    pw.print(": ");
+                    TimeUtils.formatDuration(mEventTimes[i], now, pw);;
+                    pw.println();
+                }
+            }
+
             int size = mPowerSaveWhitelistAppsExceptIdle.size();
             if (size > 0) {
                 pw.println("  Whitelist (except idle) system apps:");
@@ -2226,6 +2453,9 @@
             pw.println(lightStateToString(mLightState));
             pw.print("  mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
             pw.println();
+            if (mActiveIdleOpCount != 0) {
+                pw.print("  mActiveIdleOpCount="); pw.println(mActiveIdleOpCount);
+            }
             if (mNextAlarmTime != 0) {
                 pw.print("  mNextAlarmTime=");
                 TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
@@ -2246,6 +2476,28 @@
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mCurIdleBudget != 0) {
+                pw.print("  mCurIdleBudget=");
+                TimeUtils.formatDuration(mCurIdleBudget, pw);
+                pw.println();
+            }
+            if (mMaintenanceStartTime != 0) {
+                pw.print("  mMaintenanceStartTime=");
+                TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
+                pw.println();
+            }
+            if (mSyncActive) {
+                pw.print("  mSyncActive="); pw.println(mSyncActive);
+            }
+            if (mJobsActive) {
+                pw.print("  mJobsActive="); pw.println(mJobsActive);
+            }
+            if (mAlarmsActive) {
+                pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
+            }
+            if (mDownloadServiceActive != null) {
+                pw.print("  mDownloadServiceActive="); pw.println(mDownloadServiceActive);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index ab1d775..5e4f2b2 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -30,6 +30,7 @@
 import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.IInputSessionCallback;
 import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
 import com.android.server.statusbar.StatusBarManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -160,8 +161,8 @@
     static final int MSG_START_INPUT = 2000;
     static final int MSG_RESTART_INPUT = 2010;
 
-    static final int MSG_UNBIND_METHOD = 3000;
-    static final int MSG_BIND_METHOD = 3010;
+    static final int MSG_UNBIND_CLIENT = 3000;
+    static final int MSG_BIND_CLIENT = 3010;
     static final int MSG_SET_ACTIVE = 3020;
     static final int MSG_SET_INTERACTIVE = 3030;
     static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
@@ -308,11 +309,18 @@
     ClientState mCurClient;
 
     /**
-     * The last window token that gained focus.
+     * The last window token that we confirmed to be focused.  This is always updated upon reports
+     * from the input method client.  If the window state is already changed before the report is
+     * handled, this field just keeps the last value.
      */
     IBinder mCurFocusedWindow;
 
     /**
+     * The client by which {@link #mCurFocusedWindow} was reported.  Used only for debugging.
+     */
+    ClientState mCurFocusedWindowClient;
+
+    /**
      * The input context last provided by the current client.
      */
     IInputContext mCurInputContext;
@@ -935,7 +943,7 @@
                 || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
             if (!updateOnlyWhenLocaleChanged) {
                 hideCurrentInputLocked(0, null);
-                unbindCurrentMethodLocked(true, false);
+                resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_RESET_IME);
             }
             if (DEBUG) {
                 Slog.i(TAG, "Locale has been changed to " + newLocale);
@@ -1195,6 +1203,12 @@
             ClientState cs = mClients.remove(client.asBinder());
             if (cs != null) {
                 clearClientSessionLocked(cs);
+                if (mCurClient == cs) {
+                    mCurClient = null;
+                }
+                if (mCurFocusedWindowClient == cs) {
+                    mCurFocusedWindowClient = null;
+                }
             }
         }
     }
@@ -1208,7 +1222,8 @@
          }
     }
 
-    void unbindCurrentClientLocked() {
+    void unbindCurrentClientLocked(
+            /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
         if (mCurClient != null) {
             if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
                     + mCurClient.client.asBinder());
@@ -1222,8 +1237,8 @@
 
             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
                     MSG_SET_ACTIVE, 0, mCurClient));
-            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+                    MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
             mCurClient.sessionRequested = false;
             mCurClient = null;
 
@@ -1324,7 +1339,7 @@
             mCurClientInKeyguard = isKeyguardLocked();
             // If the client is changing, we need to switch over to the new
             // one.
-            unbindCurrentClientLocked();
+            unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
             if (DEBUG) Slog.v(TAG, "switching to client: client = "
                     + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
 
@@ -1395,7 +1410,7 @@
             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
         }
 
-        unbindCurrentMethodLocked(false, true);
+        unbindCurrentMethodLocked(true);
 
         mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
         mCurIntent.setComponent(info.getComponent());
@@ -1453,7 +1468,7 @@
                 mCurMethod = IInputMethod.Stub.asInterface(service);
                 if (mCurToken == null) {
                     Slog.w(TAG, "Service connected without a token!");
-                    unbindCurrentMethodLocked(false, false);
+                    unbindCurrentMethodLocked(false);
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
@@ -1479,7 +1494,7 @@
                     InputBindResult res = attachNewInputLocked(true);
                     if (res.method != null) {
                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
-                                MSG_BIND_METHOD, mCurClient.client, res));
+                                MSG_BIND_CLIENT, mCurClient.client, res));
                     }
                     return;
                 }
@@ -1490,11 +1505,7 @@
         channel.dispose();
     }
 
-    void unbindCurrentMethodLocked(boolean resetCurrentMethodAndClient, boolean savePosition) {
-        if (resetCurrentMethodAndClient) {
-            mCurMethodId = null;
-        }
-
+    void unbindCurrentMethodLocked(boolean savePosition) {
         if (mVisibleBound) {
             mContext.unbindService(mVisibleConnection);
             mVisibleBound = false;
@@ -1520,10 +1531,13 @@
 
         mCurId = null;
         clearCurMethodLocked();
+    }
 
-        if (resetCurrentMethodAndClient) {
-            unbindCurrentClientLocked();
-        }
+    void resetCurrentMethodAndClient(
+            /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
+        mCurMethodId = null;
+        unbindCurrentMethodLocked(false);
+        unbindCurrentClientLocked(unbindClientReason);
     }
 
     void requestClientSessionLocked(ClientState cs) {
@@ -1590,8 +1604,9 @@
                 mShowRequested = mInputShown;
                 mInputShown = false;
                 if (mCurClient != null) {
-                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+                            MSG_UNBIND_CLIENT, InputMethodClient.UNBIND_REASON_DISCONNECT_IME,
+                            mCurSeq, mCurClient.client));
                 }
             }
         }
@@ -1876,12 +1891,12 @@
                 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
-                unbindCurrentMethodLocked(true, false);
+                resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
             }
             mShortcutInputMethodsAndSubtypes.clear();
         } else {
             // There is no longer an input method set, so stop any current one.
-            unbindCurrentMethodLocked(true, false);
+            resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
         }
         // Here is not the perfect place to reset the switching controller. Ideally
         // mSwitchingController and mSettings should be able to share the same state.
@@ -1967,7 +1982,7 @@
                 intent.putExtra("input_method_id", id);
                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
             }
-            unbindCurrentClientLocked();
+            unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2189,6 +2204,7 @@
                     return null;
                 }
                 mCurFocusedWindow = windowToken;
+                mCurFocusedWindowClient = cs;
 
                 // Should we auto-show the IME even if the caller has not
                 // specified what should be done with it?
@@ -2771,14 +2787,14 @@
 
             // ---------------------------------------------------------
 
-            case MSG_UNBIND_METHOD:
+            case MSG_UNBIND_CLIENT:
                 try {
-                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
+                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
                 } catch (RemoteException e) {
                     // There is nothing interesting about the last client dying.
                 }
                 return true;
-            case MSG_BIND_METHOD: {
+            case MSG_BIND_CLIENT: {
                 args = (SomeArgs)msg.obj;
                 IInputMethodClient client = (IInputMethodClient)args.arg1;
                 InputBindResult res = (InputBindResult)args.arg2;
@@ -3700,6 +3716,7 @@
 
         IInputMethod method;
         ClientState client;
+        ClientState focusedWindowClient;
 
         final Printer p = new PrintWriterPrinter(pw);
 
@@ -3724,6 +3741,8 @@
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
+            focusedWindowClient = mCurFocusedWindowClient;
+            p.println("  mCurFocusedWindowClient=" + focusedWindowClient);
             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
                     + " mBoundToMethod=" + mBoundToMethod);
             p.println("  mCurToken=" + mCurToken);
@@ -3755,6 +3774,20 @@
             p.println("No input method client.");
         }
 
+        if (focusedWindowClient != null && client != focusedWindowClient) {
+            p.println(" ");
+            p.println("Warning: Current input method client doesn't match the last focused. "
+                    + "window.");
+            p.println("Dumping input method client in the last focused window just in case.");
+            p.println(" ");
+            pw.flush();
+            try {
+                focusedWindowClient.client.asBinder().dump(fd, args);
+            } catch (RemoteException e) {
+                p.println("Input method client in focused window dead: " + e);
+            }
+        }
+
         p.println(" ");
         if (method != null) {
             pw.flush();
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 3359060..43d10c7 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -226,7 +226,7 @@
             final int N = a.length;
             boolean printedHeader = false;
             F filter;
-            if (collapseDuplicates) {
+            if (collapseDuplicates && !printFilter) {
                 found.clear();
                 for (int i=0; i<N && (filter=a[i]) != null; i++) {
                     if (packageName != null && !isPackageForFilter(packageName, filter)) {
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 6acec6b..eb49a78 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -389,7 +389,7 @@
 
     private int getUserParentOrSelfId(int userId) {
         // Device supports per user encryption, so lock is applied to the given user.
-        if (mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
             return userId;
         }
         // Device uses Block Based Encryption, and the parent user's lock is used for the whole
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 6e4f238..a32bb2f 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -56,6 +56,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -96,6 +97,7 @@
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.NativeDaemonConnector.Command;
@@ -119,6 +121,7 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -284,6 +287,8 @@
 
     @GuardedBy("mLock")
     private int[] mStartedUsers = EmptyArray.INT;
+    @GuardedBy("mLock")
+    private int[] mUnlockedUsers = EmptyArray.INT;
 
     /** Map from disk ID to disk */
     @GuardedBy("mLock")
@@ -402,6 +407,17 @@
         }
     }
 
+    private static String escapeNull(String arg) {
+        if (TextUtils.isEmpty(arg)) {
+            return "!";
+        } else {
+            if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
+                throw new IllegalArgumentException(arg);
+            }
+            return arg;
+        }
+    }
+
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -1888,13 +1904,18 @@
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
-        synchronized (mLock) {
-            if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
-                mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
-            }
+        if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+            final boolean emulateFbe = (flags & StorageManager.DEBUG_EMULATE_FBE) != 0;
+            SystemProperties.set(StorageManager.PROP_EMULATE_FBE, Boolean.toString(emulateFbe));
+        }
 
-            writeSettingsLocked();
-            mHandler.obtainMessage(H_RESET).sendToTarget();
+        if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
+            synchronized (mLock) {
+                mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
+
+                writeSettingsLocked();
+                mHandler.obtainMessage(H_RESET).sendToTarget();
+            }
         }
     }
 
@@ -2654,65 +2675,97 @@
     }
 
     @Override
-    public void createNewUserDir(int userHandle, String path) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only SYSTEM_UID can create user directories");
-        }
-
+    public void createUserKey(int userId, int serialNumber) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
         waitForReady();
 
-        if (DEBUG_EVENTS) {
-            Slog.i(TAG, "Creating new user dir");
-        }
-
         try {
-            NativeDaemonEvent event = mCryptConnector.execute(
-                "cryptfs", "createnewuserdir", userHandle, path);
-            if (!"0".equals(event.getMessage())) {
-                String error = "createnewuserdir sent unexpected message: "
-                    + event.getMessage();
-                Slog.e(TAG,  error);
-                // ext4enc:TODO is this the right exception?
-                throw new RuntimeException(error);
-            }
+            mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber);
         } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "createnewuserdir threw exception", e);
-            throw new RuntimeException("createnewuserdir threw exception", e);
-        }
-    }
-
-    // ext4enc:TODO duplication between this and createNewUserDir is nasty
-    @Override
-    public void deleteUserKey(int userHandle) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only SYSTEM_UID can delete user keys");
-        }
-
-        waitForReady();
-
-        if (DEBUG_EVENTS) {
-            Slog.i(TAG, "Deleting user key");
-        }
-
-        try {
-            NativeDaemonEvent event = mCryptConnector.execute(
-                "cryptfs", "deleteuserkey", userHandle);
-            if (!"0".equals(event.getMessage())) {
-                String error = "deleteuserkey sent unexpected message: "
-                    + event.getMessage();
-                Slog.e(TAG,  error);
-                // ext4enc:TODO is this the right exception?
-                throw new RuntimeException(error);
-            }
-        } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "deleteuserkey threw exception", e);
-            throw new RuntimeException("deleteuserkey threw exception", e);
+            throw e.rethrowAsParcelableException();
         }
     }
 
     @Override
-    public boolean isPerUserEncryptionEnabled() {
-        return "file".equals(SystemProperties.get("ro.crypto.type", "none"));
+    public void destroyUserKey(int userId) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        try {
+            mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void unlockUserKey(int userId, int serialNumber, byte[] token) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        final String encodedToken;
+        if (ArrayUtils.isEmpty(token)) {
+            encodedToken = "!";
+        } else {
+            encodedToken = HexDump.toHexString(token);
+        }
+
+        try {
+            mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
+                    new SensitiveArg(encodedToken));
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        synchronized (mLock) {
+            mUnlockedUsers = ArrayUtils.appendInt(mUnlockedUsers, userId);
+        }
+    }
+
+    @Override
+    public void lockUserKey(int userId) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        try {
+            mCryptConnector.execute("cryptfs", "lock_user_key", userId);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        synchronized (mLock) {
+            mUnlockedUsers = ArrayUtils.removeInt(mUnlockedUsers, userId);
+        }
+    }
+
+    @Override
+    public boolean isUserKeyUnlocked(int userId) {
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
+            synchronized (mLock) {
+                return ArrayUtils.contains(mUnlockedUsers, userId);
+            }
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+        waitForReady();
+
+        try {
+            mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
+                    userId, serialNumber);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+        // TODO: Invoke vold to mount app fuse.
+        throw new UnsupportedOperationException();
     }
 
     @Override
@@ -3449,6 +3502,9 @@
             pw.println();
             pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
             pw.println("Force adoptable: " + mForceAdoptable);
+            pw.println();
+            pw.println("Started users: " + Arrays.toString(mStartedUsers));
+            pw.println("Unlocked users: " + Arrays.toString(mUnlockedUsers));
         }
 
         synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index c228422..9eb66dd 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -88,6 +88,7 @@
     private SettingsObserver mSettingObserver;
 
     native static boolean vibratorExists();
+    native static void vibratorInit();
     native static void vibratorOn(long milliseconds);
     native static void vibratorOff();
 
@@ -195,6 +196,7 @@
     }
 
     VibratorService(Context context) {
+        vibratorInit();
         // Reset the hardware to a default state, in case this is a runtime
         // restart instead of a fresh boot.
         vibratorOff();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 9ac4ba3..93eaf0e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -88,6 +88,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
@@ -114,6 +115,7 @@
 public class AccountManagerService
         extends IAccountManager.Stub
         implements RegisteredServicesCacheListener<AuthenticatorDescription> {
+
     private static final String TAG = "AccountManagerService";
 
     private static final String DATABASE_NAME = "accounts.db";
@@ -2283,6 +2285,196 @@
         }
     }
 
+    @Override
+    public void startAddAccountSession(
+            final IAccountManagerResponse response,
+            final String accountType,
+            final String authTokenType,
+            final String[] requiredFeatures,
+            final boolean expectActivityLaunch,
+            final Bundle optionsIn) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG,
+                    "startAddAccountSession: accountType " + accountType
+                    + ", response " + response
+                    + ", authTokenType " + authTokenType
+                    + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) {
+            throw new IllegalArgumentException("response is null");
+        }
+        if (accountType == null) {
+            throw new IllegalArgumentException("accountType is null");
+        }
+
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!canUserModifyAccounts(userId)) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+                        "User is not allowed to add an account!");
+            } catch (RemoteException re) {
+            }
+            showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId);
+            return;
+        }
+        if (!canUserModifyAccountsForType(userId, accountType)) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                        "User cannot modify accounts of this type (policy).");
+            } catch (RemoteException re) {
+            }
+            showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                    userId);
+            return;
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+        options.putInt(AccountManager.KEY_CALLER_UID, uid);
+        options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+        int usrId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            UserAccounts accounts = getUserAccounts(usrId);
+            logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD,
+                    TABLE_ACCOUNTS, uid);
+            new StartAccountSession(accounts, response, accountType, expectActivityLaunch,
+                    null /* accountName */, false /* authDetailsRequired */,
+                    true /* updateLastAuthenticationTime */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType,
+                            requiredFeatures, options);
+                }
+
+                @Override
+                protected String toDebugString(long now) {
+                    String requiredFeaturesStr = TextUtils.join(",", requiredFeatures);
+                    return super.toDebugString(now) + ", startAddAccountSession" + ", accountType "
+                            + accountType + ", requiredFeatures "
+                            + (requiredFeatures != null ? requiredFeaturesStr : null);
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */
+    private abstract class StartAccountSession extends Session {
+
+        public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+                String accountType, boolean expectActivityLaunch, String accountName,
+                boolean authDetailsRequired, boolean updateLastAuthenticationTime) {
+            super(accounts, response, accountType, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */, accountName, authDetailsRequired,
+                    updateLastAuthenticationTime);
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            mNumResults++;
+            Intent intent = null;
+
+            if (result != null
+                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+                /*
+                 * The Authenticator API allows third party authenticators to
+                 * supply arbitrary intents to other apps that they can run,
+                 * this can be very bad when those apps are in the system like
+                 * the System Settings.
+                 */
+                int authenticatorUid = Binder.getCallingUid();
+                long bid = Binder.clearCallingIdentity();
+                try {
+                    PackageManager pm = mContext.getPackageManager();
+                    ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                    int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+                    if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid,
+                            targetUid)) {
+                        throw new SecurityException("Activity to be started with KEY_INTENT must "
+                                + "share Authenticator's signatures");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(bid);
+                }
+            }
+
+            IAccountManagerResponse response;
+            if (mExpectActivityLaunch && result != null
+                    && result.containsKey(AccountManager.KEY_INTENT)) {
+                response = mResponse;
+            } else {
+                response = getResponseAndClose();
+            }
+            if (response == null) {
+                return;
+            }
+            if (result == null) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, getClass().getSimpleName() + " calling onError() on response "
+                            + response);
+                }
+                sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                        "null bundle returned");
+                return;
+            }
+
+            if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && (intent == null)) {
+                // All AccountManager error codes are greater
+                // than 0
+                sendErrorResponse(response, result.getInt(AccountManager.KEY_ERROR_CODE),
+                        result.getString(AccountManager.KEY_ERROR_MESSAGE));
+                return;
+            }
+
+            // Strip auth token from result.
+            result.remove(AccountManager.KEY_AUTHTOKEN);
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG,
+                        getClass().getSimpleName() + " calling onResult() on response " + response);
+            }
+
+            // Get the session bundle created by authenticator. The
+            // bundle contains data necessary for finishing the session
+            // later. The session bundle will be encrypted here and
+            // decrypted later when trying to finish the session.
+            Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+            if (sessionBundle != null) {
+                String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                if (TextUtils.isEmpty(accountType)
+                        && !mAccountType.equalsIgnoreCase(mAccountType)) {
+                    Log.w(TAG, "Account type in session bundle doesn't match request.");
+                }
+                // Add accountType info to session bundle. This will
+                // override any value set by authenticator.
+                sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+
+                // Encrypt session bundle before returning to caller.
+                try {
+                    CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+                    Bundle encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
+                    result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, encryptedBundle);
+                } catch (GeneralSecurityException e) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.v(TAG, "Failed to encrypt session bundle!", e);
+                    }
+                    sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "failed to encrypt session bundle");
+                    return;
+                }
+            }
+
+            sendResponse(response, result);
+        }
+    }
+
     private void showCantAddAccount(int errorCode, int userId) {
         Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
         cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode);
@@ -2382,6 +2574,60 @@
     }
 
     @Override
+    public void startUpdateCredentialsSession(
+            IAccountManagerResponse response,
+            final Account account,
+            final String authTokenType,
+            final boolean expectActivityLaunch,
+            final Bundle loginOptions) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG,
+                    "startUpdateCredentialsSession: " + account + ", response " + response
+                            + ", authTokenType " + authTokenType + ", expectActivityLaunch "
+                            + expectActivityLaunch + ", caller's uid " + Binder.getCallingUid()
+                            + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) {
+            throw new IllegalArgumentException("response is null");
+        }
+        if (account == null) {
+            throw new IllegalArgumentException("account is null");
+        }
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            UserAccounts accounts = getUserAccounts(userId);
+            new StartAccountSession(
+                    accounts,
+                    response,
+                    account.type,
+                    expectActivityLaunch,
+                    account.name,
+                    false /* authDetailsRequired */,
+                    true /* updateLastCredentialTime */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.startUpdateCredentialsSession(this, account, authTokenType,
+                            loginOptions);
+                }
+
+                @Override
+                protected String toDebugString(long now) {
+                    if (loginOptions != null)
+                        loginOptions.keySet();
+                    return super.toDebugString(now)
+                            + ", startUpdateCredentialsSession"
+                            + ", " + account
+                            + ", authTokenType " + authTokenType
+                            + ", loginOptions " + loginOptions;
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
     public void editProperties(IAccountManagerResponse response, final String accountType,
             final boolean expectActivityLaunch) {
         final int callingUid = Binder.getCallingUid();
@@ -3336,6 +3582,11 @@
         private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
         private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
 
+        // TODO: This action doesn't add account to accountdb. Account is only
+        // added in finishAddAccount or finishAddAccountAsUser which may be in
+        // a different user profile.
+        private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
+
         private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
         private static void createDebugTable(SQLiteDatabase db) {
@@ -4300,4 +4551,29 @@
             return mContext;
         }
     }
+
+    private void sendResponse(IAccountManagerResponse response, Bundle result) {
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
+
+    private void sendErrorResponse(IAccountManagerResponse response, int errorCode,
+            String errorMessage) {
+        try {
+            response.onError(errorCode, errorMessage);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/accounts/CryptoHelper.java b/services/core/java/com/android/server/accounts/CryptoHelper.java
new file mode 100644
index 0000000..2b59b74
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/CryptoHelper.java
@@ -0,0 +1,140 @@
+package com.android.server.accounts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
+ * key for {@link AccountManagerService}.
+ */
+/* default */ class CryptoHelper {
+    private static final String TAG = "Account";
+
+    private static final String KEY_CIPHER = "cipher";
+    private static final String KEY_MAC = "mac";
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+    private static final String MAC_ALGORITHM = "HMACSHA256";
+    private static final int IV_LENGTH = 16;
+
+    private static CryptoHelper sInstance;
+    // Keys used for encrypting and decrypting data returned in a Bundle.
+    private final SecretKeySpec mCipherKeySpec;
+    private final SecretKeySpec mMacKeySpec;
+    private final IvParameterSpec mIv;
+
+    /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
+        if (sInstance == null) {
+            sInstance = new CryptoHelper();
+        }
+        return sInstance;
+    }
+
+    private CryptoHelper() throws NoSuchAlgorithmException {
+        KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
+        SecretKey skey = kgen.generateKey();
+        mCipherKeySpec = new SecretKeySpec(skey.getEncoded(), KEY_ALGORITHM);
+
+        kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
+        skey = kgen.generateKey();
+        mMacKeySpec = new SecretKeySpec(skey.getEncoded(), MAC_ALGORITHM);
+
+        // Create random iv
+        byte[] iv = new byte[IV_LENGTH];
+        SecureRandom secureRandom = new SecureRandom();
+        secureRandom.nextBytes(iv);
+        mIv = new IvParameterSpec(iv);
+    }
+
+    @NonNull
+    /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+        Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
+        Parcel parcel = Parcel.obtain();
+        bundle.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+
+        Bundle encryptedBundle = new Bundle();
+
+        byte[] cipher = encrypt(bytes);
+        byte[] mac = createMac(cipher);
+
+        encryptedBundle.putByteArray(KEY_CIPHER, cipher);
+        encryptedBundle.putByteArray(KEY_MAC, mac);
+
+        return encryptedBundle;
+    }
+
+    @Nullable
+    /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+        Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
+        byte[] cipherArray = bundle.getByteArray(KEY_CIPHER);
+        byte[] macArray = bundle.getByteArray(KEY_MAC);
+
+        if (!verifyMac(cipherArray, macArray)) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Escrow mac mismatched!");
+            }
+            return null;
+        }
+
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, mCipherKeySpec, mIv);
+        byte[] decryptedBytes = cipher.doFinal(cipherArray);
+
+        Parcel decryptedParcel = Parcel.obtain();
+        decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
+        decryptedParcel.setDataPosition(0);
+        Bundle decryptedBundle = new Bundle();
+        decryptedBundle.readFromParcel(decryptedParcel);
+        decryptedParcel.recycle();
+        return decryptedBundle;
+    }
+
+    private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] macArray)
+            throws GeneralSecurityException {
+
+        if (cipherArray == null || cipherArray.length == 0 || macArray == null
+                || macArray.length == 0) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Cipher or MAC is empty!");
+            }
+            return false;
+        }
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mMacKeySpec);
+        mac.update(cipherArray);
+        return Arrays.equals(macArray, mac.doFinal());
+    }
+
+    @NonNull
+    private byte[] encrypt(@NonNull byte[] data) throws GeneralSecurityException {
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, mCipherKeySpec, mIv);
+        return cipher.doFinal(data);
+    }
+
+    @NonNull
+    private byte[] createMac(@NonNull byte[] cipher) throws GeneralSecurityException {
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mMacKeySpec);
+        return mac.doFinal(cipher);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2820216..17b3d2a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -168,13 +168,10 @@
      */
     class ServiceMap extends Handler {
         final int mUserId;
-        final ArrayMap<ComponentName, ServiceRecord> mServicesByName
-                = new ArrayMap<ComponentName, ServiceRecord>();
-        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
-                = new ArrayMap<Intent.FilterComparison, ServiceRecord>();
+        final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
 
-        final ArrayList<ServiceRecord> mDelayedStartList
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
         /* XXX eventually I'd like to have this based on processes instead of services.
          * That is, if we try to start two services in a row both running in the same
          * process, this should be one entry in mStartingBackground for that one process
@@ -185,8 +182,7 @@
                 = new ArrayList<DelayingProcess>();
         */
 
-        final ArrayList<ServiceRecord> mStartingBackground
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
 
         static final int MSG_BG_START_TIMEOUT = 1;
 
@@ -338,7 +334,7 @@
         ServiceRecord r = res.record;
 
         if (!mAm.mUserController.exists(r.userId)) {
-            Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
+            Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
             return null;
         }
 
@@ -510,6 +506,35 @@
         return 0;
     }
 
+    void stopInBackgroundLocked(int uid) {
+        // Stop all services associated with this uid due to it going to the background
+        // stopped state.
+        ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
+        ArrayList<ServiceRecord> stopping = null;
+        if (services != null) {
+            for (int i=services.mServicesByName.size()-1; i>=0; i--) {
+                ServiceRecord service = services.mServicesByName.valueAt(i);
+                if (service.appInfo.uid == uid && service.startRequested) {
+                    if (mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                            uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
+                        if (stopping == null) {
+                            stopping = new ArrayList<>();
+                            stopping.add(service);
+                        }
+                    }
+                }
+            }
+            if (stopping != null) {
+                for (int i=stopping.size()-1; i>=0; i--) {
+                    ServiceRecord service = stopping.get(i);
+                    service.delayed = false;
+                    services.ensureNotStartingBackground(service);
+                    stopServiceLocked(service);
+                }
+            }
+        }
+    }
+
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1069,6 +1094,22 @@
                 }
                 r = smap.mServicesByName.get(name);
                 if (r == null && createIfNeeded) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // Before going further -- if this app is not allowed to run in the
+                        // background, then at this point we aren't going to let it period.
+                        if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+                                sInfo.packageName, callingPid)) {
+                            Slog.w(TAG, "Background execution not allowed: service "
+                                    + r.intent + " to " + name.flattenToShortString()
+                                    + " from pid=" + callingPid + " uid=" + callingUid
+                                    + " pkg=" + callingPackage);
+                            return null;
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+
                     Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     ServiceRestarter res = new ServiceRestarter();
@@ -1527,7 +1568,7 @@
             synchronized (r.stats.getBatteryStats()) {
                 r.stats.startLaunchedLocked();
             }
-            mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
+            mAm.notifyPackageUse(r.serviceInfo.packageName);
             app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
             app.thread.scheduleCreateService(r, r.serviceInfo,
                     mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4998b57..29173a5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,82 +16,19 @@
 
 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.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_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;
-import static com.android.internal.util.XmlUtils.readIntAttribute;
-import static com.android.internal.util.XmlUtils.readLongAttribute;
-import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
-import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
-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;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.Manifest;
-import android.app.ActivityManager.StackId;
-import android.app.AppOpsManager;
-import android.app.ApplicationThreadNative;
-import android.app.BroadcastOptions;
-import android.app.IActivityContainer;
-import android.app.IActivityContainerCallback;
-import android.app.IAppTask;
-import android.app.ITaskStackListener;
-import android.app.ProfilerInfo;
-import android.app.assist.AssistContent;
-import android.app.assist.AssistStructure;
-import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
-import android.content.pm.AppsQueryHelper;
-import android.content.pm.PermissionInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.BatteryStats;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.os.ResultReceiver;
-import android.os.Trace;
-import android.os.TransactionTooLargeException;
-import android.os.WorkSource;
-import android.os.storage.IMountService;
-import android.os.storage.MountServiceInternal;
-import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.VoiceInteractionSession;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.DebugUtils;
-import android.view.Display;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.DumpHeapActivity;
+import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.ProcessStats;
+import com.android.internal.app.SystemUserHomeActivity;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.IResultReceiver;
@@ -109,31 +46,27 @@
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
-import com.android.server.pm.UserManagerService;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
+import android.app.ActivityManager.TaskThumbnailInfo;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityManagerNative;
@@ -141,24 +74,37 @@
 import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationThreadNative;
+import android.app.BroadcastOptions;
 import android.app.Dialog;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
 import android.app.IActivityController;
+import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
 import android.app.IProcessObserver;
 import android.app.IServiceConnection;
 import android.app.IStopUserCallback;
-import android.app.IUidObserver;
+import android.app.ITaskStackListener;
 import android.app.IUiAutomationConnection;
+import android.app.IUidObserver;
 import android.app.IUserSwitchObserver;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.backup.IBackupManager;
+import android.app.ProfilerInfo;
 import android.app.admin.DevicePolicyManager;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.backup.IBackupManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
@@ -182,18 +128,24 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.UserInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.net.Proxy;
 import android.net.ProxyInfo;
 import android.net.Uri;
+import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -211,22 +163,37 @@
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.TransactionTooLargeException;
 import android.os.UpdateLock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
+import android.os.storage.IMountService;
+import android.os.storage.MountServiceInternal;
+import android.os.storage.StorageManager;
 import android.provider.Settings;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionSession;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.DebugUtils;
 import android.util.EventLog;
+import android.util.LocaleList;
 import android.util.Log;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
@@ -234,13 +201,12 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.Xml;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 
-import dalvik.system.VMRuntime;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
@@ -270,6 +236,100 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
+import dalvik.system.VMRuntime;
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
+import static android.provider.Settings.Global.DEBUG_APP;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
+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.ActivityStackSupervisor.FORCE_FOCUS;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
 public final class ActivityManagerService extends ActivityManagerNative
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
 
@@ -330,6 +390,11 @@
     // before we decide it must be hung.
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
 
+    // How long we will retain processes hosting content providers in the "last activity"
+    // state before allowing them to drop down to the regular cached LRU list.  This is
+    // to avoid thrashing of provider processes under low memory situations.
+    static final int CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
+
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real, when the process was
     // started with a wrapper for instrumentation (such as Valgrind) because it
@@ -381,6 +446,10 @@
     // Maximum number of users we allow to be running at a time.
     static final int MAX_RUNNING_USERS = 3;
 
+    // This is the amount of time we allow an app to settle after it goes into the background,
+    // before we start restricting what it can do.
+    static final int BACKGROUND_SETTLE_TIME = 1*60*1000;
+
     // 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;
@@ -709,6 +778,12 @@
     final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     /**
+     * This is for verifying the UID report flow.
+     */
+    static final boolean VALIDATE_UID_STATES = true;
+    final SparseArray<UidRecord> mValidateUids = new SparseArray<>();
+
+    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1197,7 +1272,8 @@
     String mOrigDebugApp = null;
     boolean mOrigWaitForDebugger = false;
     boolean mAlwaysFinishActivities = false;
-    boolean mForceResizableActivites;
+    boolean mForceResizableActivities;
+    boolean mSupportsFreeformWindowManagement;
     IActivityController mController = null;
     String mProfileApp = null;
     ProcessRecord mProfileProc = null;
@@ -1303,29 +1379,29 @@
         }
     }
 
-    static final int SHOW_ERROR_MSG = 1;
-    static final int SHOW_NOT_RESPONDING_MSG = 2;
-    static final int SHOW_FACTORY_ERROR_MSG = 3;
+    static final int SHOW_ERROR_UI_MSG = 1;
+    static final int SHOW_NOT_RESPONDING_UI_MSG = 2;
+    static final int SHOW_FACTORY_ERROR_UI_MSG = 3;
     static final int UPDATE_CONFIGURATION_MSG = 4;
     static final int GC_BACKGROUND_PROCESSES_MSG = 5;
-    static final int WAIT_FOR_DEBUGGER_MSG = 6;
+    static final int WAIT_FOR_DEBUGGER_UI_MSG = 6;
     static final int SERVICE_TIMEOUT_MSG = 12;
     static final int UPDATE_TIME_ZONE = 13;
-    static final int SHOW_UID_ERROR_MSG = 14;
-    static final int SHOW_FINGERPRINT_ERROR_MSG = 15;
+    static final int SHOW_UID_ERROR_UI_MSG = 14;
+    static final int SHOW_FINGERPRINT_ERROR_UI_MSG = 15;
     static final int PROC_START_TIMEOUT_MSG = 20;
     static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
     static final int KILL_APPLICATION_MSG = 22;
     static final int FINALIZE_PENDING_INTENT_MSG = 23;
     static final int POST_HEAVY_NOTIFICATION_MSG = 24;
     static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
-    static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+    static final int SHOW_STRICT_MODE_VIOLATION_UI_MSG = 26;
     static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
     static final int CLEAR_DNS_CACHE_MSG = 28;
     static final int UPDATE_HTTP_PROXY_MSG = 29;
-    static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
-    static final int DISPATCH_PROCESSES_CHANGED = 31;
-    static final int DISPATCH_PROCESS_DIED = 32;
+    static final int SHOW_COMPAT_MODE_DIALOG_UI_MSG = 30;
+    static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
+    static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
     static final int REPORT_MEM_USAGE_MSG = 33;
     static final int REPORT_USER_SWITCH_MSG = 34;
     static final int CONTINUE_USER_SWITCH_MSG = 35;
@@ -1339,20 +1415,21 @@
     static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
-    static final int START_USER_SWITCH_MSG = 46;
+    static final int START_USER_SWITCH_UI_MSG = 46;
     static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
-    static final int DISMISS_DIALOG_MSG = 48;
+    static final int DISMISS_DIALOG_UI_MSG = 48;
     static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
     static final int DELETE_DUMPHEAP_MSG = 52;
     static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
-    static final int DISPATCH_UIDS_CHANGED_MSG = 54;
+    static final int DISPATCH_UIDS_CHANGED_UI_MSG = 54;
     static final int REPORT_TIME_TRACKER_MSG = 55;
     static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
     static final int APP_BOOST_DEACTIVATE_MSG = 58;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
+    static final int IDLE_UIDS_MSG = 60;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1387,7 +1464,7 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case SHOW_ERROR_MSG: {
+            case SHOW_ERROR_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
@@ -1432,7 +1509,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_NOT_RESPONDING_MSG: {
+            case SHOW_NOT_RESPONDING_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                     ProcessRecord proc = (ProcessRecord)data.get("app");
@@ -1464,7 +1541,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_STRICT_MODE_VIOLATION_MSG: {
+            case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord proc = (ProcessRecord) data.get("app");
@@ -1490,13 +1567,13 @@
                 }
                 ensureBootCompleted();
             } break;
-            case SHOW_FACTORY_ERROR_MSG: {
+            case SHOW_FACTORY_ERROR_UI_MSG: {
                 Dialog d = new FactoryErrorDialog(
                     mContext, msg.getData().getCharSequence("msg"));
                 d.show();
                 ensureBootCompleted();
             } break;
-            case WAIT_FOR_DEBUGGER_MSG: {
+            case WAIT_FOR_DEBUGGER_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord app = (ProcessRecord)msg.obj;
                     if (msg.arg1 != 0) {
@@ -1516,7 +1593,7 @@
                     }
                 }
             } break;
-            case SHOW_UID_ERROR_MSG: {
+            case SHOW_UID_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1524,11 +1601,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_wipe_data));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_FINGERPRINT_ERROR_MSG: {
+            case SHOW_FINGERPRINT_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1536,11 +1613,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_manufacturer));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_COMPAT_MODE_DIALOG_MSG: {
+            case SHOW_COMPAT_MODE_DIALOG_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ActivityRecord ar = (ActivityRecord) msg.obj;
                     if (mCompatModeDialog != null) {
@@ -1568,26 +1645,26 @@
                 }
                 break;
             }
-            case START_USER_SWITCH_MSG: {
+            case START_USER_SWITCH_UI_MSG: {
                 mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
                 break;
             }
-            case DISMISS_DIALOG_MSG: {
+            case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
                 break;
             }
-            case DISPATCH_PROCESSES_CHANGED: {
+            case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
                 dispatchProcessesChanged();
                 break;
             }
-            case DISPATCH_PROCESS_DIED: {
+            case DISPATCH_PROCESS_DIED_UI_MSG: {
                 final int pid = msg.arg1;
                 final int uid = msg.arg2;
                 dispatchProcessDied(pid, uid);
                 break;
             }
-            case DISPATCH_UIDS_CHANGED_MSG: {
+            case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
             }
@@ -2039,7 +2116,7 @@
                 // it is finished we make sure it is reset to its default.
                 mUserIsMonkey = false;
             } break;
-            case APP_BOOST_DEACTIVATE_MSG : {
+            case APP_BOOST_DEACTIVATE_MSG: {
                 synchronized(ActivityManagerService.this) {
                     if (mIsBoosted) {
                         if (mBoostStartTime < (SystemClock.uptimeMillis() - APP_BOOST_TIMEOUT)) {
@@ -2053,6 +2130,9 @@
                     }
                 }
             } break;
+            case IDLE_UIDS_MSG: {
+                idleUids();
+            } break;
             }
         }
     };
@@ -2345,6 +2425,17 @@
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
         mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+        mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+                new IAppOpsCallback.Stub() {
+                    @Override public void opChanged(int op, int uid, String packageName) {
+                        if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
+                            if (mAppOpsService.checkOperation(op, uid, packageName)
+                                    != AppOpsManager.MODE_ALLOWED) {
+                                runInBackgroundDisabled(uid);
+                            }
+                        }
+                    }
+                });
 
         mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
 
@@ -2356,7 +2447,7 @@
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
 
         mConfiguration.setToDefaults();
-        mConfiguration.setLocale(Locale.getDefault());
+        mConfiguration.setLocales(LocaleList.getDefault());
 
         mConfigurationSeq = mConfiguration.seq = 1;
         mProcessCpuTracker.init();
@@ -2759,7 +2850,7 @@
 
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
-        msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
+        msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
         msg.obj = r.task.askedCompatMode ? null : r;
         mUiHandler.sendMessage(msg);
     }
@@ -3060,12 +3151,10 @@
         return proc;
     }
 
-    void ensurePackageDexOpt(String packageName) {
+    void notifyPackageUse(String packageName) {
         IPackageManager pm = AppGlobals.getPackageManager();
         try {
-            if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) {
-                mDidDexOpt = true;
-            }
+            pm.notifyPackageUse(packageName);
         } catch (RemoteException e) {
         }
     }
@@ -3332,15 +3421,6 @@
             if ("1".equals(SystemProperties.get("debug.checkjni"))) {
                 debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
             }
-            String jitDebugProperty = SystemProperties.get("debug.usejit");
-            if ("true".equals(jitDebugProperty)) {
-                debugFlags |= Zygote.DEBUG_ENABLE_JIT;
-            } else if (!"false".equals(jitDebugProperty)) {
-                // If we didn't force disable by setting false, defer to the dalvik vm options.
-                if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_JIT;
-                }
-            }
             String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
             if ("true".equals(genDebugInfoProperty)) {
                 debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
@@ -3792,8 +3872,10 @@
             for (int i=0; i<N; i++) {
                 final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
                 mActiveUidChanges[i] = change;
-                change.uidRecord.pendingChange = null;
-                change.uidRecord = null;
+                if (change.uidRecord != null) {
+                    change.uidRecord.pendingChange = null;
+                    change.uidRecord = null;
+                }
             }
             mPendingUidChanges.clear();
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
@@ -3803,7 +3885,8 @@
         if (mLocalPowerManager != null) {
             for (int j=0; j<N; j++) {
                 UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if (item.gone) {
+                if (item.change == UidRecord.CHANGE_GONE
+                        || item.change == UidRecord.CHANGE_GONE_IDLE) {
                     mLocalPowerManager.uidGone(item.uid);
                 } else {
                     mLocalPowerManager.updateUidProcState(item.uid, item.processState);
@@ -3815,19 +3898,66 @@
         while (i > 0) {
             i--;
             final IUidObserver observer = mUidObservers.getBroadcastItem(i);
+            final int which = (Integer)mUidObservers.getBroadcastCookie(i);
             if (observer != null) {
                 try {
                     for (int j=0; j<N; j++) {
                         UidRecord.ChangeItem item = mActiveUidChanges[j];
-                        if (item.gone) {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID gone uid=" + item.uid);
-                            observer.onUidGone(item.uid);
+                        final int change = item.change;
+                        UidRecord validateUid = null;
+                        if (VALIDATE_UID_STATES && i == 0) {
+                            validateUid = mValidateUids.get(item.uid);
+                            if (validateUid == null && change != UidRecord.CHANGE_GONE
+                                    && change != UidRecord.CHANGE_GONE_IDLE) {
+                                validateUid = new UidRecord(item.uid);
+                                mValidateUids.put(item.uid, validateUid);
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_IDLE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID idle uid=" + item.uid);
+                                observer.onUidIdle(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    validateUid.idle = true;
+                                }
+                            }
+                        } else if (change == UidRecord.CHANGE_ACTIVE) {
+                            if ((which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID active uid=" + item.uid);
+                                observer.onUidActive(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.idle = false;
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_GONE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID gone uid=" + item.uid);
+                                observer.onUidGone(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    mValidateUids.remove(item.uid);
+                                }
+                            }
                         } else {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID CHANGED uid=" + item.uid
-                                    + ": " + item.processState);
-                            observer.onUidStateChanged(item.uid, item.processState);
+                            if ((which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID CHANGED uid=" + item.uid
+                                                + ": " + item.processState);
+                                observer.onUidStateChanged(item.uid, item.processState);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.curProcState = validateUid.setProcState
+                                        = item.processState;
+                            }
                         }
                     }
                 } catch (RemoteException e) {
@@ -4183,13 +4313,15 @@
                         "startActivityFromRecentsInner: Task " + taskId + " not found.");
             }
 
-            if (launchStackId != INVALID_STACK_ID && task.stack.mStackId != launchStackId) {
+            if (launchStackId != INVALID_STACK_ID) {
                 if (launchStackId == DOCKED_STACK_ID && bOptions != null) {
                     ActivityOptions activityOptions = new ActivityOptions(bOptions);
                     mWindowManager.setDockedStackCreateMode(activityOptions.getDockCreateMode());
                 }
-                mStackSupervisor.moveTaskToStackLocked(
-                        taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+                if (task.stack.mStackId != launchStackId) {
+                    mStackSupervisor.moveTaskToStackLocked(
+                            taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+                }
             }
 
             if (task.getRootActivity() != null) {
@@ -5095,7 +5227,7 @@
             // Bring up the infamous App Not Responding dialog
             Message msg = Message.obtain();
             HashMap<String, Object> map = new HashMap<String, Object>();
-            msg.what = SHOW_NOT_RESPONDING_MSG;
+            msg.what = SHOW_NOT_RESPONDING_UI_MSG;
             msg.obj = map;
             msg.arg1 = aboveSystem ? 1 : 0;
             map.put("app", app);
@@ -5874,7 +6006,7 @@
                 // No more processes using this uid, tell clients it is gone.
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "No more processes in " + old.uidRecord);
-                enqueueUidChangeLocked(old.uidRecord, true);
+                enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
                 mActiveUids.remove(uid);
             }
             old.uidRecord = null;
@@ -5900,7 +6032,7 @@
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                     "Creating new process uid: " + uidRec);
             mActiveUids.put(proc.uid, uidRec);
-            enqueueUidChangeLocked(uidRec, false);
+            enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
         }
         proc.uidRecord = uidRec;
         uidRec.numProcs++;
@@ -6132,11 +6264,11 @@
                         || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
             }
 
-            ensurePackageDexOpt(app.instrumentationInfo != null
+            notifyPackageUse(app.instrumentationInfo != null
                     ? app.instrumentationInfo.packageName
                     : app.info.packageName);
             if (app.instrumentationClass != null) {
-                ensurePackageDexOpt(app.instrumentationClass.getPackageName());
+                notifyPackageUse(app.instrumentationClass.getPackageName());
             }
             if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
                     + processName + " with config " + mConfiguration);
@@ -6216,7 +6348,7 @@
         if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) {
             if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
                     "New app is backup target, launching agent for " + app);
-            ensurePackageDexOpt(mBackupTarget.appInfo.packageName);
+            notifyPackageUse(mBackupTarget.appInfo.packageName);
             try {
                 thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
                         compatibilityInfoForPackageLocked(mBackupTarget.appInfo),
@@ -6708,9 +6840,11 @@
         if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
             activity = ActivityRecord.isInStackLocked(token);
             if (activity == null) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + token + " not in any stack");
                 return null;
             }
             if (activity.finishing) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + activity + " is finishing");
                 return null;
             }
         }
@@ -7262,6 +7396,36 @@
         return readMet && writeMet;
     }
 
+    public int getAppStartMode(int uid, String packageName) {
+        synchronized (this) {
+            boolean bg = checkAllowBackgroundLocked(uid, packageName, -1);
+            return bg ? ActivityManager.APP_START_MODE_NORMAL
+                    : ActivityManager.APP_START_MODE_DISABLED;
+        }
+    }
+
+    boolean checkAllowBackgroundLocked(int uid, String packageName, int callingPid) {
+        UidRecord uidRec = mActiveUids.get(uid);
+        if (uidRec == null || uidRec.idle) {
+            if (callingPid >= 0) {
+                ProcessRecord proc;
+                synchronized (mPidsSelfLocked) {
+                    proc = mPidsSelfLocked.get(callingPid);
+                }
+                if (proc != null && proc.curProcState < ActivityManager.PROCESS_STATE_RECEIVER) {
+                    // Whoever is instigating this is in the foreground, so we will allow it
+                    // to go through.
+                    return true;
+                }
+            }
+            if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private ProviderInfo getProviderInfoLocked(String authority, int userHandle) {
         ProviderInfo pi = null;
         ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -8261,7 +8425,7 @@
             if (app == null) return;
 
             Message msg = Message.obtain();
-            msg.what = WAIT_FOR_DEBUGGER_MSG;
+            msg.what = WAIT_FOR_DEBUGGER_UI_MSG;
             msg.obj = app;
             msg.arg1 = waiting ? 1 : 0;
             mUiHandler.sendMessage(msg);
@@ -8575,8 +8739,16 @@
                     }
                 }
 
+                // Use the full screen as the context for the task thumbnail
+                final Point displaySize = new Point();
+                final TaskThumbnailInfo thumbnailInfo = new TaskThumbnailInfo();
+                r.task.stack.getDisplaySize(displaySize);
+                thumbnailInfo.taskWidth = displaySize.x;
+                thumbnailInfo.taskHeight = displaySize.y;
+                thumbnailInfo.screenOrientation = mConfiguration.orientation;
+
                 TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo,
-                        intent, description);
+                        intent, description, thumbnailInfo);
 
                 int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
                 if (trimIdx >= 0) {
@@ -8595,7 +8767,7 @@
                 mRecentTasks.add(task);
                 r.task.stack.addTask(task, false, false);
 
-                task.setLastThumbnail(thumbnail);
+                task.setLastThumbnailLocked(thumbnail);
                 task.freeLastThumbnail();
 
                 return task.taskId;
@@ -8690,12 +8862,25 @@
         Rect rect = new Rect();
         try {
             synchronized (this) {
-                TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
+                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
                 if (task == null) {
                     Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
                     return rect;
                 }
-                mWindowManager.getTaskBounds(task.taskId, rect);
+                if (task.stack != null) {
+                    // Return the bounds from window manager since it will be adjusted for various
+                    // things like the presense of a docked stack for tasks that aren't resizeable.
+                    mWindowManager.getTaskBounds(task.taskId, rect);
+                } else {
+                    // Task isn't in window manager yet since it isn't associated with a stack.
+                    // Return the persist value from activity manager
+                    if (task.mBounds != null) {
+                        rect.set(task.mBounds);
+                    } else if (task.mLastNonFullscreenBounds != null) {
+                        rect.set(task.mLastNonFullscreenBounds);
+                    }
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -8704,12 +8889,19 @@
     }
 
     @Override
-    public Bitmap getTaskDescriptionIcon(String filename) {
-        if (!FileUtils.isValidExtFilename(filename)
-                || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
-            throw new IllegalArgumentException("Bad filename: " + filename);
+    public Bitmap getTaskDescriptionIcon(String filePath, int userId) {
+        if (userId != UserHandle.getCallingUserId()) {
+            enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "getTaskDescriptionIcon");
         }
-        return mTaskPersister.getTaskDescriptionIcon(filename);
+        final File passedIconFile = new File(filePath);
+        final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId),
+                passedIconFile.getName());
+        if (!legitIconFile.getPath().equals(filePath)
+                || !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
+            throw new IllegalArgumentException("Bad file path: " + filePath);
+        }
+        return mTaskPersister.getTaskDescriptionIcon(filePath);
     }
 
     @Override
@@ -9447,7 +9639,7 @@
                     app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
                             mProcessStats);
                 }
-                ensurePackageDexOpt(cpi.applicationInfo.packageName);
+                notifyPackageUse(cpi.applicationInfo.packageName);
             }
         }
         return providers;
@@ -9623,6 +9815,14 @@
             if (conn.stableCount == 0 && conn.unstableCount == 0) {
                 cpr.connections.remove(conn);
                 conn.client.conProviders.remove(conn);
+                if (conn.client.setProcState < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+                    // The client is more important than last activity -- note the time this
+                    // is happening, so we keep the old provider process around a bit as last
+                    // activity to avoid thrashing it.
+                    if (cpr.proc != null) {
+                        cpr.proc.lastProviderTime = SystemClock.uptimeMillis();
+                    }
+                }
                 stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.name);
                 return true;
             }
@@ -11177,11 +11377,12 @@
         }
     }
 
-    public void registerUidObserver(IUidObserver observer) {
+    @Override
+    public void registerUidObserver(IUidObserver observer, int which) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
         synchronized (this) {
-            mUidObservers.register(observer);
+            mUidObservers.register(observer, which);
         }
     }
 
@@ -11673,21 +11874,21 @@
 
     private void retrieveSettings() {
         final ContentResolver resolver = mContext.getContentResolver();
-        String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP);
-        boolean waitForDebugger = Settings.Global.getInt(
-            resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
-        boolean alwaysFinishActivities = Settings.Global.getInt(
-            resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
-        boolean forceRtl = Settings.Global.getInt(
-                resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
-        int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
-        boolean forceResizable = Settings.Global.getInt(
-                resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
-                defaultForceResizable) != 0;
-        // Transfer any global setting for forcing RTL layout, into a System Property
-        SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+        final boolean freeformWindowManagement =
+                mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
 
-        Configuration configuration = new Configuration();
+        final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+        final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
+        final boolean alwaysFinishActivities =
+                Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+        final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
+        final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
+        final boolean forceResizable = Settings.Global.getInt(
+                resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, defaultForceResizable) != 0;
+        // Transfer any global setting for forcing RTL layout, into a System Property
+        SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+
+        final Configuration configuration = new Configuration();
         Settings.System.getConfiguration(resolver, configuration);
         if (forceRtl) {
             // This will take care of setting the correct layout direction flags
@@ -11698,7 +11899,8 @@
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
             mAlwaysFinishActivities = alwaysFinishActivities;
-            mForceResizableActivites = forceResizable;
+            mForceResizableActivities = forceResizable;
+            mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
             // This happens before any activities are started, so we can
             // change mConfiguration in-place.
             updateConfigurationLocked(configuration, null, true);
@@ -12020,7 +12222,7 @@
                     mTopData = null;
                     mTopComponent = null;
                     Message msg = Message.obtain();
-                    msg.what = SHOW_FACTORY_ERROR_MSG;
+                    msg.what = SHOW_FACTORY_ERROR_UI_MSG;
                     msg.getData().putCharSequence("msg", errorMsg);
                     mUiHandler.sendMessage(msg);
                 }
@@ -12066,24 +12268,33 @@
                 }
             }
 
-            enableSystemUserApps();
-
             // Start up initial activity.
             mBooting = true;
+            // Enable home activity for system user, so that the system can always boot
+            if (UserManager.isSplitSystemUser()) {
+                ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
+                try {
+                    AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
+                            UserHandle.USER_SYSTEM);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
             startHomeActivityLocked(currentUserId, "systemReady");
 
             try {
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
                     Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your"
                             + " data partition or your device will be unstable.");
-                    mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
+                    mUiHandler.obtainMessage(SHOW_UID_ERROR_UI_MSG).sendToTarget();
                 }
             } catch (RemoteException e) {
             }
 
             if (!Build.isBuildConsistent()) {
                 Slog.e(TAG, "Build fingerprint is not consistent, warning user");
-                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
+                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_UI_MSG).sendToTarget();
             }
 
             long ident = Binder.clearCallingIdentity();
@@ -12119,43 +12330,6 @@
         }
     }
 
-    private void enableSystemUserApps() {
-        // For system user, enable apps based on the following conditions:
-        // - app is whitelisted; or has no launcher icons; or has INTERACT_ACROSS_USERS permission
-        // - app is not in the blacklist
-        if (UserManager.isSplitSystemUser()) {
-            AppsQueryHelper queryHelper = new AppsQueryHelper(mContext);
-            Set<String> enableApps = new HashSet<>();
-            enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
-                            | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
-                            | AppsQueryHelper.GET_DEFAULT_IMES,
-                            /* systemAppsOnly */ true, UserHandle.SYSTEM));
-            ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
-            enableApps.addAll(wlApps);
-            ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
-            enableApps.removeAll(blApps);
-
-            List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
-                    UserHandle.SYSTEM);
-            final int systemAppsSize = systemApps.size();
-            for (int i = 0; i < systemAppsSize; i++) {
-                String pName = systemApps.get(i);
-                boolean enable = enableApps.contains(pName);
-                try {
-                    if (enable) {
-                        AppGlobals.getPackageManager().installExistingPackageAsUser(pName,
-                                UserHandle.USER_SYSTEM);
-                    } else {
-                        AppGlobals.getPackageManager().deletePackageAsUser(pName, null,
-                                UserHandle.USER_SYSTEM, PackageManager.DELETE_SYSTEM_APP);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error occured when processing package " + pName, e);
-                }
-            }
-        }
-    }
-
     private boolean makeAppCrashingLocked(ProcessRecord app,
             String shortMsg, String longMsg, String stackTrace) {
         app.crashing = true;
@@ -12401,7 +12575,7 @@
                 final long origId = Binder.clearCallingIdentity();
 
                 Message msg = Message.obtain();
-                msg.what = SHOW_STRICT_MODE_VIOLATION_MSG;
+                msg.what = SHOW_STRICT_MODE_VIOLATION_UI_MSG;
                 HashMap<String, Object> data = new HashMap<String, Object>();
                 data.put("result", result);
                 data.put("app", r);
@@ -12858,7 +13032,7 @@
             }
 
             Message msg = Message.obtain();
-            msg.what = SHOW_ERROR_MSG;
+            msg.what = SHOW_ERROR_UI_MSG;
             HashMap data = new HashMap();
             data.put("result", result);
             data.put("app", r);
@@ -13487,6 +13661,39 @@
         }
     }
 
+    boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+            String header, boolean needSep) {
+        boolean printed = false;
+        int whichAppId = -1;
+        if (dumpPackage != null) {
+            try {
+                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                        dumpPackage, 0);
+                whichAppId = UserHandle.getAppId(info.uid);
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        for (int i=0; i<uids.size(); i++) {
+            UidRecord uidRec = uids.valueAt(i);
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+                continue;
+            }
+            if (!printed) {
+                printed = true;
+                if (needSep) {
+                    pw.println();
+                }
+                pw.print("  ");
+                pw.println(header);
+                needSep = true;
+            }
+            pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
+            pw.print(": "); pw.println(uidRec);
+        }
+        return printed;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -13543,33 +13750,13 @@
         }
 
         if (mActiveUids.size() > 0) {
-            boolean printed = false;
-            int whichAppId = -1;
-            if (dumpPackage != null) {
-                try {
-                    ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
-                            dumpPackage, 0);
-                    whichAppId = UserHandle.getAppId(info.uid);
-                } catch (NameNotFoundException e) {
-                    e.printStackTrace();
-                }
+            if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
+                printedAnything = needSep = true;
             }
-            for (int i=0; i<mActiveUids.size(); i++) {
-                UidRecord uidRec = mActiveUids.valueAt(i);
-                if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
-                    continue;
-                }
-                if (!printed) {
-                    printed = true;
-                    if (needSep) {
-                        pw.println();
-                    }
-                    pw.println("  UID states:");
-                    needSep = true;
-                    printedAnything = true;
-                }
-                pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
-                pw.print(": "); pw.println(uidRec);
+        }
+        if (mValidateUids.size() > 0) {
+            if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
+                printedAnything = needSep = true;
             }
         }
 
@@ -14460,6 +14647,9 @@
                 if (object1.first.setAdj != object2.first.setAdj) {
                     return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
                 }
+                if (object1.first.setProcState != object2.first.setProcState) {
+                    return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
+                }
                 if (object1.second.intValue() != object2.second.intValue()) {
                     return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
                 }
@@ -15278,6 +15468,10 @@
                 pw.print(" Lost RAM: "); pw.println(stringifyKBSize(memInfo.getTotalSizeKb()
                         - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                         - memInfo.getKernelUsedSizeKb()));
+            } else {
+                pw.print("lostram,"); pw.println(memInfo.getTotalSizeKb()
+                        - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+                        - memInfo.getKernelUsedSizeKb());
             }
             if (!brief) {
                 if (memInfo.getZramTotalSizeKb() != 0) {
@@ -15832,7 +16026,8 @@
                 mAvailProcessChanges.add(item);
             }
         }
-        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, app.pid, app.info.uid,
+                null).sendToTarget();
 
         // If the caller is restarting this app, then leave it in its
         // current lists and let the caller take care of it.
@@ -17531,6 +17726,9 @@
                 UserHandle.USER_NULL);
     }
 
+    // To cache the list of supported system locales
+    private String[] mSupportedSystemLocales = null;
+
     /**
      * Do either or both things: (1) change the current configuration, and (2)
      * make sure the given activity is running with the (now) current
@@ -17554,11 +17752,14 @@
 
                 EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
 
-                if (!initLocale && values.locale != null && values.userSetLocale) {
-                    final String languageTag = values.locale.toLanguageTag();
-                    SystemProperties.set("persist.sys.locale", languageTag);
+                if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
+                    if (mSupportedSystemLocales == null) {
+                        mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
+                    }
+                    final Locale locale = values.getLocales().getBestMatch(mSupportedSystemLocales);
+                    SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
                     mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
-                            values.locale));
+                            locale));
                 }
 
                 mConfigurationSeq++;
@@ -17859,7 +18060,7 @@
 
         app.systemNoUi = false;
 
-        final int PROCESS_STATE_TOP = mTopProcessState;
+        final int PROCESS_STATE_CUR_TOP = mTopProcessState;
 
         // Determine the importance of the process, starting with most
         // important to least, and assign an appropriate OOM adjustment.
@@ -17874,7 +18075,7 @@
             schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "top-activity";
             foregroundActivities = true;
-            procState = PROCESS_STATE_TOP;
+            procState = PROCESS_STATE_CUR_TOP;
         } else if (app.instrumentationClass != null) {
             // Don't want to kill running instrumentation.
             adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -17928,8 +18129,8 @@
                         adj = ProcessList.VISIBLE_APP_ADJ;
                         app.adjType = "visible";
                     }
-                    if (procState > PROCESS_STATE_TOP) {
-                        procState = PROCESS_STATE_TOP;
+                    if (procState > PROCESS_STATE_CUR_TOP) {
+                        procState = PROCESS_STATE_CUR_TOP;
                     }
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.cached = false;
@@ -17947,8 +18148,8 @@
                         adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                         app.adjType = "pausing";
                     }
-                    if (procState > PROCESS_STATE_TOP) {
-                        procState = PROCESS_STATE_TOP;
+                    if (procState > PROCESS_STATE_CUR_TOP) {
+                        procState = PROCESS_STATE_CUR_TOP;
                     }
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.cached = false;
@@ -17986,7 +18187,8 @@
             }
         }
 
-        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+                || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
             if (app.foregroundServices) {
                 // The user is aware of this app, so make it visible.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
@@ -18377,6 +18579,18 @@
             }
         }
 
+        if (app.lastProviderTime > 0 && (app.lastProviderTime+CONTENT_PROVIDER_RETAIN_TIME) > now) {
+            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+                adj = ProcessList.PREVIOUS_APP_ADJ;
+                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+                app.cached = false;
+                app.adjType = "provider";
+            }
+            if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+                procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+            }
+        }
+
         if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
             // A client of one of our services or providers is in the top state.  We
             // *may* want to be in the top state, but not if we are already running in
@@ -18885,8 +19099,6 @@
                         }
                     }
                 }
-                Process.setSwappiness(app.pid,
-                        app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
             }
         }
         if (app.repForegroundActivities != app.foregroundActivities) {
@@ -19011,7 +19223,7 @@
                 if (mPendingProcessChanges.size() == 0) {
                     if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                             "*** Enqueueing dispatch processes changed!");
-                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
                 }
                 mPendingProcessChanges.add(item);
             }
@@ -19030,29 +19242,46 @@
         return success;
     }
 
-    private final void enqueueUidChangeLocked(UidRecord uidRec, boolean gone) {
-        if (uidRec.pendingChange == null) {
+    private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+        final UidRecord.ChangeItem pendingChange;
+        if (uidRec == null || uidRec.pendingChange == null) {
             if (mPendingUidChanges.size() == 0) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "*** Enqueueing dispatch uid changed!");
-                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_MSG).sendToTarget();
+                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
             }
             final int NA = mAvailUidChanges.size();
             if (NA > 0) {
-                uidRec.pendingChange = mAvailUidChanges.remove(NA-1);
+                pendingChange = mAvailUidChanges.remove(NA-1);
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Retrieving available item: " + uidRec.pendingChange);
+                        "Retrieving available item: " + pendingChange);
             } else {
-                uidRec.pendingChange = new UidRecord.ChangeItem();
+                pendingChange = new UidRecord.ChangeItem();
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Allocating new item: " + uidRec.pendingChange);
+                        "Allocating new item: " + pendingChange);
             }
-            uidRec.pendingChange.uidRecord = uidRec;
-            uidRec.pendingChange.uid = uidRec.uid;
-            mPendingUidChanges.add(uidRec.pendingChange);
+            if (uidRec != null) {
+                uidRec.pendingChange = pendingChange;
+                if (change == UidRecord.CHANGE_GONE && !uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change = UidRecord.CHANGE_GONE_IDLE;
+                }
+            } else if (uid < 0) {
+                throw new IllegalArgumentException("No UidRecord or uid");
+            }
+            pendingChange.uidRecord = uidRec;
+            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+            mPendingUidChanges.add(pendingChange);
+        } else {
+            pendingChange = uidRec.pendingChange;
+            if (change == UidRecord.CHANGE_GONE && pendingChange.change == UidRecord.CHANGE_IDLE) {
+                change = UidRecord.CHANGE_GONE_IDLE;
+            }
         }
-        uidRec.pendingChange.gone = gone;
-        uidRec.pendingChange.processState = uidRec.setProcState;
+        pendingChange.change = change;
+        pendingChange.processState = uidRec != null
+                ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
     }
 
     private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
@@ -19599,12 +19828,31 @@
         // Update from any uid changes.
         for (int i=mActiveUids.size()-1; i>=0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
+            int uidChange = UidRecord.CHANGE_PROCSTATE;
             if (uidRec.setProcState != uidRec.curProcState) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "Changes in " + uidRec + ": proc state from " + uidRec.setProcState
                         + " to " + uidRec.curProcState);
+                if (ActivityManager.isProcStateBackground(uidRec.curProcState)) {
+                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)) {
+                        uidRec.lastBackgroundTime = nowElapsed;
+                        if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                            // Note: the background settle time is in elapsed realtime, while
+                            // the handler time base is uptime.  All this means is that we may
+                            // stop background uids later than we had intended, but that only
+                            // happens because the device was sleeping so we are okay anyway.
+                            mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, BACKGROUND_SETTLE_TIME);
+                        }
+                    }
+                } else {
+                    if (uidRec.idle) {
+                        uidChange = UidRecord.CHANGE_ACTIVE;
+                        uidRec.idle = false;
+                    }
+                    uidRec.lastBackgroundTime = 0;
+                }
                 uidRec.setProcState = uidRec.curProcState;
-                enqueueUidChangeLocked(uidRec, false);
+                enqueueUidChangeLocked(uidRec, -1, uidChange);
             }
         }
 
@@ -19629,6 +19877,53 @@
         }
     }
 
+    final void idleUids() {
+        synchronized (this) {
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long maxBgTime = nowElapsed - BACKGROUND_SETTLE_TIME;
+            long nextTime = 0;
+            for (int i=mActiveUids.size()-1; i>=0; i--) {
+                final UidRecord uidRec = mActiveUids.valueAt(i);
+                final long bgTime = uidRec.lastBackgroundTime;
+                if (bgTime > 0 && !uidRec.idle) {
+                    if (bgTime <= maxBgTime) {
+                        uidRec.idle = true;
+                        doStopUidLocked(uidRec.uid, uidRec);
+                    } else {
+                        if (nextTime == 0 || nextTime > bgTime) {
+                            nextTime = bgTime;
+                        }
+                    }
+                }
+            }
+            if (nextTime > 0) {
+                mHandler.removeMessages(IDLE_UIDS_MSG);
+                mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                        nextTime + BACKGROUND_SETTLE_TIME - nowElapsed);
+            }
+        }
+    }
+
+    final void runInBackgroundDisabled(int uid) {
+        synchronized (this) {
+            UidRecord uidRec = mActiveUids.get(uid);
+            if (uidRec != null) {
+                // This uid is actually running...  should it be considered background now?
+                if (uidRec.idle) {
+                    doStopUidLocked(uidRec.uid, uidRec);
+                }
+            } else {
+                // This uid isn't actually running...  still send a report about it being "stopped".
+                doStopUidLocked(uid, null);
+            }
+        }
+    }
+
+    final void doStopUidLocked(int uid, final UidRecord uidRec) {
+        mServices.stopInBackgroundLocked(uid);
+        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+    }
+
     final void trimApplications() {
         synchronized (this) {
             int i;
@@ -19948,6 +20243,11 @@
     }
 
     @Override
+    public boolean unlockUser(int userId, byte[] token) {
+        return mUserController.unlockUser(userId, token);
+    }
+
+    @Override
     public boolean switchUser(final int userId) {
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         String userName;
@@ -19964,8 +20264,9 @@
             userName = userInfo.name;
             mUserController.setTargetUserIdLocked(userId);
         }
-        mUiHandler.removeMessages(START_USER_SWITCH_MSG);
-        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
+        mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
+                userName));
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d1e7e85..13c1417 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,7 +73,7 @@
         String opt;
         while ((opt = getNextOption()) != null) {
             if (opt.equals("--user")) {
-                userId = parseUserArg(getNextArgRequired());
+                userId = UserHandle.parseUserArg(getNextArgRequired());
             } else {
                 pw.println("Error: Unknown option: " + opt);
                 return -1;
@@ -89,7 +89,7 @@
         String opt;
         while ((opt=getNextOption()) != null) {
             if (opt.equals("--user")) {
-                userId = parseUserArg(getNextArgRequired());
+                userId = UserHandle.parseUserArg(getNextArgRequired());
             } else {
                 pw.println("Error: Unknown option: " + opt);
                 return -1;
@@ -141,22 +141,6 @@
         return 0;
     }
 
-    int parseUserArg(String arg) {
-        int userId;
-        if ("all".equals(arg)) {
-            userId = UserHandle.USER_ALL;
-        } else if ("current".equals(arg) || "cur".equals(arg)) {
-            userId = UserHandle.USER_CURRENT;
-        } else {
-            try {
-                userId = Integer.parseInt(arg);
-            } catch (NumberFormatException e) {
-                throw new IllegalArgumentException("Bad user number: " + arg);
-            }
-        }
-        return userId;
-    }
-
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9278a90..ea8a12f 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -61,6 +61,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -932,7 +933,7 @@
         if (newThumbnail != null) {
             if (DEBUG_THUMBNAILS) Slog.i(TAG_THUMBNAILS,
                     "Setting thumbnail of " + this + " to " + newThumbnail);
-            boolean thumbnailUpdated = task.setLastThumbnail(newThumbnail);
+            boolean thumbnailUpdated = task.setLastThumbnailLocked(newThumbnail);
             if (thumbnailUpdated && isPersistable()) {
                 mStackSupervisor.mService.notifyTaskPersisterLocked(task, false);
             }
@@ -1212,8 +1213,10 @@
         if (_taskDescription.getIconFilename() == null &&
                 (icon = _taskDescription.getIcon()) != null) {
             final String iconFilename = createImageFilename(createTime, task.taskId);
-            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilename);
-            _taskDescription.setIconFilename(iconFilename);
+            final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
+            final String iconFilePath = iconFile.getAbsolutePath();
+            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath);
+            _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
     }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 6b8bdf8..e28d198 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -31,6 +31,7 @@
 import static com.android.server.am.ActivityStackSupervisor.MOVING;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArraySet;
 
@@ -388,6 +389,10 @@
         mWindowManager.detachStack(mStackId);
     }
 
+    public void getDisplaySize(Point out) {
+        mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
+    }
+
     void setBounds(Rect bounds) {
         mBounds = mFullscreen ? null : new Rect(bounds);
         if (mTaskPositioner != null) {
@@ -1333,7 +1338,12 @@
             return topHomeActivity == null || !topHomeActivity.isHomeActivity();
         }
 
-        final int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+        // Find the first stack below focused stack that actually got something visible.
+        int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1;
+        while (belowFocusedIndex >= 0 &&
+                mStacks.get(belowFocusedIndex).topRunningActivityLocked() == null) {
+            belowFocusedIndex--;
+        }
         if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
                 && stackIndex == belowFocusedIndex) {
             // Stacks directly behind the docked or pinned stack are always visible.
@@ -1408,19 +1418,9 @@
         if (top == null) {
             return;
         }
-        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                "ensureActivitiesVisible behind " + top
+        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top
                 + " configChanges=0x" + Integer.toHexString(configChanges));
-
-        if (mTranslucentActivityWaiting != top) {
-            mUndrawnActivitiesBelowTopTranslucent.clear();
-            if (mTranslucentActivityWaiting != null) {
-                // Call the callback with a timeout indication.
-                notifyActivityDrawnLocked(null);
-                mTranslucentActivityWaiting = null;
-            }
-            mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
-        }
+        checkTranslucentActivityWaiting(top);
 
         // If the top activity is not fullscreen, then we need to
         // make sure any activities under it are now visible.
@@ -1448,7 +1448,6 @@
                     if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
                             "Make visible? " + r + " finishing=" + r.finishing
                             + " state=" + r.state);
-
                     // First: if this is not the current activity being started, make
                     // sure it matches the current configuration.
                     if (r != starting) {
@@ -1456,143 +1455,28 @@
                     }
 
                     if (r.app == null || r.app.thread == null) {
-                        // We need to make sure the app is running if it's the top, or it is
-                        // just made visible from invisible.
-                        // If the app is already visible, it must have died while it was visible.
-                        // In this case, we'll show the dead window but will not restart the app.
-                        // Otherwise we could end up thrashing.
-                        if (r == top || !r.visible) {
-                            // This activity needs to be visible, but isn't even running...
-                            // get it started and resume if no other stack in this stack is resumed.
-                            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                    "Start and freeze screen for " + r);
-                            if (r != starting) {
-                                r.startFreezingScreenLocked(r.app, configChanges);
-                            }
-                            if (!r.visible || r.mLaunchTaskBehind) {
-                                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                        "Starting and making visible: " + r);
-                                setVisible(r, true);
-                            }
-                            if (r != starting) {
-                                mStackSupervisor.startSpecificActivityLocked(
-                                        r, noStackActivityResumed, false);
-                                if (activityNdx >= activities.size()) {
-                                    // Record may be removed if its process needs to restart.
-                                    activityNdx = activities.size() - 1;
-                                } else {
-                                    noStackActivityResumed = false;
-                                }
+                        if (makeVisibleAndRestartIfNeeded(starting, configChanges, top,
+                                noStackActivityResumed, r)) {
+                            if (activityNdx >= activities.size()) {
+                                // Record may be removed if its process needs to restart.
+                                activityNdx = activities.size() - 1;
+                            } else {
+                                noStackActivityResumed = false;
                             }
                         }
                     } else if (r.visible) {
-                        // If this activity is already visible, then there is nothing
-                        // else to do here.
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                "Skipping: already visible at " + r);
-                        r.stopFreezingScreenLocked(false);
-                        try {
-                            if (r.returningOptions != null) {
-                                r.app.thread.scheduleOnNewActivityOptions(r.appToken,
-                                        r.returningOptions);
-                            }
-                        } catch(RemoteException e) {
-                        }
-                        if (r.state == ActivityState.RESUMED) {
+                        if (alreadyVisible(r)) {
                             noStackActivityResumed = false;
                         }
                     } else {
-                        // This activity is not currently visible, but is running.
-                        // Tell it to become visible.
-                        r.visible = true;
-                        if (r.state != ActivityState.RESUMED && r != starting) {
-                            // If this activity is paused, tell it
-                            // to now show its window.
-                            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                    "Making visible and scheduling visibility: " + r);
-                            try {
-                                if (mTranslucentActivityWaiting != null) {
-                                    r.updateOptionsLocked(r.returningOptions);
-                                    mUndrawnActivitiesBelowTopTranslucent.add(r);
-                                }
-                                setVisible(r, true);
-                                r.sleeping = false;
-                                r.app.pendingUiClean = true;
-                                r.app.thread.scheduleWindowVisibility(r.appToken, true);
-                                r.stopFreezingScreenLocked(false);
-                            } catch (Exception e) {
-                                // Just skip on any failure; we'll make it
-                                // visible when it next restarts.
-                                Slog.w(TAG, "Exception thrown making visibile: "
-                                        + r.intent.getComponent(), e);
-                            }
-                        }
+                        becomeVisible(starting, r);
                     }
-
                     // Aggregate current change flags.
                     configChanges |= r.configChangeFlags;
-
-                    if (r.fullscreen) {
-                        // At this point, nothing else needs to be shown in this task.
-                        behindFullscreenActivity = true;
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
-                                + " stackInvisible=" + stackInvisible
-                                + " behindFullscreenActivity=" + behindFullscreenActivity);
-                    } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
-                        behindFullscreenActivity = true;
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
-                                + " stackInvisible=" + stackInvisible
-                                + " behindFullscreenActivity=" + behindFullscreenActivity);
-                    }
+                    behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+                            behindFullscreenActivity, task, r);
                 } else {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                            "Make invisible? " + r + " finishing=" + r.finishing
-                            + " state=" + r.state + " stackInvisible=" + stackInvisible
-                            + " behindFullscreenActivity=" + behindFullscreenActivity);
-                    // Now for any activities that aren't visible to the user, make
-                    // sure they no longer are keeping the screen frozen.
-                    if (r.visible) {
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
-                        try {
-                            setVisible(r, false);
-                            switch (r.state) {
-                                case STOPPING:
-                                case STOPPED:
-                                    if (r.app != null && r.app.thread != null) {
-                                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                                "Scheduling invisibility: " + r);
-                                        r.app.thread.scheduleWindowVisibility(r.appToken, false);
-                                    }
-                                    break;
-
-                                case INITIALIZING:
-                                case RESUMED:
-                                case PAUSING:
-                                case PAUSED:
-                                    // This case created for transitioning activities from
-                                    // translucent to opaque {@link Activity#convertToOpaque}.
-                                    if (getVisibleBehindActivity() == r) {
-                                        releaseBackgroundResources(r);
-                                    } else {
-                                        if (!mStackSupervisor.mStoppingActivities.contains(r)) {
-                                            mStackSupervisor.mStoppingActivities.add(r);
-                                        }
-                                        mStackSupervisor.scheduleIdleLocked();
-                                    }
-                                    break;
-
-                                default:
-                                    break;
-                            }
-                        } catch (Exception e) {
-                            // Just skip on any failure; we'll make it
-                            // visible when it next restarts.
-                            Slog.w(TAG, "Exception thrown making hidden: "
-                                    + r.intent.getComponent(), e);
-                        }
-                    } else {
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
-                    }
+                    becomeInvisible(stackInvisible, behindFullscreenActivity, r);
                 }
             }
             if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1610,6 +1494,147 @@
         }
     }
 
+    private void checkTranslucentActivityWaiting(ActivityRecord top) {
+        if (mTranslucentActivityWaiting != top) {
+            mUndrawnActivitiesBelowTopTranslucent.clear();
+            if (mTranslucentActivityWaiting != null) {
+                // Call the callback with a timeout indication.
+                notifyActivityDrawnLocked(null);
+                mTranslucentActivityWaiting = null;
+            }
+            mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+        }
+    }
+
+    private boolean makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+            ActivityRecord top, boolean noStackActivityResumed, ActivityRecord r) {
+        // We need to make sure the app is running if it's the top, or it is just made visible from
+        // invisible. If the app is already visible, it must have died while it was visible. In this
+        // case, we'll show the dead window but will not restart the app. Otherwise we could end up
+        // thrashing.
+        if (r == top || !r.visible) {
+            // This activity needs to be visible, but isn't even running...
+            // get it started and resume if no other stack in this stack is resumed.
+            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
+            if (r != starting) {
+                r.startFreezingScreenLocked(r.app, configChanges);
+            }
+            if (!r.visible || r.mLaunchTaskBehind) {
+                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
+                setVisible(r, true);
+            }
+            if (r != starting) {
+                mStackSupervisor.startSpecificActivityLocked(r, noStackActivityResumed, false);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void becomeInvisible(boolean stackInvisible, boolean behindFullscreenActivity,
+            ActivityRecord r) {
+        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing="
+                + r.finishing + " state=" + r.state + " stackInvisible=" + stackInvisible
+                + " behindFullscreenActivity=" + behindFullscreenActivity);
+        if (!r.visible) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
+            return;
+        }
+        // Now for any activities that aren't visible to the user, make sure they no longer are
+        // keeping the screen frozen.
+        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
+        try {
+            setVisible(r, false);
+            switch (r.state) {
+                case STOPPING:
+                case STOPPED:
+                    if (r.app != null && r.app.thread != null) {
+                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+                                "Scheduling invisibility: " + r);
+                        r.app.thread.scheduleWindowVisibility(r.appToken, false);
+                    }
+                    break;
+
+                case INITIALIZING:
+                case RESUMED:
+                case PAUSING:
+                case PAUSED:
+                    // This case created for transitioning activities from
+                    // translucent to opaque {@link Activity#convertToOpaque}.
+                    if (getVisibleBehindActivity() == r) {
+                        releaseBackgroundResources(r);
+                    } else {
+                        if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+                            mStackSupervisor.mStoppingActivities.add(r);
+                        }
+                        mStackSupervisor.scheduleIdleLocked();
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        } catch (Exception e) {
+            // Just skip on any failure; we'll make it visible when it next restarts.
+            Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e);
+        }
+    }
+
+    private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
+            TaskRecord task, ActivityRecord r) {
+        if (r.fullscreen) {
+            // At this point, nothing else needs to be shown in this task.
+            behindFullscreenActivity = true;
+            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+                    + " stackInvisible=" + stackInvisible
+                    + " behindFullscreenActivity=" + behindFullscreenActivity);
+        } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+            behindFullscreenActivity = true;
+            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
+                    + " stackInvisible=" + stackInvisible
+                    + " behindFullscreenActivity=" + behindFullscreenActivity);
+        }
+        return behindFullscreenActivity;
+    }
+
+    private void becomeVisible(ActivityRecord starting, ActivityRecord r) {
+        // This activity is not currently visible, but is running. Tell it to become visible.
+        r.visible = true;
+        if (r.state != ActivityState.RESUMED && r != starting) {
+            // If this activity is paused, tell it to now show its window.
+            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+                    "Making visible and scheduling visibility: " + r);
+            try {
+                if (mTranslucentActivityWaiting != null) {
+                    r.updateOptionsLocked(r.returningOptions);
+                    mUndrawnActivitiesBelowTopTranslucent.add(r);
+                }
+                setVisible(r, true);
+                r.sleeping = false;
+                r.app.pendingUiClean = true;
+                r.app.thread.scheduleWindowVisibility(r.appToken, true);
+                r.stopFreezingScreenLocked(false);
+            } catch (Exception e) {
+                // Just skip on any failure; we'll make it
+                // visible when it next restarts.
+                Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e);
+            }
+        }
+    }
+
+    private boolean alreadyVisible(ActivityRecord r) {
+        // If this activity is already visible, then there is nothing else to do here.
+        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
+        r.stopFreezingScreenLocked(false);
+        try {
+            if (r.returningOptions != null) {
+                r.app.thread.scheduleOnNewActivityOptions(r.appToken, r.returningOptions);
+            }
+        } catch(RemoteException e) {
+        }
+        return r.state == ActivityState.RESUMED;
+    }
+
     void convertActivityToTranslucent(ActivityRecord r) {
         mTranslucentActivityWaiting = r;
         mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -3098,6 +3123,9 @@
             // If the activity is PAUSING, we will complete the finish once
             // it is done pausing; else we can just directly finish it here.
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
+            if (r.visible) {
+                mWindowManager.setAppVisibility(r.appToken, false);
+            }
             return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null;
         } else {
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
@@ -4539,6 +4567,11 @@
         if (!moving) {
             mStackSupervisor.removeLockedTaskLocked(task);
             mWindowManager.removeTask(task.taskId);
+            if (!StackId.persistTaskBounds(mStackId)) {
+                // Reset current bounds for task whose bounds shouldn't be persisted so it uses
+                // default configuration the next time it launches.
+                task.updateOverrideConfiguration(null);
+            }
         }
 
         final ActivityRecord r = mResumedActivity;
@@ -4601,14 +4634,20 @@
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, false);
-        if (mTaskPositioner != null) {
-            mTaskPositioner.updateDefaultBounds(task, mTaskHistory, info.layout);
-        } else if (mBounds != null && task.mResizeable) {
+        if (!layoutTaskInStack(task, info.layout) && mBounds != null && task.mResizeable) {
             task.updateOverrideConfiguration(mBounds);
         }
         return task;
     }
 
+    boolean layoutTaskInStack(TaskRecord task, ActivityInfo.Layout layout) {
+        if (mTaskPositioner == null) {
+            return false;
+        }
+        mTaskPositioner.updateDefaultBounds(task, mTaskHistory, layout);
+        return true;
+    }
+
     ArrayList<TaskRecord> getAllTasks() {
         return new ArrayList<>(mTaskHistory);
     }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index fb2c7b1..124d2ef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -99,7 +99,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.TransactionTooLargeException;
 import android.os.UserHandle;
@@ -1294,7 +1293,7 @@
                 // Home process is the root process of the task.
                 mService.mHomeProcess = task.mActivities.get(0).app;
             }
-            mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
+            mService.notifyPackageUse(r.intent.getComponent().getPackageName());
             r.sleeping = false;
             r.forceNewConfig = false;
             mService.showAskCompatModeDialogLocked(r);
@@ -1666,9 +1665,7 @@
 
         UserInfo user = getUserInfo(userId);
         // TODO: Timeout for work challenge
-        if (user.isManagedProfile()
-                && mService.mContext.getSystemService(StorageManager.class)
-                    .isPerUserEncryptionEnabled()) {
+        if (user.isManagedProfile() && StorageManager.isFileBasedEncryptionEnabled()) {
             KeyguardManager km = (KeyguardManager) mService.mContext
                     .getSystemService(Context.KEYGUARD_SERVICE);
 
@@ -1926,9 +1923,9 @@
         boolean overrideBounds = false;
         Rect newBounds = null;
         if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) {
-            if (options.hasBounds()) {
+            if (canUseActivityOptionsLaunchBounds(options)) {
                 overrideBounds = true;
-                newBounds = options.getBounds();
+                newBounds = options.getLaunchBounds();
             }
         }
 
@@ -2930,8 +2927,8 @@
         }
 
         if (task.mResizeable && options != null) {
-            if (options.hasBounds()) {
-                Rect bounds = options.getBounds();
+            if (canUseActivityOptionsLaunchBounds(options)) {
+                Rect bounds = options.getLaunchBounds();
                 task.updateOverrideConfiguration(bounds);
                 final int stackId = task.getLaunchStackId();
                 if (stackId != task.stack.mStackId) {
@@ -2955,6 +2952,12 @@
                 "findTaskToMoveToFront: moved to front of stack=" + task.stack);
     }
 
+    private boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
+        // We use the launch bounds in the activity options is the device supports freeform
+        // window management.
+        return options.hasLaunchBounds() && mService.mSupportsFreeformWindowManagement;
+    }
+
     ActivityStack getStack(int stackId) {
         return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
     }
@@ -3272,7 +3275,7 @@
             task.stack.removeTask(task, "restoreRecentTaskLocked", MOVING);
         }
 
-        ActivityStack stack =
+        final ActivityStack stack =
                 getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
 
         if (stack == null) {
@@ -3339,31 +3342,35 @@
             Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
             return;
         }
-        if (StackId.preserveWindowOnTaskMove(stackId)) {
+
+        if (task.stack != null && task.stack.mStackId == stackId) {
+            // You are already in the right stack silly...
+            Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
+            return;
+        }
+
+        final ActivityRecord topActivity = task.getTopActivity();
+        if (StackId.preserveWindowOnTaskMove(stackId) && topActivity != null) {
             // 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
             // entrance of the new window to be properly animated.
-            ActivityRecord r = task.getTopActivity();
-            mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
+            mWindowManager.setReplacingWindow(topActivity.appToken);
         }
-        final ActivityStack stack =
-                moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
-                        "moveTaskToStack:" + reason);
+        final ActivityStack stack = moveTaskToStackUncheckedLocked(
+                task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
-            resizeTaskLocked(task, stack.mBounds,
-                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == FREEFORM_WORKSPACE_STACK_ID
                 && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
             resizeTaskLocked(task, task.mLastNonFullscreenBounds,
                     RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-            resizeTaskLocked(task, stack.mBounds,
-                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
+            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
@@ -3419,9 +3426,12 @@
             Slog.w(TAG, "positionTaskInStackLocked: no task for id=" + taskId);
             return;
         }
-        ActivityStack stack =
-                getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
-        mWindowManager.positionTaskInStack(taskId, stackId, position);
+        final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
+
+        task.updateOverrideConfigurationForStack(stack);
+
+        mWindowManager.positionTaskInStack(
+                taskId, stackId, position, task.mBounds, task.mOverrideConfig);
         final boolean stackChanged = task.stack != null && task.stack != stack;
         if (stackChanged) {
             task.stack.removeTask(task, "moveTaskToStack", MOVING);
@@ -3680,7 +3690,7 @@
     void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
         r.mLaunchTaskBehind = false;
         final TaskRecord task = r.task;
-        task.setLastThumbnail(task.stack.screenshotActivities(r));
+        task.setLastThumbnailLocked(task.stack.screenshotActivities(r));
         mRecentTasks.addLocked(task);
         mService.notifyTaskStackChangedLocked();
         mWindowManager.setAppVisibility(r.appToken, false);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6de8579..fb37eda 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -269,7 +269,7 @@
             if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
                     "Delivering to component " + r.curComponent
                     + ": " + r);
-            mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
+            mService.notifyPackageUse(r.intent.getComponent().getPackageName());
             app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                     mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
                     r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
@@ -556,6 +556,17 @@
                     + " (uid " + r.callingUid + ")");
             skip = true;
         }
+        if (!skip) {
+            if (!mService.checkAllowBackgroundLocked(filter.receiverList.uid, filter.packageName,
+                    -1)) {
+                Slog.w(TAG, "Background execution not allowed: receiving "
+                        + r.intent
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")");
+                skip = true;
+            }
+        }
 
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -938,6 +949,15 @@
                 skip = true;
             }
             if (!skip) {
+                if (!mService.checkAllowBackgroundLocked(info.activityInfo.applicationInfo.uid,
+                        info.activityInfo.packageName, -1)) {
+                    Slog.w(TAG, "Background execution not allowed: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString());
+                    skip = true;
+                }
+            }
+            if (!skip) {
                 skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                         r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b77eec8..4bfe300 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -136,6 +136,7 @@
     long curCpuTime;            // How long proc has run CPU most recently
     long lastRequestedGc;       // When we last asked the app to do a gc
     long lastLowMemory;         // When we last told the app that memory is low
+    long lastProviderTime;      // The last time someone else was using a provider in this process.
     boolean reportLowMemory;    // Set to true when waiting to report low mem
     boolean empty;              // Is this an empty background process?
     boolean cached;             // Is this a cached process?
@@ -317,6 +318,11 @@
                     pw.print(" foregroundActivities="); pw.print(foregroundActivities);
                     pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
         }
+        if (lastProviderTime > 0) {
+            pw.print(prefix); pw.print("lastProviderTime=");
+            TimeUtils.formatDuration(lastProviderTime, now, pw);
+            pw.println();
+        }
         if (hasStartedServices) {
             pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
         }
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 862a973..c63eaac 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -462,10 +462,12 @@
                     final boolean sameActivity = task.realActivity != null
                             && tr.realActivity != null
                             && task.realActivity.equals(tr.realActivity);
-                    if (!sameActivity) {
+                    // If the document is open in another app or is not the same
+                    // document, we don't need to trim it.
+                    if (!sameActivity || !sameIntent) {
                         continue;
-                    }
-                    if (maxRecents > 0 && !doTrim) {
+                    // Otherwise only trim if we are over our max recents for this task
+                    } else if (maxRecents > 0 && !doTrim) {
                         --maxRecents;
                         continue;
                     }
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 871331b..9a00075 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,26 +16,18 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.pm.IPackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
-import android.os.RemoteException;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.Xml;
 import android.os.Process;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
@@ -51,14 +43,11 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
 import libcore.io.IoUtils;
 
-import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-
 public class TaskPersister {
     static final String TAG = "TaskPersister";
     static final boolean DEBUG = false;
@@ -66,8 +55,10 @@
     /** When not flushing don't write out files faster than this */
     private static final long INTER_WRITE_DELAY_MS = 500;
 
-    /** When not flushing delay this long before writing the first file out. This gives the next
-     * task being launched a chance to load its resources without this occupying IO bandwidth. */
+    /**
+     * When not flushing delay this long before writing the first file out. This gives the next task
+     * being launched a chance to load its resources without this occupying IO bandwidth.
+     */
     private static final long PRE_TASK_DELAY_MS = 3000;
 
     /** The maximum number of entries to keep in the queue before draining it automatically. */
@@ -84,35 +75,37 @@
 
     private static final String TAG_TASK = "task";
 
-    static File sImagesDir;
-    static File sTasksDir;
-
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final RecentTasks mRecentTasks;
 
-    /** Value determines write delay mode as follows:
-     *    < 0 We are Flushing. No delays between writes until the image queue is drained and all
-     * tasks needing persisting are written to disk. There is no delay between writes.
-     *    == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
-     *    > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
-     * delayed by #INTER_WRITE_DELAY_MS. */
+    /**
+     * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
+     * until the image queue is drained and all tasks needing persisting are written to disk. There
+     * is no delay between writes. == 0 We are Idle. Next writes will be delayed by
+     * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent
+     * writes will be delayed by #INTER_WRITE_DELAY_MS.
+     */
     private long mNextWriteTime = 0;
 
     private final LazyTaskWriterThread mLazyTaskWriterThread;
 
     private static class WriteQueueItem {}
+
     private static class TaskWriteQueueItem extends WriteQueueItem {
         final TaskRecord mTask;
+
         TaskWriteQueueItem(TaskRecord task) {
             mTask = task;
         }
     }
+
     private static class ImageWriteQueueItem extends WriteQueueItem {
-        final String mFilename;
+        final String mFilePath;
         Bitmap mImage;
-        ImageWriteQueueItem(String filename, Bitmap image) {
-            mFilename = filename;
+
+        ImageWriteQueueItem(String filePath, Bitmap image) {
+            mFilePath = filePath;
             mImage = image;
         }
     }
@@ -121,19 +114,18 @@
 
     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
             RecentTasks recentTasks) {
-        sTasksDir = new File(systemDir, TASKS_DIRNAME);
-        if (!sTasksDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
-            if (!sTasksDir.mkdir()) {
-                Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
+
+        final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
+        if (legacyImagesDir.exists()) {
+            if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
+                Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
             }
         }
 
-        sImagesDir = new File(systemDir, IMAGES_DIRNAME);
-        if (!sImagesDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
-            if (!sImagesDir.mkdir()) {
-                Slog.e(TAG, "Failure creating images directory " + sImagesDir);
+        final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
+        if (legacyTasksDir.exists()) {
+            if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
+                Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
             }
         }
 
@@ -154,8 +146,8 @@
         for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
             final WriteQueueItem item = mWriteQueue.get(queueNdx);
             if (item instanceof ImageWriteQueueItem &&
-                    ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
-                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
+                    ((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) {
+                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
                         " from write queue");
                 mWriteQueue.remove(queueNdx);
             }
@@ -223,14 +215,14 @@
         }
     }
 
-    void saveImage(Bitmap image, String filename) {
+    void saveImage(Bitmap image, String filePath) {
         synchronized (this) {
             int queueNdx;
             for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    if (imageWriteQueueItem.mFilename.equals(filename)) {
+                    if (imageWriteQueueItem.mFilePath.equals(filePath)) {
                         // replace the Bitmap with the new one.
                         imageWriteQueueItem.mImage = image;
                         break;
@@ -238,14 +230,14 @@
                 }
             }
             if (queueNdx < 0) {
-                mWriteQueue.add(new ImageWriteQueueItem(filename, image));
+                mWriteQueue.add(new ImageWriteQueueItem(filePath, image));
             }
             if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
                 mNextWriteTime = FLUSH_QUEUE;
             } else if (mNextWriteTime == 0) {
                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
             }
-            if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+            if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" +
                     SystemClock.uptimeMillis() + " mNextWriteTime=" +
                     mNextWriteTime + " Callers=" + Debug.getCallers(4));
             notifyAll();
@@ -254,22 +246,22 @@
         yieldIfQueueTooDeep();
     }
 
-    Bitmap getTaskDescriptionIcon(String filename) {
+    Bitmap getTaskDescriptionIcon(String filePath) {
         // See if it is in the write queue
-        final Bitmap icon = getImageFromWriteQueue(filename);
+        final Bitmap icon = getImageFromWriteQueue(filePath);
         if (icon != null) {
             return icon;
         }
-        return restoreImage(filename);
+        return restoreImage(filePath);
     }
 
-    Bitmap getImageFromWriteQueue(String filename) {
+    Bitmap getImageFromWriteQueue(String filePath) {
         synchronized (this) {
             for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    if (imageWriteQueueItem.mFilename.equals(filename)) {
+                    if (imageWriteQueueItem.mFilePath.equals(filePath)) {
                         return imageWriteQueueItem.mImage;
                     }
                 }
@@ -285,7 +277,7 @@
         xmlSerializer.setOutput(stringWriter);
 
         if (DEBUG) xmlSerializer.setFeature(
-                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                "http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
         // save task
         xmlSerializer.startDocument(null, true);
@@ -331,19 +323,22 @@
         return null;
     }
 
-    ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) {
-        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+    private List<TaskRecord> restoreTasksForUserLocked(final int userId) {
+        final List<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
 
-        File[] recentFiles = sTasksDir.listFiles();
+        File userTasksDir = getUserTasksDir(userId);
+
+        File[] recentFiles = userTasksDir.listFiles();
         if (recentFiles == null) {
-            Slog.e(TAG, "Unable to list files from " + sTasksDir);
+            Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
             return tasks;
         }
 
         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
             File taskFile = recentFiles[taskNdx];
-            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+            if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId
+                    + ", taskFile=" + taskFile.getName());
             BufferedReader reader = null;
             boolean deleteFile = false;
             try {
@@ -358,30 +353,29 @@
                     if (event == XmlPullParser.START_TAG) {
                         if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
                         if (TAG_TASK.equals(name)) {
-                            final TaskRecord task =
-                                    TaskRecord.restoreFromXml(in, mStackSupervisor);
-                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
-                                    task);
+                            final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
+                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task="
+                                    + task);
                             if (task != null) {
                                 // XXX Don't add to write queue... there is no reason to write
                                 // out the stuff we just read, if we don't write it we will
                                 // read the same thing again.
-                                //mWriteQueue.add(new TaskWriteQueueItem(task));
+                                // mWriteQueue.add(new TaskWriteQueueItem(task));
                                 final int taskId = task.taskId;
                                 mStackSupervisor.setNextTaskId(taskId);
                                 // Check if it's a valid user id. Don't add tasks for removed users.
-                                if (ArrayUtils.contains(validUserIds, task.userId)) {
+                                if (userId == task.userId) {
                                     task.isPersistable = true;
                                     tasks.add(task);
                                     recoveredTaskIds.add(taskId);
                                 }
                             } else {
-                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
-                                        fileToString(taskFile));
+                                Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
+                                        + taskFile + ": " + fileToString(taskFile));
                             }
                         } else {
-                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
-                                    " name=" + name);
+                            Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
+                                    + " name=" + name);
                         }
                     }
                     XmlUtils.skipCurrentTag(in);
@@ -400,10 +394,19 @@
         }
 
         if (!DEBUG) {
-            removeObsoleteFiles(recoveredTaskIds);
+            removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
+        }
+        return tasks;
+    }
+
+    ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
+        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+
+        for (int userId : validUserIds) {
+            tasks.addAll(restoreTasksForUserLocked(userId));
         }
 
-        // Fixup task affiliation from taskIds
+        // Fix up task affiliation from taskIds
         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = tasks.get(taskNdx);
             task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
@@ -430,7 +433,7 @@
     }
 
     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
-        if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
+        if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
                 " files=" + files);
         if (files == null) {
             Slog.e(TAG, "File error accessing recents directory (too many files open?).");
@@ -444,14 +447,14 @@
                 final int taskId;
                 try {
                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+                    if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
                 } catch (Exception e) {
-                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
+                    Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
                     file.delete();
                     continue;
                 }
                 if (!persistentTaskIds.contains(taskId)) {
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
+                    if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
                     file.delete();
                 }
             }
@@ -459,13 +462,39 @@
     }
 
     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
-        removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
-        removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
+        for (int userId : mService.getRunningUserIds()) {
+            removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
+            removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
+        }
     }
 
     static Bitmap restoreImage(String filename) {
         if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
-        return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
+        return BitmapFactory.decodeFile(filename);
+    }
+
+    static File getUserTasksDir(int userId) {
+        File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
+
+        if (!userTasksDir.exists()) {
+            if (!userTasksDir.mkdir()) {
+                Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": "
+                        + userTasksDir);
+            }
+        }
+        return userTasksDir;
+    }
+
+    static File getUserImagesDir(int userId) {
+        File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
+
+        if (!userImagesDir.exists()) {
+            if (!userImagesDir.mkdir()) {
+                Slog.e(TAG, "Failure creating images directory for user " + userId + ": "
+                        + userImagesDir);
+            }
+        }
+        return userImagesDir;
     }
 
     private class LazyTaskWriterThread extends Thread {
@@ -518,7 +547,6 @@
                                 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
                     }
 
-
                     while (mWriteQueue.isEmpty()) {
                         if (mNextWriteTime != 0) {
                             mNextWriteTime = 0; // idle.
@@ -552,15 +580,15 @@
 
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    final String filename = imageWriteQueueItem.mFilename;
+                    final String filePath = imageWriteQueueItem.mFilePath;
                     final Bitmap bitmap = imageWriteQueueItem.mImage;
-                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
                     FileOutputStream imageFile = null;
                     try {
-                        imageFile = new FileOutputStream(new File(sImagesDir, filename));
+                        imageFile = new FileOutputStream(new File(filePath));
                         bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
                     } catch (Exception e) {
-                        Slog.e(TAG, "saveImage: unable to save " + filename, e);
+                        Slog.e(TAG, "saveImage: unable to save " + filePath, e);
                     } finally {
                         IoUtils.closeQuietly(imageFile);
                     }
@@ -585,18 +613,21 @@
                         FileOutputStream file = null;
                         AtomicFile atomicFile = null;
                         try {
-                            atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
-                                    task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
+                            atomicFile = new AtomicFile(new File(
+                                    getUserTasksDir(task.userId),
+                                    String.valueOf(task.taskId) + RECENTS_FILENAME
+                                    + TASK_EXTENSION));
                             file = atomicFile.startWrite();
                             file.write(stringWriter.toString().getBytes());
                             file.write('\n');
                             atomicFile.finishWrite(file);
+
                         } catch (IOException e) {
                             if (file != null) {
                                 atomicFile.failWrite(file);
                             }
-                            Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
-                                    e);
+                            Slog.e(TAG,
+                                    "Unable to open " + atomicFile + " for persisting. " + e);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 031f6d2..02cf87a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_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;
@@ -26,16 +27,26 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+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.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
-import android.app.ActivityManager.TaskThumbnail;
 import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManager.TaskThumbnail;
+import android.app.ActivityManager.TaskThumbnailInfo;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -46,6 +57,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.ParcelFileDescriptor;
@@ -54,8 +66,10 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.util.XmlUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -193,6 +207,7 @@
     private Bitmap mLastThumbnail; // Last thumbnail captured for this item.
     private final File mLastThumbnailFile; // File containing last thumbnail.
     private final String mFilename;
+    private TaskThumbnailInfo mLastThumbnailInfo;
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
@@ -232,7 +247,9 @@
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        userId = UserHandle.getUserId(info.applicationInfo.uid);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
+        mLastThumbnailInfo = new TaskThumbnailInfo();
         taskId = _taskId;
         mAffiliatedTaskId = _taskId;
         voiceSession = _voiceSession;
@@ -246,11 +263,13 @@
     }
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
-            TaskDescription _taskDescription) {
+            TaskDescription _taskDescription, TaskThumbnailInfo thumbnailInfo) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        userId = UserHandle.getUserId(info.applicationInfo.uid);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
+        mLastThumbnailInfo = thumbnailInfo;
         taskId = _taskId;
         mAffiliatedTaskId = _taskId;
         voiceSession = null;
@@ -269,7 +288,6 @@
 
         taskType = APPLICATION_ACTIVITY_TYPE;
         mTaskToReturnTo = HOME_ACTIVITY_TYPE;
-        userId = UserHandle.getUserId(info.applicationInfo.uid);
         lastTaskDescription = _taskDescription;
         mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
     }
@@ -281,12 +299,14 @@
             int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities,
             long _firstActiveTime, long _lastActiveTime, long lastTimeMoved,
             boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
-            int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
-            int callingUid, String callingPackage, boolean resizeable, boolean privileged) {
+            TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
+            int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
+            boolean resizeable, boolean privileged) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(_userId), mFilename);
+        mLastThumbnailInfo = lastThumbnailInfo;
         taskId = _taskId;
         intent = _intent;
         affinityIntent = _affinityIntent;
@@ -317,7 +337,7 @@
         mNextAffiliateTaskId = nextTaskId;
         mCallingUid = callingUid;
         mCallingPackage = callingPackage;
-        mResizeable = resizeable || mService.mForceResizableActivites;
+        mResizeable = resizeable || mService.mForceResizableActivities;
         mPrivileged = privileged;
         ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null;
         mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -419,7 +439,7 @@
         } else {
             autoRemoveRecents = false;
         }
-        mResizeable = info.resizeable || mService.mForceResizableActivites;
+        mResizeable = info.resizeable || mService.mForceResizableActivities;
         mLockTaskMode = info.lockTaskLaunchMode;
         mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
         setLockTaskAuth();
@@ -489,18 +509,46 @@
     }
 
     /**
-     * Sets the last thumbnail.
+     * Sets the last thumbnail with the current task bounds and the system orientation.
      * @return whether the thumbnail was set
      */
-    boolean setLastThumbnail(Bitmap thumbnail) {
+    boolean setLastThumbnailLocked(Bitmap thumbnail) {
+        final Configuration serviceConfig = mService.mConfiguration;
+        int taskWidth = 0;
+        int taskHeight = 0;
+        if (mBounds != null) {
+            // Non-fullscreen tasks
+            taskWidth = mBounds.width();
+            taskHeight = mBounds.height();
+        } else if (stack != null) {
+            // Fullscreen tasks
+            final Point displaySize = new Point();
+            stack.getDisplaySize(displaySize);
+            taskWidth = displaySize.x;
+            taskHeight = displaySize.y;
+        } else {
+            Slog.e(TAG, "setLastThumbnailLocked() called on Task without stack");
+        }
+        return setLastThumbnailLocked(thumbnail, taskWidth, taskHeight, serviceConfig.orientation);
+    }
+
+    /**
+     * Sets the last thumbnail with the current task bounds.
+     * @return whether the thumbnail was set
+     */
+    private boolean setLastThumbnailLocked(Bitmap thumbnail, int taskWidth, int taskHeight,
+            int screenOrientation) {
         if (mLastThumbnail != thumbnail) {
             mLastThumbnail = thumbnail;
+            mLastThumbnailInfo.taskWidth = taskWidth;
+            mLastThumbnailInfo.taskHeight = taskHeight;
+            mLastThumbnailInfo.screenOrientation = screenOrientation;
             if (thumbnail == null) {
                 if (mLastThumbnailFile != null) {
                     mLastThumbnailFile.delete();
                 }
             } else {
-                mService.mTaskPersister.saveImage(thumbnail, mFilename);
+                mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
             }
             return true;
         }
@@ -509,9 +557,11 @@
 
     void getLastThumbnail(TaskThumbnail thumbs) {
         thumbs.mainThumbnail = mLastThumbnail;
+        thumbs.thumbnailInfo = mLastThumbnailInfo;
         thumbs.thumbnailFileDescriptor = null;
         if (mLastThumbnail == null) {
-            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename);
+            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(
+                    mLastThumbnailFile.getAbsolutePath());
         }
         // Only load the thumbnail file if we don't have a thumbnail
         if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) {
@@ -523,11 +573,18 @@
         }
     }
 
+    /**
+     * Removes in-memory thumbnail data when the max number of in-memory task thumbnails is reached.
+     */
     void freeLastThumbnail() {
         mLastThumbnail = null;
     }
 
+    /**
+     * Removes all associated thumbnail data when a task is removed or pruned from recents.
+     */
     void disposeThumbnail() {
+        mLastThumbnailInfo.reset();
         mLastThumbnail = null;
         lastDescription = null;
     }
@@ -637,7 +694,7 @@
         // Only set this based on the first activity
         if (mActivities.isEmpty()) {
             taskType = r.mActivityType;
-            if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) {
+            if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivities) {
                 mResizeable = r.info.resizeable;
             }
             isPersistable = r.isPersistable();
@@ -778,7 +835,7 @@
             final ActivityRecord resumedActivity = stack.mResumedActivity;
             if (resumedActivity != null && resumedActivity.task == this) {
                 final Bitmap thumbnail = stack.screenshotActivities(resumedActivity);
-                setLastThumbnail(thumbnail);
+                setLastThumbnailLocked(thumbnail);
             }
         }
         final TaskThumbnail taskThumbnail = new TaskThumbnail();
@@ -990,6 +1047,7 @@
         if (lastTaskDescription != null) {
             lastTaskDescription.saveToXml(out);
         }
+        mLastThumbnailInfo.saveToXml(out);
         out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
         out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
         out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
@@ -1034,7 +1092,7 @@
             throws IOException, XmlPullParserException {
         Intent intent = null;
         Intent affinityIntent = null;
-        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+        ArrayList<ActivityRecord> activities = new ArrayList<>();
         ComponentName realActivity = null;
         ComponentName origActivity = null;
         String affinity = null;
@@ -1054,6 +1112,7 @@
         int taskId = INVALID_TASK_ID;
         final int outerDepth = in.getDepth();
         TaskDescription taskDescription = new TaskDescription();
+        TaskThumbnailInfo thumbnailInfo = new TaskThumbnailInfo();
         int taskAffiliation = INVALID_TASK_ID;
         int taskAffiliationColor = 0;
         int prevTaskId = INVALID_TASK_ID;
@@ -1102,6 +1161,8 @@
                 lastTimeOnTop = Long.valueOf(attrValue);
             } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
                 neverRelinquishIdentity = Boolean.valueOf(attrValue);
+            } else if (attrName.startsWith(TaskThumbnailInfo.ATTR_TASK_THUMBNAILINFO_PREFIX)) {
+                thumbnailInfo.restoreFromXml(attrName, attrValue);
             } else if (attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
                 taskDescription.restoreFromXml(attrName, attrValue);
             } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
@@ -1180,8 +1241,8 @@
                 affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
                 autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
                 activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
-                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
-                callingUid, callingPackage, resizeable, privileged);
+                taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
+                taskAffiliationColor, callingUid, callingPackage, resizeable, privileged);
         task.updateOverrideConfiguration(bounds);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -1216,7 +1277,7 @@
             }
 
             final Configuration serviceConfig = mService.mConfiguration;
-            mOverrideConfig = new Configuration(serviceConfig);
+            mOverrideConfig = new Configuration(Configuration.EMPTY);
             // TODO(multidisplay): Update Dp to that of display stack is on.
             final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
             mOverrideConfig.screenWidthDp =
@@ -1233,6 +1294,31 @@
         return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
     }
 
+    /** Updates the task's bounds and override configuration to match what is expected for the
+     * input stack. */
+    void updateOverrideConfigurationForStack(ActivityStack inStack) {
+        if (stack != null && stack == inStack) {
+            return;
+        }
+
+        if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+            if (!mResizeable) {
+                throw new IllegalArgumentException("Can not position non-resizeable task="
+                        + this + " in stack=" + inStack);
+            }
+            if (mBounds != null) {
+                return;
+            }
+            if (mLastNonFullscreenBounds != null) {
+                updateOverrideConfiguration(mLastNonFullscreenBounds);
+            } else {
+                inStack.layoutTaskInStack(this, null);
+            }
+        } else {
+            updateOverrideConfiguration(inStack.mBounds);
+        }
+    }
+
     /**
      * Returns the correct stack to use based on task type and currently set bounds,
      * regardless of the focused stack and current stack association of the task.
@@ -1366,6 +1452,8 @@
             sb.append(stringName);
             sb.append(" U=");
             sb.append(userId);
+            sb.append(" StackId=");
+            sb.append(stack != null ? stack.mStackId : INVALID_STACK_ID);
             sb.append(" sz=");
             sb.append(mActivities.size());
             sb.append('}');
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index b4efbf0..d24c3a5 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,7 +17,9 @@
 package com.android.server.am;
 
 import android.app.ActivityManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.TimeUtils;
 
 /**
  * Overall information about a uid that has actively running processes.
@@ -26,12 +28,20 @@
     final int uid;
     int curProcState;
     int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+    long lastBackgroundTime;
+    boolean idle;
     int numProcs;
 
+    static final int CHANGE_PROCSTATE = 0;
+    static final int CHANGE_GONE = 1;
+    static final int CHANGE_GONE_IDLE = 2;
+    static final int CHANGE_IDLE = 3;
+    static final int CHANGE_ACTIVE = 4;
+
     static final class ChangeItem {
         UidRecord uidRecord;
         int uid;
-        boolean gone;
+        int change;
         int processState;
     }
 
@@ -54,9 +64,16 @@
         UserHandle.formatUid(sb, uid);
         sb.append(' ');
         sb.append(ProcessList.makeProcStateString(curProcState));
-        sb.append(" / ");
+        if (lastBackgroundTime > 0) {
+            sb.append(" bg:");
+            TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+        }
+        if (idle) {
+            sb.append(" idle");
+        }
+        sb.append(" procs:");
         sb.append(numProcs);
-        sb.append(" procs}");
+        sb.append("}");
         return sb.toString();
     }
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index cbc13fe..4b2a55f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,7 +18,6 @@
 
 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 android.os.Process.SYSTEM_UID;
@@ -57,13 +56,17 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.UserManagerService;
 
@@ -97,7 +100,9 @@
     /**
      * Which users have been started, so are allowed to run code.
      */
+    @GuardedBy("mService")
     private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+
     /**
      * LRU list of history of current users.  Most recently current is at the end.
      */
@@ -134,7 +139,9 @@
         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));
+        final UserState uss = new UserState(UserHandle.SYSTEM);
+        mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
+        updateUserUnlockedState(uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         updateStartedUserArrayLocked();
     }
@@ -409,6 +416,21 @@
         return userManager;
     }
 
+    private void updateUserUnlockedState(UserState uss) {
+        final IMountService mountService = IMountService.Stub
+                .asInterface(ServiceManager.getService("mount"));
+        if (mountService != null) {
+            try {
+                uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            // System isn't fully booted yet, so guess based on property
+            uss.unlocked = !StorageManager.isFileBasedEncryptionEnabled();
+        }
+    }
+
     boolean startUser(final int userId, final boolean foreground) {
         if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -453,11 +475,14 @@
                 // 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));
+                    mStartedUsers.put(userId, new UserState(new UserHandle(userId)));
                     updateStartedUserArrayLocked();
                     needStart = true;
                 }
 
+                final UserState uss = mStartedUsers.get(userId);
+                updateUserUnlockedState(uss);
+
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
@@ -479,8 +504,6 @@
                     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) {
@@ -499,6 +522,9 @@
                 }
 
                 if (uss.mState == UserState.STATE_BOOTING) {
+                    // Let user manager propagate user restrictions to other services.
+                    getUserManager().onBeforeStartUser(userId);
+
                     // 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.
@@ -586,6 +612,35 @@
         return result;
     }
 
+    boolean unlockUser(final int userId, byte[] token) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: unlockUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final UserInfo userInfo = getUserInfo(userId);
+        final IMountService mountService = IMountService.Stub
+                .asInterface(ServiceManager.getService("mount"));
+        try {
+            mountService.unlockUserKey(userId, userInfo.serialNumber, token);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to unlock: " + e.getMessage());
+            throw e.rethrowAsRuntimeException();
+        }
+
+        synchronized (mService) {
+            final UserState uss = mStartedUsers.get(userId);
+            updateUserUnlockedState(uss);
+        }
+
+        return true;
+    }
+
     void showUserSwitchDialog(int userId, String userName) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
@@ -619,7 +674,7 @@
 
     void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mService) {
-            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
         }
     }
@@ -956,8 +1011,11 @@
             return true;
         }
         if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
-            // TODO: add in amnesia lifecycle
-            return false;
+            // If user is currently locked, we fall through to default "running"
+            // behavior below
+            if (state.unlocked) {
+                return false;
+            }
         }
         return state.mState != UserState.STATE_STOPPING
                 && state.mState != UserState.STATE_SHUTDOWN;
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index b3d82bc..b5b5c1d 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -40,6 +40,7 @@
     public int mState = STATE_BOOTING;
     public boolean switching;
     public boolean initializing;
+    public boolean unlocked;
 
     /**
      * The last time that a provider was reported to usage stats as being brought to important
@@ -47,7 +48,7 @@
      */
     public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
 
-    public UserState(UserHandle handle, boolean initial) {
+    public UserState(UserHandle handle) {
         mHandle = handle;
     }
 
@@ -62,6 +63,11 @@
         }
         if (switching) pw.print(" SWITCHING");
         if (initializing) pw.print(" INITIALIZING");
+        if (unlocked) {
+            pw.print(" UNLOCKED");
+        } else {
+            pw.print(" LOCKED");
+        }
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 058d681..fe9fe50 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1068,6 +1068,7 @@
         if (DEBUG_VOL) {
             Log.d(TAG, String.format("Master mute %s, user=%d", masterMute, currentUser));
         }
+        setSystemAudioMute(masterMute);
         AudioSystem.setMasterMute(masterMute);
         broadcastMasterMuteStatus(masterMute);
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index c1aaf07..6687412 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1353,18 +1353,9 @@
                     if (iface != null) {
                         String[] dnsServers = mDefaultDnsServers;
                         Collection<InetAddress> dnses = linkProperties.getDnsServers();
-                        if (dnses != null) {
-                            // we currently only handle IPv4
-                            ArrayList<InetAddress> v4Dnses =
-                                    new ArrayList<InetAddress>(dnses.size());
-                            for (InetAddress dnsAddress : dnses) {
-                                if (dnsAddress instanceof Inet4Address) {
-                                    v4Dnses.add(dnsAddress);
-                                }
-                            }
-                            if (v4Dnses.size() > 0) {
-                                dnsServers = NetworkUtils.makeStrings(v4Dnses);
-                            }
+                        if (dnses != null && !dnses.isEmpty()) {
+                            // TODO: remove this invocation of NetworkUtils.makeStrings().
+                            dnsServers = NetworkUtils.makeStrings(dnses);
                         }
                         try {
                             Network network = getConnectivityManager().getNetworkForType(upType);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index b766894..75a74c0 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -466,7 +466,7 @@
                 if (cname == null) {
                     info = new SyncStorageEngine.EndPoint(account, authority, userId);
                 } else {
-                    info = new SyncStorageEngine.EndPoint(cname, userId);
+                    info = new SyncStorageEngine.EndPoint(cname, userId, -1);
                 }
                 syncManager.clearScheduledSyncOperations(info);
                 syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 82e0eaf..4f53882 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,8 +21,10 @@
 import android.accounts.AccountManager;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -79,11 +81,14 @@
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
 import com.android.server.content.SyncStorageEngine.EndPoint;
@@ -207,6 +212,7 @@
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
     volatile private boolean mDeviceIsIdle = false;
+    volatile private boolean mReportedSyncActive = false;
 
     private final NotificationManager mNotificationMgr;
     private AlarmManager mAlarmService = null;
@@ -234,7 +240,7 @@
 
     private final AppIdleMonitor mAppIdleMonitor;
 
-    private BroadcastReceiver mStorageIntentReceiver =
+    private final BroadcastReceiver mStorageIntentReceiver =
             new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -257,7 +263,7 @@
                 }
             };
 
-    private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             boolean idle = mPowerManager.isDeviceIdleMode()
                     || mPowerManager.isLightDeviceIdleMode();
@@ -267,12 +273,18 @@
                         SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
                         null /* any sync */);
             } else {
+                if (mLocalDeviceIdleController != null) {
+                    if (!mReportedSyncActive) {
+                        mReportedSyncActive = true;
+                        mLocalDeviceIdleController.setSyncActive(true);
+                    }
+                }
                 sendCheckAlarmsMessage();
             }
         }
     };
 
-    private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mBootCompleted = true;
@@ -280,7 +292,7 @@
         }
     };
 
-    private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             updateRunningAccounts();
@@ -291,7 +303,23 @@
         }
     };
 
+    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelSyncsForUid(uid);
+        }
+    };
+
     private final PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     // Use this as a random offset to seed all periodic syncs.
     private int mSyncRandomOffsetMillis;
@@ -494,6 +522,13 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         if (!factoryTest) {
             mNotificationMgr = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -651,6 +686,26 @@
             Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
         }
 
+        final android.content.pm.ServiceInfo sinfo;
+        try {
+            sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Not scheduling sync " + cname
+                    + " -- can't find service for user " + userId);
+            return;
+        }
+        final int sUid = sinfo.applicationInfo.uid;
+
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(sUid, cname.getPackageName())
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling sync " + sUid + ":" + cname
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
+
         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
         if (expedited) {
             runtimeMillis = -1; // this means schedule at the front of the queue
@@ -678,7 +733,7 @@
             }
             return;
         }
-        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId, sUid);
         Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
         long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
         final long backoffTime = backoff != null ? backoff.first : 0;
@@ -692,7 +747,7 @@
                         + ", extras " + extras);
         }
         scheduleSyncOperation(
-                new SyncOperation(cname, userId, uid, source, extras,
+                new SyncOperation(cname, userId, sUid, cname.getPackageName(), uid, source, extras,
                         runtimeMillis /* runtime */,
                         beforeRunTimeMillis /* flextime */,
                         backoffTime,
@@ -827,6 +882,18 @@
                 if (syncAdapterInfo == null) {
                     continue;
                 }
+                final int owningUid = syncAdapterInfo.uid;
+                final String owningPackage = syncAdapterInfo.componentName.getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(owningUid,
+                            owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                        Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+                                + syncAdapterInfo.componentName
+                                + " -- package not allowed to start");
+                        return;
+                    }
+                } catch (RemoteException e) {
+                }
                 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                 if (isSyncable < 0 && isAlwaysSyncable) {
@@ -876,7 +943,8 @@
                                 + ", extras " + newExtras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -892,7 +960,8 @@
                                 + ", extras " + extras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, extras, runtimeMillis, beforeRuntimeMillis,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -1299,6 +1368,14 @@
         }
     }
 
+    void cancelSyncsForUid(int uid) {
+        synchronized (mSyncQueue) {
+            if (mSyncQueue.removeUidIfNeededLocked(uid)) {
+                sendCheckAlarmsMessage();
+            }
+        }
+    }
+
     /**
      * @hide
      */
@@ -1470,6 +1547,7 @@
         }
         pw.print("memory low: "); pw.println(mStorageIsLow);
         pw.print("device idle: "); pw.println(mDeviceIsIdle);
+        pw.print("reported active: "); pw.println(mReportedSyncActive);
 
         final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
 
@@ -2155,8 +2233,8 @@
         /**
          * Stash any messages that come to the handler before boot is complete or before the device
          * is properly provisioned (i.e. out of set-up wizard).
-         * {@link #onBootCompleted()} and {@link #onDeviceProvisioned(boolean)} both need to come
-         * in before we start syncing.
+         * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
+         * need to come in before we start syncing.
          * @param msg Message to dispatch at a later point.
          * @return true if a message was enqueued, false otherwise. This is to avoid losing the
          * message if we manage to acquire the lock but by the time we do boot has completed.
@@ -2492,6 +2570,8 @@
                             }
                             scheduleSyncOperation(
                                     new SyncOperation(target.account, target.userId,
+                                            syncAdapterInfo.uid,
+                                            syncAdapterInfo.componentName.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             target.provider, extras,
@@ -2502,6 +2582,7 @@
                         } else if (target.target_service) {
                             scheduleSyncOperation(
                                     new SyncOperation(target.service, target.userId,
+                                            target.serviceUid, target.service.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             extras,
@@ -2544,6 +2625,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2551,6 +2633,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: memory low, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2558,6 +2641,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: device idle, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2567,6 +2651,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2772,9 +2857,25 @@
                 dispatchSyncOperation(candidate);
             }
 
+            setSyncActive(mActiveSyncContexts.size() > 0);
+
             return nextReadyToRunTime;
         }
 
+        void setSyncActive(boolean active) {
+            if (mLocalDeviceIdleController == null) {
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
+            }
+            if (mLocalDeviceIdleController != null) {
+                if (mReportedSyncActive != active) {
+                    mReportedSyncActive = active;
+                    mLocalDeviceIdleController.setSyncActive(active);
+                }
+            }
+
+        }
+
         private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) {
             final long bytesTransferredCurrent =
                     getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid);
@@ -3114,6 +3215,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.account, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, info.provider, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
@@ -3124,6 +3226,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.service, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 10efe81..ab777ae 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
 
 /**
@@ -62,6 +63,8 @@
 
     /** Identifying info for the target for this operation. */
     public final SyncStorageEngine.EndPoint target;
+    public final int owningUid;
+    public final String owningPackage;
     /** Why this sync was kicked off. {@link #REASON_NAMES} */
     public final int reason;
     /** Where this sync was initiated. */
@@ -93,25 +96,28 @@
     /** Whether this sync op was recently skipped due to the app being idle */
     public boolean appIdle;
 
-    public SyncOperation(Account account, int userId, int reason, int source, String provider,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
-            long delayUntil, boolean allowParallelSyncs) {
-        this(new SyncStorageEngine.EndPoint(account, provider, userId),
+    public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
+            int reason, int source, String provider, Bundle extras, long runTimeFromNow,
+            long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs) {
+        this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
                 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
                 allowParallelSyncs);
     }
 
-    public SyncOperation(ComponentName service, int userId, int reason, int source,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+    public SyncOperation(ComponentName service, int userId, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff,
             long delayUntil) {
-        this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
-                runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
+        this(new SyncStorageEngine.EndPoint(service, userId, owningUid), owningUid, owningPackage,
+                reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
+                true /* allowParallelSyncs */);
     }
 
-    private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
-            long runTimeFromNow, long flexTime, long backoff, long delayUntil,
-            boolean allowParallelSyncs) {
+    private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime,
+            long backoff, long delayUntil, boolean allowParallelSyncs) {
         this.target = info;
+        this.owningUid = owningUid;
+        this.owningPackage = owningPackage;
         this.reason = reason;
         this.syncSource = source;
         this.extras = new Bundle(extras);
@@ -142,7 +148,8 @@
 
     /** Used to reschedule a sync at a new point in time. */
     public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
-        this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
+        this(other.target, other.owningUid, other.owningPackage, other.reason, other.syncSource,
+                new Bundle(other.extras),
                 newRunTimeFromNow,
                 0L /* In back-off so no flex */,
                 other.backoff,
@@ -228,6 +235,13 @@
         }
         sb.append(", reason: ");
         sb.append(reasonToString(pm, reason));
+        if (!useOneLine) {
+            sb.append("\n    ");
+            sb.append("owningUid=");
+            UserHandle.formatUid(sb, owningUid);
+            sb.append(" owningPackage=");
+            sb.append(owningPackage);
+        }
         if (!useOneLine && !extras.keySet().isEmpty()) {
             sb.append("\n    ");
             extrasToStringBuilder(extras, sb);
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
index 587de1c..b15d0d8 100644
--- a/services/core/java/com/android/server/content/SyncQueue.java
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -16,16 +16,20 @@
 
 package com.android.server.content;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.content.pm.PackageManager;
 import android.content.SyncAdapterType;
 import android.content.SyncAdaptersCache;
 import android.content.pm.RegisteredServicesCache.ServiceInfo;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -74,8 +78,9 @@
                     continue;
                 }
                 operationToAdd = new SyncOperation(
-                        info.account, info.userId, op.reason, op.syncSource, info.provider,
-                        op.extras,
+                        info.account, info.userId, syncAdapterInfo.uid,
+                        syncAdapterInfo.componentName.getPackageName(), op.reason,
+                        op.syncSource, info.provider, op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
                         backoff != null ? backoff.first : 0L,
@@ -84,16 +89,24 @@
                 operationToAdd.pendingOperation = op;
                 add(operationToAdd, op);
             } else if (info.target_service) {
+                android.content.pm.ServiceInfo sinfo;
                 try {
-                    mPackageManager.getServiceInfo(info.service, 0);
+                    sinfo = mPackageManager.getServiceInfo(info.service, info.userId);
                 } catch (PackageManager.NameNotFoundException e) {
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.w(TAG, "Missing sync service for authority " + op.target);
                     }
                     continue;
                 }
+                if (sinfo == null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Missing sync service for authority " + op.target);
+                    }
+                    continue;
+                }
                 operationToAdd = new SyncOperation(
-                        info.service, info.userId, op.reason, op.syncSource,
+                        info.service, info.userId, sinfo.applicationInfo.uid,
+                        info.service.getPackageName(), op.reason, op.syncSource,
                         op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
@@ -161,9 +174,38 @@
                 opsToRemove.add(op);
             }
         }
-            for (SyncOperation op : opsToRemove) {
-                remove(op);
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+    }
+
+    public boolean removeUidIfNeededLocked(int uid) {
+        ArrayList<SyncOperation> opsToRemove = null;
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.owningUid != uid) {
+                continue;
             }
+            try {
+                if (ActivityManagerNative.getDefault().getAppStartMode(op.owningUid,
+                        op.owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                    Slog.w(TAG, "Removing sync " + op.owningUid + ":" + op
+                            + " -- package not allowed to start");
+                    continue;
+                }
+            } catch (RemoteException e) {
+            }
+            if (opsToRemove == null) {
+                opsToRemove = new ArrayList<SyncOperation>();
+            }
+            opsToRemove.add(op);
+        }
+        if (opsToRemove == null) {
+            return false;
+        }
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index cca0c16..8266c08 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -27,6 +27,7 @@
 import android.content.SyncInfo;
 import android.content.SyncRequest;
 import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -42,6 +43,7 @@
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.ArrayMap;
 import android.util.Xml;
@@ -227,14 +229,16 @@
         public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
                 new EndPoint(null, null, UserHandle.USER_ALL);
         final ComponentName service;
+        final int serviceUid;           // -1 for "any"
         final Account account;
         final int userId;
         final String provider;
         final boolean target_service;
         final boolean target_provider;
 
-        public EndPoint(ComponentName service, int userId) {
+        public EndPoint(ComponentName service, int userId, int uid) {
             this.service = service;
+            this.serviceUid = uid;
             this.userId = userId;
             this.account = null;
             this.provider = null;
@@ -247,6 +251,7 @@
             this.provider = provider;
             this.userId = userId;
             this.service = null;
+            this.serviceUid = -1;
             this.target_service = false;
             this.target_provider = true;
         }
@@ -264,6 +269,11 @@
                 return false;
             }
             if (target_service && spec.target_service) {
+                if (serviceUid != spec.serviceUid
+                    && serviceUid >= 0
+                    && spec.serviceUid >= 0) {
+                    return false;
+                }
                 return service.equals(spec.service);
             } else if (target_provider && spec.target_provider) {
                 boolean accountsMatch;
@@ -290,8 +300,9 @@
                     .append("/")
                     .append(provider == null ? "ALL PDRS" : provider);
             } else if (target_service) {
-                sb.append(service.getPackageName() + "/")
-                  .append(service.getClassName());
+                service.appendShortString(sb);
+                sb.append(":");
+                UserHandle.formatUid(sb,serviceUid);
             } else {
                 sb.append("invalid target");
             }
@@ -737,7 +748,7 @@
         synchronized (mAuthorities) {
             if (cname != null) {
                 AuthorityInfo authority = getAuthorityLocked(
-                        new EndPoint(cname, userId),
+                        new EndPoint(cname, userId, -1),
                         "get service active");
                 if (authority == null) {
                     return false;
@@ -749,7 +760,7 @@
     }
 
     public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
-        setSyncableStateForEndPoint(new EndPoint(cname, userId), active ?
+        setSyncableStateForEndPoint(new EndPoint(cname, userId, -1), active ?
                 AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
     }
 
@@ -2064,18 +2075,30 @@
                             new Account(accountName, accountType),
                             authorityName, userId);
                 } else {
-                    info = new EndPoint(
-                            new ComponentName(packageName, className),
-                            userId);
+                    final ComponentName cname = new ComponentName(packageName, className);
+                    android.content.pm.ServiceInfo sinfo = null;
+                    try {
+                        sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Not restoring sync " + cname
+                                + " -- can't find service for user " + userId);
+                    }
+                    if (sinfo != null) {
+                        info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
+                    } else {
+                        info = null;
+                    }
                 }
-                authority = getOrCreateAuthorityLocked(info, id, false);
-                // If the version is 0 then we are upgrading from a file format that did not
-                // know about periodic syncs. In that case don't clear the list since we
-                // want the default, which is a daily periodic sync.
-                // Otherwise clear out this default list since we will populate it later with
-                // the periodic sync descriptions that are read from the configuration file.
-                if (version > 0) {
-                    authority.periodicSyncs.clear();
+                if (info != null) {
+                    authority = getOrCreateAuthorityLocked(info, id, false);
+                    // If the version is 0 then we are upgrading from a file format that did not
+                    // know about periodic syncs. In that case don't clear the list since we
+                    // want the default, which is a daily periodic sync.
+                    // Otherwise clear out this default list since we will populate it later with
+                    // the periodic sync descriptions that are read from the configuration file.
+                    if (version > 0) {
+                        authority.periodicSyncs.clear();
+                    }
                 }
             }
             if (authority != null) {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index ec7c1c4..103ed0a 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1139,6 +1139,12 @@
                     public void onUserSwitching(int newUserId, IRemoteCallback reply) {
                         mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
                                 .sendToTarget();
+                        if (reply != null) {
+                            try {
+                                reply.sendResult(null);
+                            } catch (RemoteException e) {
+                            }
+                        }
                     }
                     @Override
                     public void onUserSwitchComplete(int newUserId) throws RemoteException {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 2b535b9..4d7df9c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -23,7 +23,9 @@
 import java.util.List;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
@@ -51,6 +53,8 @@
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
@@ -83,6 +87,7 @@
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
+    static final int MSG_STOP_JOB = 2;
 
     // Policy constants
     /**
@@ -127,6 +132,7 @@
 
     IBatteryStats mBatteryStats;
     PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     /**
      * Set to true once we are allowed to run third party apps.
@@ -139,6 +145,11 @@
     boolean mDeviceIdleMode;
 
     /**
+     * What we last reported to DeviceIdleController about wheter we are active.
+     */
+    boolean mReportedActive;
+
+    /**
      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
      * still clean up. On reinstall the package will have a new uid.
      */
@@ -154,7 +165,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForUid(uidRemoved);
+                    cancelJobsForUid(uidRemoved, true);
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -172,6 +183,21 @@
         }
     };
 
+    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelJobsForUid(uid, false);
+        }
+    };
+
     @Override
     public void onStartUser(int userHandle) {
         mStartedUsers.add(userHandle);
@@ -194,6 +220,15 @@
     public int schedule(JobInfo job, int uId) {
         JobStatus jobStatus = new JobStatus(job, uId);
         cancelJob(uId, job.getId());
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
+                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+                        + " -- package not allowed to start");
+                return JobScheduler.RESULT_FAILURE;
+            }
+        } catch (RemoteException e) {
+        }
         startTrackingJob(jobStatus);
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
         return JobScheduler.RESULT_SUCCESS;
@@ -229,14 +264,26 @@
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      * @param uid Uid to check against for removal of a job.
+     * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
+     * whose apps are stopped.
      */
-    public void cancelJobsForUid(int uid) {
+    public void cancelJobsForUid(int uid, boolean forceAll) {
         List<JobStatus> jobsForUid;
         synchronized (mJobs) {
             jobsForUid = mJobs.getJobsByUid(uid);
         }
         for (int i=0; i<jobsForUid.size(); i++) {
             JobStatus toRemove = jobsForUid.get(i);
+            if (!forceAll) {
+                String packageName = toRemove.getServiceComponent().getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
+                            != ActivityManager.APP_START_MODE_DISABLED) {
+                        continue;
+                    }
+                } catch (RemoteException e) {
+                }
+            }
             cancelJobImpl(toRemove);
         }
     }
@@ -268,6 +315,7 @@
             mPendingJobs.remove(cancelled);
             // Cancel if running.
             stopJobOnServiceContextLocked(cancelled);
+            reportActive();
         }
     }
 
@@ -299,12 +347,39 @@
                     }
                 } else {
                     // When coming out of idle, allow thing to start back up.
+                    if (rocking) {
+                        if (mLocalDeviceIdleController != null) {
+                            if (!mReportedActive) {
+                                mReportedActive = true;
+                                mLocalDeviceIdleController.setJobsActive(true);
+                            }
+                        }
+                    }
                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                 }
             }
         }
     }
 
+    void reportActive() {
+        boolean active = false;
+        if (mPendingJobs.size() <= 0) {
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobServiceContext jsc = mActiveServices.get(i);
+                if (!jsc.isAvailable()) {
+                    active = true;
+                    break;
+                }
+            }
+        }
+        if (mLocalDeviceIdleController != null) {
+            if (mReportedActive != active) {
+                mReportedActive = active;
+                mLocalDeviceIdleController.setJobsActive(active);
+            }
+        }
+    }
+
     /**
      * Initializes the system service.
      * <p>
@@ -348,12 +423,20 @@
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
+            try {
+                ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                        ActivityManager.UID_OBSERVER_IDLE);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mJobs) {
                 // Let's go!
                 mReadyToRock = true;
                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                         BatteryStats.SERVICE_NAME));
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
                 // Create the "runners".
                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
                     mActiveServices.add(
@@ -597,6 +680,9 @@
                         maybeQueueReadyJobsForExecutionLockedH();
                     }
                     break;
+                case MSG_STOP_JOB:
+                    cancelJobImpl((JobStatus)message.obj);
+                    break;
             }
             maybeRunPendingJobsH();
             // Don't remove JOB_EXPIRED in case one came along while processing the queue.
@@ -623,6 +709,7 @@
                     stopJobOnServiceContextLocked(job);
                 }
             }
+            reportActive();
             if (DEBUG) {
                 final int queuedJobs = mPendingJobs.size();
                 if (queuedJobs == 0) {
@@ -647,11 +734,22 @@
             int idleCount =  0;
             int backoffCount = 0;
             int connectivityCount = 0;
-            List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
+            List<JobStatus> runnableJobs = null;
             ArraySet<JobStatus> jobs = mJobs.getJobs();
             for (int i=0; i<jobs.size(); i++) {
                 JobStatus job = jobs.valueAt(i);
                 if (isReadyToBeExecutedLocked(job)) {
+                    try {
+                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
+                                job.getJob().getService().getPackageName())
+                                == ActivityManager.APP_START_MODE_DISABLED) {
+                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+                                    + job.getJob().toString() + " -- package not allowed to start");
+                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
+                            continue;
+                        }
+                    } catch (RemoteException e) {
+                    }
                     if (job.getNumFailures() > 0) {
                         backoffCount++;
                     }
@@ -664,6 +762,9 @@
                     if (job.hasChargingConstraint()) {
                         chargingCount++;
                     }
+                    if (runnableJobs == null) {
+                        runnableJobs = new ArrayList<>();
+                    }
                     runnableJobs.add(job);
                 } else if (isReadyToBeCancelledLocked(job)) {
                     stopJobOnServiceContextLocked(job);
@@ -673,7 +774,7 @@
                     idleCount >= MIN_IDLE_COUNT ||
                     connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                     chargingCount >= MIN_CHARGING_COUNT ||
-                    runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
+                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                 }
@@ -685,6 +786,7 @@
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                 }
             }
+            reportActive();
             if (DEBUG) {
                 Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
                 connectivityCount + " charging=" + chargingCount + " tot=" +
@@ -766,6 +868,7 @@
                         it.remove();
                     }
                 }
+                reportActive();
             }
         }
     }
@@ -867,7 +970,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid);
+                JobSchedulerService.this.cancelJobsForUid(uid, true);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -948,6 +1051,7 @@
             pw.println();
             pw.print("mReadyToRock="); pw.println(mReadyToRock);
             pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
+            pw.print("mReportedActive="); pw.println(mReportedActive);
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 7028fa6..90dd10ea 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -948,8 +948,8 @@
                 // Launch the last PendingIntent we had with priority
                 int userId = ActivityManager.getCurrentUser();
                 UserRecord user = mUserRecords.get(userId);
-                if (user.mLastMediaButtonReceiver != null
-                        || user.mRestoredMediaButtonReceiver != null) {
+                if (user != null && (user.mLastMediaButtonReceiver != null
+                        || user.mRestoredMediaButtonReceiver != null)) {
                     if (DEBUG) {
                         Log.d(TAG, "Sending media key to last known PendingIntent "
                                 + user.mLastMediaButtonReceiver + " or restored Intent "
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 9db6a06..5b1cedc 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -239,9 +239,7 @@
             throw new RuntimeException("Problem setting firewall rules", e);
         }
 
-        synchronized (mStateLock) {
-            handleStateChangedLocked();
-        }
+        handleStateChangedLocked();
     }
 
     public void shutdown() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 41aea04..2ac0ba6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -459,7 +459,8 @@
         updateScreenOn();
 
         try {
-            mActivityManager.registerUidObserver(mUidObserver);
+            mActivityManager.registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
             mNetworkManager.registerObserver(mAlertObserver);
         } catch (RemoteException e) {
             // ignored; both services live in system_server
@@ -541,6 +542,12 @@
                 removeUidStateLocked(uid);
             }
         }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+        }
     };
 
     final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b84811f..946fbb1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.notification;
 
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_PEEK;
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -78,7 +80,6 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Condition;
-import android.service.notification.IConditionListener;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
@@ -1210,42 +1211,36 @@
         }
 
         @Override
-        public void setPackagePriority(String pkg, int uid, int priority) {
+        public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
             checkCallerIsSystem();
-            mRankingHelper.setPackagePriority(pkg, uid, priority);
+            return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
+        }
+
+        @Override
+        public void setTopicPriority(String pkg, int uid, Notification.Topic topic, int priority) {
+            checkCallerIsSystem();
+            mRankingHelper.setTopicPriority(pkg, uid, topic, priority);
             savePolicyFile();
         }
 
         @Override
-        public int getPackagePriority(String pkg, int uid) {
+        public int getTopicPriority(String pkg, int uid, Notification.Topic topic) {
             checkCallerIsSystem();
-            return mRankingHelper.getPackagePriority(pkg, uid);
+            return mRankingHelper.getTopicPriority(pkg, uid, topic);
         }
 
         @Override
-        public void setPackagePeekable(String pkg, int uid, boolean peekable) {
+        public void setTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic,
+                int visibility) {
             checkCallerIsSystem();
-
-            mRankingHelper.setPackagePeekable(pkg, uid, peekable);
-        }
-
-        @Override
-        public boolean getPackagePeekable(String pkg, int uid) {
-            checkCallerIsSystem();
-            return mRankingHelper.getPackagePeekable(pkg, uid);
-        }
-
-        @Override
-        public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
-            checkCallerIsSystem();
-            mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
+            mRankingHelper.setTopicVisibilityOverride(pkg, uid, topic, visibility);
             savePolicyFile();
         }
 
         @Override
-        public int getPackageVisibilityOverride(String pkg, int uid) {
+        public int getTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic) {
             checkCallerIsSystem();
-            return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
+            return mRankingHelper.getTopicVisibilityOverride(pkg, uid, topic);
         }
 
         /**
@@ -2156,14 +2151,6 @@
                             notification.priority = Notification.PRIORITY_HIGH;
                         }
                     }
-                    // force no heads up per package config
-                    if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
-                        if (notification.extras == null) {
-                            notification.extras = new Bundle();
-                        }
-                        notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
-                                Notification.HEADS_UP_NEVER);
-                    }
 
                     // 1. initial score: buckets of 10, around the app [-20..20]
                     final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
@@ -2511,7 +2498,9 @@
         // light
         // release the light
         boolean wasShowLights = mLights.remove(record.getKey());
-        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
+        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
+                && ((record.getSuppressedVisualEffects()
+                & NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS) == 0)) {
             mLights.add(record.getKey());
             updateLightsLocked();
             if (mUseAttentionLight) {
@@ -2701,6 +2690,11 @@
     // let zen mode evaluate this record
     private void applyZenModeLocked(NotificationRecord record) {
         record.setIntercepted(mZenModeHelper.shouldIntercept(record));
+        if (record.isIntercepted()) {
+            int suppressed = (mZenModeHelper.shouldSuppressLight() ? SUPPRESSED_EFFECT_LIGHTS : 0)
+                    | (mZenModeHelper.shouldSuppressPeek() ? SUPPRESSED_EFFECT_PEEK : 0);
+            record.setSuppressedVisualEffects(suppressed);
+        }
     }
 
     // lock on mNotificationList
@@ -3234,6 +3228,7 @@
         ArrayList<String> keys = new ArrayList<String>(N);
         ArrayList<String> interceptedKeys = new ArrayList<String>(N);
         Bundle visibilityOverrides = new Bundle();
+        Bundle suppressedVisualEffects = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -3242,7 +3237,10 @@
             keys.add(record.sbn.getKey());
             if (record.isIntercepted()) {
                 interceptedKeys.add(record.sbn.getKey());
+
             }
+            suppressedVisualEffects.putInt(
+                    record.sbn.getKey(), record.getSuppressedVisualEffects());
             if (record.getPackageVisibilityOverride()
                     != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
                 visibilityOverrides.putInt(record.sbn.getKey(),
@@ -3264,7 +3262,7 @@
         String[] keysAr = keys.toArray(new String[keys.size()]);
         String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
-                speedBumpIndex);
+                speedBumpIndex, suppressedVisualEffects);
     }
 
     private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f37702c..2a7568d 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -83,6 +83,8 @@
     private String mGlobalSortKey;
     private int mPackageVisibility;
 
+    private int mSuppressedVisualEffects = 0;
+
     @VisibleForTesting
     public NotificationRecord(StatusBarNotification sbn, int score)
     {
@@ -199,6 +201,7 @@
         pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
         pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
         pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
+        pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
     }
 
 
@@ -274,6 +277,14 @@
         return mIntercept;
     }
 
+    public void setSuppressedVisualEffects(int effects) {
+        mSuppressedVisualEffects = effects;
+    }
+
+    public int getSuppressedVisualEffects() {
+        return mSuppressedVisualEffects;
+    }
+
     public boolean isCategory(String category) {
         return Objects.equals(getNotification().category, category);
     }
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 803db10..7ee29e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -15,16 +15,20 @@
  */
 package com.android.server.notification;
 
+import android.app.Notification;
+
+import java.util.List;
+
 public interface RankingConfig {
-    int getPackagePriority(String packageName, int uid);
 
-    void setPackagePriority(String packageName, int uid, int priority);
+    List<Notification.Topic> getTopics(String packageName, int uid);
 
-    boolean getPackagePeekable(String packageName, int uid);
+    int getTopicPriority(String packageName, int uid, Notification.Topic topic);
 
-    void setPackagePeekable(String packageName, int uid, boolean peekable);
+    void setTopicPriority(String packageName, int uid, Notification.Topic topic, int priority);
 
-    int getPackageVisibilityOverride(String packageName, int uid);
+    int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic);
 
-    void setPackageVisibilityOverride(String packageName, int uid, int visibility);
+    void setTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic,
+            int visibility);
 }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 66381f5..4d33248 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -35,6 +37,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 public class RankingHelper implements RankingConfig {
@@ -45,15 +49,16 @@
     private static final String TAG_RANKING = "ranking";
     private static final String TAG_PACKAGE = "package";
     private static final String ATT_VERSION = "version";
+    private static final String TAG_TOPIC = "topic";
 
     private static final String ATT_NAME = "name";
     private static final String ATT_UID = "uid";
     private static final String ATT_PRIORITY = "priority";
-    private static final String ATT_PEEKABLE = "peekable";
     private static final String ATT_VISIBILITY = "visibility";
+    private static final String ATT_TOPIC_ID = "id";
+    private static final String ATT_TOPIC_LABEL = "label";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
-    private static final boolean DEFAULT_PEEKABLE = true;
     private static final int DEFAULT_VISIBILITY =
             NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
 
@@ -141,7 +146,6 @@
                 if (TAG_PACKAGE.equals(tag)) {
                     int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
                     int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
-                    boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
                     int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
                     String name = parser.getAttributeValue(null, ATT_NAME);
 
@@ -164,15 +168,14 @@
                         } else {
                             r = getOrCreateRecord(name, uid);
                         }
-                        if (priority != DEFAULT_PRIORITY) {
-                            r.priority = priority;
-                        }
-                        if (peekable != DEFAULT_PEEKABLE) {
-                            r.peekable = peekable;
-                        }
-                        if (vis != DEFAULT_VISIBILITY) {
-                            r.visibility = vis;
-                        }
+
+                        // Migrate package level settings to the default topic.
+                        // Might be overwritten by parseTopics.
+                        Topic defaultTopic = r.topics.get(Notification.TOPIC_DEFAULT);
+                        defaultTopic.priority = priority;
+                        defaultTopic.visibility = vis;
+
+                        parseTopics(r, parser);
                     }
                 }
             }
@@ -180,6 +183,38 @@
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
+    public void parseTopics(Record r, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        final int innerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (TAG_TOPIC.equals(tagName)) {
+                int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+                int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+                String id = parser.getAttributeValue(null, ATT_TOPIC_ID);
+                CharSequence label = parser.getAttributeValue(null, ATT_TOPIC_LABEL);
+
+                if (!TextUtils.isEmpty(id)) {
+                    Topic topic = new Topic(new Notification.Topic(id, label));
+
+                    if (priority != DEFAULT_PRIORITY) {
+                        topic.priority = priority;
+                    }
+                    if (vis != DEFAULT_VISIBILITY) {
+                        topic.visibility = vis;
+                    }
+                    r.topics.put(id, topic);
+                }
+            }
+        }
+    }
+
     private static String recordKey(String pkg, int uid) {
         return pkg + "|" + uid;
     }
@@ -191,22 +226,14 @@
             r = new Record();
             r.pkg = pkg;
             r.uid = uid;
+            r.topics.put(Notification.TOPIC_DEFAULT,
+                    new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
+                            mContext.getString(R.string.default_notification_topic_label))));
             mRecords.put(key, r);
         }
         return r;
     }
 
-    private void removeDefaultRecords() {
-        final int N = mRecords.size();
-        for (int i = N - 1; i >= 0; i--) {
-            final Record r = mRecords.valueAt(i);
-            if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
-                    && r.visibility == DEFAULT_VISIBILITY) {
-                mRecords.remove(i);
-            }
-        }
-    }
-
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
         out.startTag(null, TAG_RANKING);
         out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -220,23 +247,32 @@
             }
             out.startTag(null, TAG_PACKAGE);
             out.attribute(null, ATT_NAME, r.pkg);
-            if (r.priority != DEFAULT_PRIORITY) {
-                out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
-            }
-            if (r.peekable != DEFAULT_PEEKABLE) {
-                out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
-            }
-            if (r.visibility != DEFAULT_VISIBILITY) {
-                out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
-            }
+
             if (!forBackup) {
                 out.attribute(null, ATT_UID, Integer.toString(r.uid));
             }
+
+            writeTopicsXml(out, r);
             out.endTag(null, TAG_PACKAGE);
         }
         out.endTag(null, TAG_RANKING);
     }
 
+    public void writeTopicsXml(XmlSerializer out, Record r) throws IOException {
+        for (Topic t : r.topics.values()) {
+            out.startTag(null, TAG_TOPIC);
+            out.attribute(null, ATT_TOPIC_ID, t.topic.getId());
+            out.attribute(null, ATT_TOPIC_LABEL, t.topic.getLabel().toString());
+            if (t.priority != DEFAULT_PRIORITY) {
+                out.attribute(null, ATT_PRIORITY, Integer.toString(t.priority));
+            }
+            if (t.visibility != DEFAULT_VISIBILITY) {
+                out.attribute(null, ATT_VISIBILITY, Integer.toString(t.visibility));
+            }
+            out.endTag(null, TAG_TOPIC);
+        }
+    }
+
     private void updateConfig() {
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
@@ -332,51 +368,52 @@
     }
 
     @Override
-    public int getPackagePriority(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.priority : DEFAULT_PRIORITY;
+    public List<Notification.Topic> getTopics(String packageName, int uid) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        List<Notification.Topic> topics = new ArrayList<>();
+        for (Topic t :  r.topics.values()) {
+            topics.add(t.topic);
+        }
+        return topics;
     }
 
     @Override
-    public void setPackagePriority(String packageName, int uid, int priority) {
-        if (priority == getPackagePriority(packageName, uid)) {
-            return;
-        }
-        getOrCreateRecord(packageName, uid).priority = priority;
-        removeDefaultRecords();
+    public int getTopicPriority(String packageName, int uid, Notification.Topic topic) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        return getOrCreateTopic(r, topic).priority;
+    }
+
+    @Override
+    public void setTopicPriority(String packageName, int uid, Notification.Topic topic,
+            int priority) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        getOrCreateTopic(r, topic).priority = priority;
         updateConfig();
     }
 
     @Override
-    public boolean getPackagePeekable(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.peekable : DEFAULT_PEEKABLE;
+    public int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        return getOrCreateTopic(r, topic).visibility;
     }
 
     @Override
-    public void setPackagePeekable(String packageName, int uid, boolean peekable) {
-        if (peekable == getPackagePeekable(packageName, uid)) {
-            return;
-        }
-        getOrCreateRecord(packageName, uid).peekable = peekable;
-        removeDefaultRecords();
+    public void setTopicVisibilityOverride(String pkgName, int uid, Notification.Topic topic,
+        int visibility) {
+        final Record r = getOrCreateRecord(pkgName, uid);
+        getOrCreateTopic(r, topic).visibility = visibility;
         updateConfig();
     }
 
-    @Override
-    public int getPackageVisibilityOverride(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.visibility : DEFAULT_VISIBILITY;
-    }
-
-    @Override
-    public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
-        if (visibility == getPackageVisibilityOverride(packageName, uid)) {
-            return;
+    private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+        Topic t = r.topics.get(topic.getId());
+        if (t != null) {
+            return t;
+        } else {
+            t = new Topic(topic);
+            r.topics.put(topic.getId(), t);
+            return t;
         }
-        getOrCreateRecord(packageName, uid).visibility = visibility;
-        removeDefaultRecords();
-        updateConfig();
     }
 
     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
@@ -411,19 +448,22 @@
                 pw.print(" (");
                 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
                 pw.print(')');
-                if (r.priority != DEFAULT_PRIORITY) {
-                    pw.print(" priority=");
-                    pw.print(Notification.priorityToString(r.priority));
-                }
-                if (r.peekable != DEFAULT_PEEKABLE) {
-                    pw.print(" peekable=");
-                    pw.print(r.peekable);
-                }
-                if (r.visibility != DEFAULT_VISIBILITY) {
-                    pw.print(" visibility=");
-                    pw.print(Notification.visibilityToString(r.visibility));
-                }
                 pw.println();
+                for (Topic t : r.topics.values()) {
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.print("  ");
+                    pw.print(t.topic.getId());
+                    if (t.priority != DEFAULT_PRIORITY) {
+                        pw.print(" priority=");
+                        pw.print(Notification.priorityToString(t.priority));
+                    }
+                    if (t.visibility != DEFAULT_VISIBILITY) {
+                        pw.print(" visibility=");
+                        pw.print(Notification.visibilityToString(t.visibility));
+                    }
+                    pw.println();
+                }
             }
         }
     }
@@ -459,9 +499,16 @@
 
         String pkg;
         int uid = UNKNOWN_UID;
-        int priority = DEFAULT_PRIORITY;
-        boolean peekable = DEFAULT_PEEKABLE;
-        int visibility = DEFAULT_VISIBILITY;
-    }
+        Map<String, Topic> topics = new ArrayMap<>();
+   }
 
+    private static class Topic {
+        Notification.Topic topic;
+        int priority = DEFAULT_PRIORITY;
+        int visibility = DEFAULT_VISIBILITY;
+
+        public Topic(Notification.Topic topic) {
+            this.topic = topic;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
similarity index 79%
rename from services/core/java/com/android/server/notification/PackagePriorityExtractor.java
rename to services/core/java/com/android/server/notification/TopicPriorityExtractor.java
index 6beed9c..5bf989ae 100644
--- a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
@@ -18,8 +18,11 @@
 import android.content.Context;
 import android.util.Slog;
 
-public class PackagePriorityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantPackageExtractor";
+/**
+ * Determines if the given notification can bypass Do Not Disturb.
+ */
+public class TopicPriorityExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "ImportantTopicExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
@@ -39,8 +42,8 @@
             return null;
         }
 
-        final int packagePriority = mConfig.getPackagePriority(
-                record.sbn.getPackageName(), record.sbn.getUid());
+        final int packagePriority = mConfig.getTopicPriority(record.sbn.getPackageName(),
+                record.sbn.getUid(), record.sbn.getNotification().getTopic());
         record.setPackagePriority(packagePriority);
 
         return null;
diff --git a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
similarity index 80%
rename from services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
rename to services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
index af99db7..e053382 100644
--- a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
@@ -18,8 +18,11 @@
 import android.content.Context;
 import android.util.Slog;
 
-public class PackageVisibilityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "PackageVisibilityExtractor";
+/**
+ * Determines if the given notification can display sensitive content on the lockscreen.
+ */
+public class TopicVisibilityExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "TopicVisibilityExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
@@ -39,8 +42,9 @@
             return null;
         }
 
-        final int packageVisibility = mConfig.getPackageVisibilityOverride(
-                record.sbn.getPackageName(), record.sbn.getUid());
+        final int packageVisibility = mConfig.getTopicVisibilityOverride(
+                record.sbn.getPackageName(), record.sbn.getUid(),
+                record.sbn.getNotification().getTopic());
         record.setPackageVisibilityOverride(packageVisibility);
 
         return null;
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 0420269..c9e1315 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -41,6 +41,9 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+import android.os.SystemClock;
+import com.android.internal.logging.MetricsLogger;
+
 /**
  * This {@link NotificationSignalExtractor} attempts to validate
  * people references. Also elevates the priority of real people.
@@ -218,6 +221,7 @@
 
     private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
             float[] affinityOut) {
+        long start = SystemClock.elapsedRealtime();
         float affinity = NONE;
         if (extras == null) {
             return null;
@@ -251,6 +255,9 @@
         // record the best available data, so far:
         affinityOut[0] = affinity;
 
+        MetricsLogger.histogram(mBaseContext, "validate_people_cache_latency",
+                (int) (SystemClock.elapsedRealtime() - start));
+
         if (pendingLookups.isEmpty()) {
             if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
             return null;
@@ -430,6 +437,7 @@
 
         @Override
         public void work() {
+            long start = SystemClock.elapsedRealtime();
             if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
@@ -468,6 +476,9 @@
                 mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
                         mContactAffinity == STARRED_CONTACT, false /* cached */);
             }
+
+            MetricsLogger.histogram(mBaseContext, "validate_people_lookup_latency",
+                    (int) (SystemClock.elapsedRealtime() - start));
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a1f8c41..dbdc3f4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -138,6 +138,18 @@
         }
     }
 
+    public boolean shouldSuppressLight() {
+        synchronized (mConfig) {
+            return !mConfig.allowLights;
+        }
+    }
+
+    public boolean shouldSuppressPeek() {
+        synchronized (mConfig) {
+            return !mConfig.allowPeek;
+        }
+    }
+
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
     }
@@ -394,11 +406,11 @@
             return;
         }
         pw.printf("allow(calls=%s,callsFrom=%s,repeatCallers=%s,messages=%s,messagesFrom=%s,"
-                + "events=%s,reminders=%s)\n",
+                + "events=%s,reminders=%s,lights=%s,peek=%s)\n",
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
                 ZenModeConfig.sourceToString(config.allowMessagesFrom),
-                config.allowEvents, config.allowReminders);
+                config.allowEvents, config.allowReminders, config.allowLights, config.allowPeek);
         pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
         if (config.automaticRules.isEmpty()) return;
         final int N = config.automaticRules.size();
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 7b1ac5ca..af20679 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
 
 /**
  * {@hide}
@@ -55,6 +56,7 @@
                 .setRequiresDeviceIdle(true)
                 .setRequiresCharging(true)
                 .setMinimumLatency(minLatency)
+                .setPeriodic(TimeUnit.DAYS.toMillis(1))
                 .build();
         js.schedule(job);
     }
@@ -89,7 +91,7 @@
                         // skip previously failing package
                         continue;
                     }
-                    if (!pm.performDexOpt(pkg, null /* instruction set */, true)) {
+                    if (!pm.performDexOpt(pkg, null /* instruction set */)) {
                         // there was a problem running dexopt,
                         // remember this so we do not keep retrying.
                         sFailedPackageNames.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index e4dbf65..073b4f03 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -57,6 +57,8 @@
     private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
     private static final boolean DEBUG = false;
 
+    private static final int DEFAULT_FLAGS = PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS;
+
     private static final String AUDIO_MIME_TYPE = "audio/mpeg";
 
     private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
@@ -696,7 +698,7 @@
     private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
             Intent intent, int userId) {
         ResolveInfo handler = mService.resolveIntent(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
         if (handler == null || handler.activityInfo == null) {
             return null;
         }
@@ -711,7 +713,7 @@
     private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
             Intent intent, int userId) {
         List<ResolveInfo> handlers = mService.queryIntentServices(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
         if (handlers == null) {
             return null;
         }
@@ -738,7 +740,8 @@
             homeIntent.setPackage(syncAdapterPackageName);
 
             ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
-                    homeIntent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                    homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
+                    userId);
             if (homeActivity != null) {
                 continue;
             }
@@ -754,7 +757,7 @@
 
     private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
             String authority, int userId) {
-        ProviderInfo provider = mService.resolveContentProvider(authority, 0, userId);
+        ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
         if (provider != null) {
             return getSystemPackageLPr(provider.packageName);
         }
@@ -871,7 +874,7 @@
             return false;
         }
         PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-        if (sysPkg != null) {
+        if (sysPkg != null && sysPkg.pkg != null) {
             if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
                 return false;
             }
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
new file mode 100644
index 0000000..628ad0e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimedRemoteCaller;
+
+import com.android.internal.app.EphemeralResolverService;
+import com.android.internal.app.EphemeralResolveInfo;
+import com.android.internal.app.IEphemeralResolver;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Represents a remote ephemeral resolver. It is responsible for binding to the remote
+ * service and handling all interactions in a timely manner.
+ * @hide
+ */
+final class EphemeralResolverConnection {
+    // This is running in a critical section and the timeout must be sufficiently low
+    private static final long BIND_SERVICE_TIMEOUT_MS =
+            ("eng".equals(Build.TYPE)) ? 300 : 200;
+
+    private final Object mLock = new Object();
+    private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
+            new GetEphemeralResolveInfoCaller();
+    private final ServiceConnection mServiceConnection = new MyServiceConnection();
+    private final Context mContext;
+    /** Intent used to bind to the service */
+    private final Intent mIntent;
+
+    private IEphemeralResolver mRemoteInstance;
+
+    public EphemeralResolverConnection(Context context, ComponentName componentName) {
+        mContext = context;
+        mIntent = new Intent().setComponent(componentName);
+    }
+
+    public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
+        throwIfCalledOnMainThread();
+        try {
+            return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
+                    getRemoteInstanceLazy(), hashPrefix);
+        } catch (RemoteException re) {
+        } catch (TimeoutException te) {
+        } finally {
+            synchronized (mLock) {
+                mLock.notifyAll();
+            }
+        }
+        return null;
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.append(prefix).append("bound=")
+                    .append((mRemoteInstance != null) ? "true" : "false").println();
+
+            pw.flush();
+
+            try {
+                getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix });
+            } catch (TimeoutException te) {
+                /* ignore */
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+    }
+
+    private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException {
+        synchronized (mLock) {
+            if (mRemoteInstance != null) {
+                return mRemoteInstance;
+            }
+            bindLocked();
+            return mRemoteInstance;
+        }
+    }
+
+    private void bindLocked() throws TimeoutException {
+        if (mRemoteInstance != null) {
+            return;
+        }
+
+        mContext.bindServiceAsUser(mIntent, mServiceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM);
+
+        final long startMillis = SystemClock.uptimeMillis();
+        while (true) {
+            if (mRemoteInstance != null) {
+                break;
+            }
+            final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+            final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
+            if (remainingMillis <= 0) {
+                throw new TimeoutException("Didn't bind to resolver in time.");
+            }
+            try {
+                mLock.wait(remainingMillis);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+
+        mLock.notifyAll();
+    }
+
+    private void throwIfCalledOnMainThread() {
+        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+            throw new RuntimeException("Cannot invoke on the main thread");
+        }
+    }
+
+    private final class MyServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                mRemoteInstance = IEphemeralResolver.Stub.asInterface(service);
+                mLock.notifyAll();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                mRemoteInstance = null;
+            }
+        }
+    }
+
+    private static final class GetEphemeralResolveInfoCaller
+            extends TimedRemoteCaller<List<EphemeralResolveInfo>> {
+        private final IRemoteCallback mCallback;
+
+        public GetEphemeralResolveInfoCaller() {
+            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+            mCallback = new IRemoteCallback.Stub() {
+                    @Override
+                    public void sendResult(Bundle data) throws RemoteException {
+                        final ArrayList<EphemeralResolveInfo> resolveList =
+                                data.getParcelableArrayList(
+                                        EphemeralResolverService.EXTRA_RESOLVE_INFO);
+                        int sequence =
+                                data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1);
+                        onRemoteMethodResult(resolveList, sequence);
+                    }
+            };
+        }
+
+        public List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+                IEphemeralResolver target, int hashPrefix)
+                        throws RemoteException, TimeoutException {
+            final int sequence = onBeforeRemoteCall();
+            target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
+            return getResultTimed(sequence);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 150c849..99a051a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -185,15 +185,6 @@
         return mInstaller.execute(builder.toString());
     }
 
-    public int rename(String oldname, String newname) {
-        StringBuilder builder = new StringBuilder("rename");
-        builder.append(' ');
-        builder.append(oldname);
-        builder.append(' ');
-        builder.append(newname);
-        return mInstaller.execute(builder.toString());
-    }
-
     @Deprecated
     public int fixUid(String name, int uid, int gid) {
         return fixUid(null, name, uid, gid);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 56c4fb1..d29a623 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -55,7 +55,6 @@
     static final int DEX_OPT_FAILED = -1;
 
     private final PackageManagerService mPackageManagerService;
-    private ArraySet<PackageParser.Package> mDeferredDexOpt;
 
     private final PowerManager.WakeLock mDexoptWakeLock;
     private volatile boolean mSystemReady;
@@ -75,8 +74,7 @@
      * {@link PackageManagerService#mInstallLock}.
      */
     int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
-            boolean forceDex, boolean defer, boolean inclDependencies,
-            boolean bootComplete, boolean useJit) {
+            boolean inclDependencies) {
         ArraySet<String> done;
         if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
             done = new ArraySet<String>();
@@ -91,8 +89,7 @@
                 mDexoptWakeLock.acquire();
             }
             try {
-                return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete,
-                        useJit, done);
+                return performDexOptLI(pkg, instructionSets, done);
             } finally {
                 if (useLock) {
                     mDexoptWakeLock.release();
@@ -102,7 +99,6 @@
     }
 
     private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
-            boolean forceDex, boolean defer, boolean bootComplete, boolean useJit,
             ArraySet<String> done) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
@@ -110,12 +106,10 @@
         if (done != null) {
             done.add(pkg.packageName);
             if (pkg.usesLibraries != null) {
-                performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer,
-                        bootComplete, useJit, done);
+                performDexOptLibsLI(pkg.usesLibraries, instructionSets, done);
             }
             if (pkg.usesOptionalLibraries != null) {
-                performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer,
-                        bootComplete, useJit, done);
+                performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done);
             }
         }
 
@@ -134,30 +128,18 @@
         // 3.) we are skipping an unneeded dexopt
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-            if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+            if (pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
                 continue;
             }
 
             for (String path : paths) {
                 final int dexoptNeeded;
-                if (forceDex) {
-                    dexoptNeeded = DexFile.DEX2OAT_NEEDED;
-                } else {
-                    try {
-                        dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
-                                dexCodeInstructionSet, defer);
-                    } catch (IOException ioe) {
-                        Slog.w(TAG, "IOException reading apk: " + path, ioe);
-                        return DEX_OPT_FAILED;
-                    }
-                }
-
-                if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
-                    // We're deciding to defer a needed dexopt. Don't bother dexopting for other
-                    // paths and instruction sets. We'll deal with them all together when we process
-                    // our list of deferred dexopts.
-                    addPackageForDeferredDexopt(pkg);
-                    return DEX_OPT_DEFERRED;
+                try {
+                    dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+                            dexCodeInstructionSet, /* defer */false);
+                } catch (IOException ioe) {
+                    Slog.w(TAG, "IOException reading apk: " + path, ioe);
+                    return DEX_OPT_FAILED;
                 }
 
                 if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
@@ -177,20 +159,17 @@
                     Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                             + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                             + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
-                            + " oatDir = " + oatDir + " bootComplete=" + bootComplete
-                            + " useJit=" + useJit);
+                            + " oatDir = " + oatDir);
                     final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                     final int dexFlags =
                             (!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
                             | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                             | (debuggable ? DEXOPT_DEBUGGABLE : 0)
-                            | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0)
-                            | (useJit ? DEXOPT_USEJIT : 0);
+                            | DEXOPT_BOOTCOMPLETE;
                     final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
                             pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir, dexFlags);
 
-                    // Dex2oat might fail due to compiler / verifier errors. We soldier on
-                    // regardless, and attempt to interpret the app as a safety net.
+                    // Dex2oat might fail due to compiler / verifier errors.
                     if (ret == 0) {
                         performedDexOpt = true;
                     }
@@ -243,34 +222,16 @@
     }
 
     private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
-            boolean forceDex, boolean defer, boolean bootComplete, boolean useJit,
             ArraySet<String> done) {
         for (String libName : libs) {
             PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
                     libName);
             if (libPkg != null && !done.contains(libName)) {
-                performDexOptLI(libPkg, instructionSets, forceDex, defer, bootComplete, useJit, done);
+                performDexOptLI(libPkg, instructionSets, done);
             }
         }
     }
 
-    /**
-     * Clears set of deferred dexopt packages.
-     * @return content of dexopt set if it was not empty
-     */
-    public ArraySet<PackageParser.Package> clearDeferredDexOptPackages() {
-        ArraySet<PackageParser.Package> result = mDeferredDexOpt;
-        mDeferredDexOpt = null;
-        return result;
-    }
-
-    public void addPackageForDeferredDexopt(PackageParser.Package pkg) {
-        if (mDeferredDexOpt == null) {
-            mDeferredDexOpt = new ArraySet<>();
-        }
-        mDeferredDexOpt.add(pkg);
-    }
-
     void systemReady() {
         mSystemReady = true;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09efefa..21a4206 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -105,6 +105,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.AppsQueryHelper;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
@@ -208,6 +209,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.EphemeralResolveInfo;
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.NativeLibraryHelper;
@@ -251,6 +253,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.cert.CertificateEncodingException;
@@ -300,6 +303,7 @@
     private static final boolean DEBUG_VERIFY = false;
     private static final boolean DEBUG_DEXOPT = false;
     private static final boolean DEBUG_ABI_SELECTION = false;
+    private static final boolean DEBUG_EPHEMERAL = false;
 
     static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
 
@@ -429,8 +433,6 @@
     final Context mContext;
     final boolean mFactoryTest;
     final boolean mOnlyCore;
-    final boolean mLazyDexOpt;
-    final long mDexOptLRUThresholdInMills;
     final DisplayMetrics mMetrics;
     final int mDefParseFlags;
     final String[] mSeparateProcesses;
@@ -599,12 +601,25 @@
     private final ComponentName mIntentFilterVerifierComponent;
     private int mIntentFilterVerificationToken = 0;
 
+    /** Component that knows whether or not an ephemeral application exists */
+    final ComponentName mEphemeralResolverComponent;
+    /** The service connection to the ephemeral resolver */
+    final EphemeralResolverConnection mEphemeralResolverConnection;
+
+    /** Component used to install ephemeral applications */
+    final ComponentName mEphemeralInstallerComponent;
+    final ActivityInfo mEphemeralInstallerActivity = new ActivityInfo();
+    final ResolveInfo mEphemeralInstallerInfo = new ResolveInfo();
+
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
             = new SparseArray<IntentFilterVerificationState>();
 
     final DefaultPermissionGrantPolicy mDefaultPermissionPolicy =
             new DefaultPermissionGrantPolicy(this);
 
+    // List of packages names to keep cached, even if they are uninstalled for all users
+    private List<String> mKeepUninstalledPackages;
+
     private static class IFVerificationParams {
         PackageParser.Package pkg;
         boolean replacing;
@@ -1376,7 +1391,9 @@
                             // Now that we successfully installed the package, grant runtime
                             // permissions if requested before broadcasting the install.
                             if ((args.installFlags
-                                    & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) {
+                                    & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+                                    && res.pkg.applicationInfo.targetSdkVersion
+                                            >= Build.VERSION_CODES.M) {
                                 grantRequestedRuntimePermissions(res.pkg, args.user.getIdentifier(),
                                         args.installGrantPermissions);
                             }
@@ -1415,18 +1432,18 @@
                                 }
                             }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                                    packageName, extras, null, null, firstUsers);
+                                    packageName, extras, 0, null, null, firstUsers);
                             final boolean update = res.removedInfo.removedPackage != null;
                             if (update) {
                                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
                             }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                                    packageName, extras, null, null, updateUsers);
+                                    packageName, extras, 0, null, null, updateUsers);
                             if (update) {
                                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                                        packageName, extras, null, null, updateUsers);
+                                        packageName, extras, 0, null, null, updateUsers);
                                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
-                                        null, null, packageName, null, updateUsers);
+                                        null, null, 0, packageName, null, updateUsers);
 
                                 // treat asec-hosted packages like removable media on upgrade
                                 if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
@@ -1812,10 +1829,48 @@
             boolean factoryTest, boolean onlyCore) {
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
+        m.enableSystemUserApps();
         ServiceManager.addService("package", m);
         return m;
     }
 
+    private void enableSystemUserApps() {
+        if (!UserManager.isSplitSystemUser()) {
+            return;
+        }
+        // For system user, enable apps based on the following conditions:
+        // - app is whitelisted or belong to one of these groups:
+        //   -- system app which has no launcher icons
+        //   -- system app which has INTERACT_ACROSS_USERS permission
+        //   -- system IME app
+        // - app is not in the blacklist
+        AppsQueryHelper queryHelper = new AppsQueryHelper(this);
+        Set<String> enableApps = new ArraySet<>();
+        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
+                | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
+                | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
+        ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
+        enableApps.addAll(wlApps);
+        ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
+        enableApps.removeAll(blApps);
+
+        List<String> systemApps = queryHelper.queryApps(0, /* systemAppsOnly */ true,
+                UserHandle.SYSTEM);
+        final int systemAppsSize = systemApps.size();
+        synchronized (mPackages) {
+            for (int i = 0; i < systemAppsSize; i++) {
+                String pName = systemApps.get(i);
+                PackageSetting pkgSetting = mSettings.mPackages.get(pName);
+                // Should not happen, but we shouldn't be failing if it does
+                if (pkgSetting == null) {
+                    continue;
+                }
+                boolean installed = enableApps.contains(pName);
+                pkgSetting.setInstalled(installed, UserHandle.USER_SYSTEM);
+            }
+        }
+    }
+
     static String[] splitString(String str, char sep) {
         int count = 1;
         int i = 0;
@@ -1856,7 +1911,6 @@
         mContext = context;
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
-        mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
         mMetrics = new DisplayMetrics();
         mSettings = new Settings(mPackages);
         mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
@@ -1872,15 +1926,6 @@
         mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
 
-        // TODO: add a property to control this?
-        long dexOptLRUThresholdInMinutes;
-        if (mLazyDexOpt) {
-            dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds.
-        } else {
-            dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users.
-        }
-        mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000;
-
         String separateProcesses = SystemProperties.get("debug.separate_processes");
         if (separateProcesses != null && separateProcesses.length() > 0) {
             if ("*".equals(separateProcesses)) {
@@ -1929,8 +1974,7 @@
             mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
-            sUserManager = new UserManagerService(context, this,
-                    mInstallLock, mPackages);
+            sUserManager = new UserManagerService(context, this, mPackages);
 
             // Propagate permission configuration in to package manager.
             ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -1975,31 +2019,14 @@
             // scanning install directories.
             final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
 
-            final ArraySet<String> alreadyDexOpted = new ArraySet<String>();
-
-            /**
-             * Add everything in the in the boot class path to the
-             * list of process files because dexopt will have been run
-             * if necessary during zygote startup.
-             */
             final String bootClassPath = System.getenv("BOOTCLASSPATH");
             final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
 
-            if (bootClassPath != null) {
-                String[] bootClassPathElements = splitString(bootClassPath, ':');
-                for (String element : bootClassPathElements) {
-                    alreadyDexOpted.add(element);
-                }
-            } else {
+            if (bootClassPath == null) {
                 Slog.w(TAG, "No BOOTCLASSPATH found!");
             }
 
-            if (systemServerClassPath != null) {
-                String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
-                for (String element : systemServerClassPathElements) {
-                    alreadyDexOpted.add(element);
-                }
-            } else {
+            if (systemServerClassPath == null) {
                 Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
             }
 
@@ -2026,7 +2053,6 @@
                         try {
                             int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
                             if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
-                                alreadyDexOpted.add(lib);
                                 mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,
                                         dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/);
                             }
@@ -2042,52 +2068,6 @@
 
             File frameworkDir = new File(Environment.getRootDirectory(), "framework");
 
-            // Gross hack for now: we know this file doesn't contain any
-            // code, so don't dexopt it to avoid the resulting log spew.
-            alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
-
-            // Gross hack for now: we know this file is only part of
-            // the boot class path for art, so don't dexopt it to
-            // avoid the resulting log spew.
-            alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
-
-            /**
-             * There are a number of commands implemented in Java, which
-             * we currently need to do the dexopt on so that they can be
-             * run from a non-root shell.
-             */
-            String[] frameworkFiles = frameworkDir.list();
-            if (frameworkFiles != null) {
-                // TODO: We could compile these only for the most preferred ABI. We should
-                // first double check that the dex files for these commands are not referenced
-                // by other system apps.
-                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-                    for (int i=0; i<frameworkFiles.length; i++) {
-                        File libPath = new File(frameworkDir, frameworkFiles[i]);
-                        String path = libPath.getPath();
-                        // Skip the file if we already did it.
-                        if (alreadyDexOpted.contains(path)) {
-                            continue;
-                        }
-                        // Skip the file if it is not a type we want to dexopt.
-                        if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
-                            continue;
-                        }
-                        try {
-                            int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
-                            if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
-                                mInstaller.dexopt(path, Process.SYSTEM_UID, dexCodeInstructionSet,
-                                        dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/);
-                            }
-                        } catch (FileNotFoundException e) {
-                            Slog.w(TAG, "Jar not found: " + path);
-                        } catch (IOException e) {
-                            Slog.w(TAG, "Exception reading jar: " + path, e);
-                        }
-                    }
-                }
-            }
-
             final VersionInfo ver = mSettings.getInternalVersion();
             mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
             // when upgrading from pre-M, promote system app permissions from install to runtime
@@ -2306,7 +2286,6 @@
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
                 adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
-                        false /* force dexopt */, false /* defer dexopt */,
                         false /* boot complete */);
             }
 
@@ -2383,6 +2362,33 @@
             mIntentFilterVerifier = new IntentVerifierProxy(mContext,
                     mIntentFilterVerifierComponent);
 
+            final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
+            final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr();
+            // both the installer and resolver must be present to enable ephemeral
+            if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent
+                            + " installer:" + ephemeralInstallerComponent);
+                }
+                mEphemeralResolverComponent = ephemeralResolverComponent;
+                mEphemeralInstallerComponent = ephemeralInstallerComponent;
+                setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
+                mEphemeralResolverConnection =
+                        new EphemeralResolverConnection(mContext, mEphemeralResolverComponent);
+            } else {
+                if (DEBUG_EPHEMERAL) {
+                    final String missingComponent =
+                            (ephemeralResolverComponent == null)
+                            ? (ephemeralInstallerComponent == null)
+                                    ? "resolver and installer"
+                                    : "resolver"
+                            : "installer";
+                    Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent);
+                }
+                mEphemeralResolverComponent = null;
+                mEphemeralInstallerComponent = null;
+                mEphemeralResolverConnection = null;
+            }
         } // synchronized (mPackages)
         } // synchronized (mInstallLock)
 
@@ -2521,6 +2527,89 @@
         return verifierComponentName;
     }
 
+    private ComponentName getEphemeralResolverLPr() {
+        final String[] packageArray =
+                mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
+        if (packageArray.length == 0) {
+            if (DEBUG_EPHEMERAL) {
+                Slog.d(TAG, "Ephemeral resolver NOT found; empty package list");
+            }
+            return null;
+        }
+
+        Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
+        final List<ResolveInfo> resolvers = queryIntentServices(resolverIntent,
+                null /*resolvedType*/, 0 /*flags*/, UserHandle.USER_SYSTEM);
+
+        final int N = resolvers.size();
+        if (N == 0) {
+            if (DEBUG_EPHEMERAL) {
+                Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters");
+            }
+            return null;
+        }
+
+        final Set<String> possiblePackages = new ArraySet<>(Arrays.asList(packageArray));
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = resolvers.get(i);
+
+            if (info.serviceInfo == null) {
+                continue;
+            }
+
+            final String packageName = info.serviceInfo.packageName;
+            if (!possiblePackages.contains(packageName)) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.d(TAG, "Ephemeral resolver not in allowed package list;"
+                            + " pkg: " + packageName + ", info:" + info);
+                }
+                continue;
+            }
+
+            if (DEBUG_EPHEMERAL) {
+                Slog.v(TAG, "Ephemeral resolver found;"
+                        + " pkg: " + packageName + ", info:" + info);
+            }
+            return new ComponentName(packageName, info.serviceInfo.name);
+        }
+        if (DEBUG_EPHEMERAL) {
+            Slog.v(TAG, "Ephemeral resolver NOT found");
+        }
+        return null;
+    }
+
+    private ComponentName getEphemeralInstallerLPr() {
+        Intent installerIntent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+        installerIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
+        final List<ResolveInfo> installers = queryIntentActivities(installerIntent,
+                PACKAGE_MIME_TYPE, 0 /*flags*/, 0 /*userId*/);
+
+        ComponentName ephemeralInstaller = null;
+
+        final int N = installers.size();
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = installers.get(i);
+            final String packageName = info.activityInfo.packageName;
+
+            if (!info.activityInfo.applicationInfo.isSystemApp()) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.d(TAG, "Ephemeral installer is not system app;"
+                            + " pkg: " + packageName + ", info:" + info);
+                }
+                continue;
+            }
+
+            if (ephemeralInstaller != null) {
+                throw new RuntimeException("There must only be one ephemeral installer");
+            }
+
+            ephemeralInstaller = new ComponentName(packageName, info.activityInfo.name);
+        }
+
+        return ephemeralInstaller;
+    }
+
     private void primeDomainVerificationsLPw(int userId) {
         if (DEBUG_DOMAIN_VERIFICATION) {
             Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -3058,21 +3147,25 @@
      * purposefully done before acquiring {@link #mPackages} lock.
      */
     private int augmentFlagsForUser(int flags, int userId) {
-        final IActivityManager am = ActivityManagerNative.getDefault();
-        if (am == null) {
-            // We must be early in boot, so the best we can do is assume the
-            // user is fully running.
-            return flags;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            if (am.isUserRunning(userId, ActivityManager.FLAG_WITH_AMNESIA)) {
-                flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
+            final IMountService mount = IMountService.Stub
+                    .asInterface(ServiceManager.getService("mount"));
+            if (mount == null) {
+                // We must be early in boot, so the best we can do is assume the
+                // user is fully running.
+                Slog.w(TAG, "Early during boot, assuming not encrypted");
+                return flags;
             }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        } finally {
-            Binder.restoreCallingIdentity(token);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!mount.isUserKeyUnlocked(userId)) {
+                    flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
         return flags;
     }
@@ -3603,6 +3696,11 @@
                 return;
             }
 
+            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+                return;
+            }
+
             final int result = permissionsState.grantRuntimePermission(bp, userId);
             switch (result) {
                 case PermissionsState.PERMISSION_OPERATION_FAILURE: {
@@ -4299,8 +4397,97 @@
                 false, false, false, userId);
     }
 
+    private boolean isEphemeralAvailable(Intent intent, String resolvedType, int userId) {
+        MessageDigest digest = null;
+        try {
+            digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            // If we can't create a digest, ignore ephemeral apps.
+            return false;
+        }
+
+        final byte[] hostBytes = intent.getData().getHost().getBytes();
+        final byte[] digestBytes = digest.digest(hostBytes);
+        int shaPrefix =
+                digestBytes[0] << 24
+                | digestBytes[1] << 16
+                | digestBytes[2] << 8
+                | digestBytes[3] << 0;
+        final List<EphemeralResolveInfo> ephemeralResolveInfoList =
+                mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix);
+        if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
+            // No hash prefix match; there are no ephemeral apps for this domain.
+            return false;
+        }
+        for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) {
+            EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i);
+            if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) {
+                continue;
+            }
+            final List<IntentFilter> filters = ephemeralApplication.getFilters();
+            // No filters; this should never happen.
+            if (filters.isEmpty()) {
+                continue;
+            }
+            // We have a domain match; resolve the filters to see if anything matches.
+            final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
+            for (int j = filters.size() - 1; j >= 0; --j) {
+                ephemeralResolver.addFilter(filters.get(j));
+            }
+            List<ResolveInfo> ephemeralResolveList = ephemeralResolver.queryIntent(
+                    intent, resolvedType, false /*defaultOnly*/, userId);
+            return !ephemeralResolveList.isEmpty();
+        }
+        // Hash or filter mis-match; no ephemeral apps for this domain.
+        return false;
+    }
+
     private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
             int flags, List<ResolveInfo> query, int userId) {
+        final boolean isWebUri = hasWebURI(intent);
+        // Check whether or not an ephemeral app exists to handle the URI.
+        if (isWebUri && mEphemeralResolverConnection != null) {
+            // Deny ephemeral apps if the user choose _ALWAYS or _ALWAYS_ASK for intent resolution.
+            boolean hasAlwaysHandler = false;
+            synchronized (mPackages) {
+                final int count = query.size();
+                for (int n=0; n<count; n++) {
+                    ResolveInfo info = query.get(n);
+                    String packageName = info.activityInfo.packageName;
+                    PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (ps != null) {
+                        // Try to get the status from User settings first
+                        long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+                        int status = (int) (packedStatus >> 32);
+                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+                                || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                            hasAlwaysHandler = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // Only consider installing an ephemeral app if there isn't already a verified handler.
+            // We've determined that there's an ephemeral app available for the URI, ignore any
+            // ResolveInfo's and just return the ephemeral installer
+            if (!hasAlwaysHandler && isEphemeralAvailable(intent, resolvedType, userId)) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.v(TAG, "Resolving to the ephemeral installer");
+                }
+                // ditch the result and return a ResolveInfo to launch the ephemeral installer
+                ResolveInfo ri = new ResolveInfo(mEphemeralInstallerInfo);
+                ri.activityInfo = new ActivityInfo(ri.activityInfo);
+                // make a deep copy of the applicationInfo
+                ri.activityInfo.applicationInfo = new ApplicationInfo(
+                        ri.activityInfo.applicationInfo);
+                if (userId != 0) {
+                    ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+                            UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+                }
+                return ri;
+            }
+        }
         if (query != null) {
             final int N = query.size();
             if (N == 1) {
@@ -6147,8 +6334,8 @@
     }
 
     @Override
-    public void performBootDexOpt() {
-        enforceSystemOrRoot("Only the system can request dexopt be performed");
+    public void performFstrimIfNeeded() {
+        enforceSystemOrRoot("Only the system can request fstrim");
 
         // Before everything else, see whether we need to fstrim.
         try {
@@ -6189,98 +6376,6 @@
         } catch (RemoteException e) {
             // Can't happen; MountService is local
         }
-
-        final ArraySet<PackageParser.Package> pkgs;
-        synchronized (mPackages) {
-            pkgs = mPackageDexOptimizer.clearDeferredDexOptPackages();
-        }
-
-        if (pkgs != null) {
-            // Sort apps by importance for dexopt ordering. Important apps are given more priority
-            // in case the device runs out of space.
-            ArrayList<PackageParser.Package> sortedPkgs = new ArrayList<PackageParser.Package>();
-            // Give priority to core apps.
-            for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
-                PackageParser.Package pkg = it.next();
-                if (pkg.coreApp) {
-                    if (DEBUG_DEXOPT) {
-                        Log.i(TAG, "Adding core app " + sortedPkgs.size() + ": " + pkg.packageName);
-                    }
-                    sortedPkgs.add(pkg);
-                    it.remove();
-                }
-            }
-            // Give priority to system apps that listen for pre boot complete.
-            Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
-            ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
-            for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
-                PackageParser.Package pkg = it.next();
-                if (pkgNames.contains(pkg.packageName)) {
-                    if (DEBUG_DEXOPT) {
-                        Log.i(TAG, "Adding pre boot system app " + sortedPkgs.size() + ": " + pkg.packageName);
-                    }
-                    sortedPkgs.add(pkg);
-                    it.remove();
-                }
-            }
-            // Filter out packages that aren't recently used.
-            filterRecentlyUsedApps(pkgs);
-            // Add all remaining apps.
-            for (PackageParser.Package pkg : pkgs) {
-                if (DEBUG_DEXOPT) {
-                    Log.i(TAG, "Adding app " + sortedPkgs.size() + ": " + pkg.packageName);
-                }
-                sortedPkgs.add(pkg);
-            }
-
-            // If we want to be lazy, filter everything that wasn't recently used.
-            if (mLazyDexOpt) {
-                filterRecentlyUsedApps(sortedPkgs);
-            }
-
-            int i = 0;
-            int total = sortedPkgs.size();
-            File dataDir = Environment.getDataDirectory();
-            long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
-            if (lowThreshold == 0) {
-                throw new IllegalStateException("Invalid low memory threshold");
-            }
-            for (PackageParser.Package pkg : sortedPkgs) {
-                long usableSpace = dataDir.getUsableSpace();
-                if (usableSpace < lowThreshold) {
-                    Log.w(TAG, "Not running dexopt on remaining apps due to low memory: " + usableSpace);
-                    break;
-                }
-                performBootDexOpt(pkg, ++i, total);
-            }
-        }
-    }
-
-    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs) {
-        // Filter out packages that aren't recently used.
-        //
-        // The exception is first boot of a non-eng device (aka !mLazyDexOpt), which
-        // should do a full dexopt.
-        if (mLazyDexOpt || (!isFirstBoot() && mPackageUsage.isHistoricalPackageUsageAvailable())) {
-            int total = pkgs.size();
-            int skipped = 0;
-            long now = System.currentTimeMillis();
-            for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
-                PackageParser.Package pkg = i.next();
-                long then = pkg.mLastPackageUsageTimeInMills;
-                if (then + mDexOptLRUThresholdInMills < now) {
-                    if (DEBUG_DEXOPT) {
-                        Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
-                              ((then == 0) ? "never" : new Date(then)));
-                    }
-                    i.remove();
-                    skipped++;
-                }
-            }
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
-            }
-        }
     }
 
     private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
@@ -6299,54 +6394,36 @@
         return pkgNames;
     }
 
-    private void performBootDexOpt(PackageParser.Package pkg, int curr, int total) {
-        if (DEBUG_DEXOPT) {
-            Log.i(TAG, "Optimizing app " + curr + " of " + total + ": " + pkg.packageName);
-        }
-        if (!isFirstBoot()) {
-            try {
-                ActivityManagerNative.getDefault().showBootMessage(
-                        mContext.getResources().getString(R.string.android_upgrading_apk,
-                                curr, total), true);
-            } catch (RemoteException e) {
+    @Override
+    public void notifyPackageUse(String packageName) {
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (p == null) {
+                return;
             }
-        }
-        PackageParser.Package p = pkg;
-        synchronized (mInstallLock) {
-            mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */,
-                    false /* force dex */, false /* defer */, true /* include dependencies */,
-                    false /* boot complete */, false /*useJit*/);
+            p.mLastPackageUsageTimeInMills = System.currentTimeMillis();
         }
     }
 
     @Override
     public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
-        return performDexOptTraced(packageName, instructionSet, false);
+        return performDexOptTraced(packageName, instructionSet);
     }
 
-    public boolean performDexOpt(
-            String packageName, String instructionSet, boolean backgroundDexopt) {
-        return performDexOptTraced(packageName, instructionSet, backgroundDexopt);
+    public boolean performDexOpt(String packageName, String instructionSet) {
+        return performDexOptTraced(packageName, instructionSet);
     }
 
-    private boolean performDexOptTraced(
-            String packageName, String instructionSet, boolean backgroundDexopt) {
+    private boolean performDexOptTraced(String packageName, String instructionSet) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
         try {
-            return performDexOptInternal(packageName, instructionSet, backgroundDexopt);
+            return performDexOptInternal(packageName, instructionSet);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
-    private boolean performDexOptInternal(
-            String packageName, String instructionSet, boolean backgroundDexopt) {
-        boolean dexopt = mLazyDexOpt || backgroundDexopt;
-        boolean updateUsage = !backgroundDexopt;  // Don't update usage if this is just a backgroundDexopt
-        if (!dexopt && !updateUsage) {
-            // We aren't going to dexopt or update usage, so bail early.
-            return false;
-        }
+    private boolean performDexOptInternal(String packageName, String instructionSet) {
         PackageParser.Package p;
         final String targetInstructionSet;
         synchronized (mPackages) {
@@ -6354,14 +6431,7 @@
             if (p == null) {
                 return false;
             }
-            if (updateUsage) {
-                p.mLastPackageUsageTimeInMills = System.currentTimeMillis();
-            }
             mPackageUsage.write(false);
-            if (!dexopt) {
-                // We aren't going to dexopt, so bail early.
-                return false;
-            }
 
             targetInstructionSet = instructionSet != null ? instructionSet :
                     getPrimaryInstructionSet(p.applicationInfo);
@@ -6374,8 +6444,7 @@
             synchronized (mInstallLock) {
                 final String[] instructionSets = new String[] { targetInstructionSet };
                 int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
-                        false /* forceDex */, false /* defer */, true /* inclDependencies */,
-                        true /* boot complete */, false /*useJit*/);
+                        true /* inclDependencies */);
                 return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
             }
         } finally {
@@ -6425,8 +6494,7 @@
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
             final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
-                    true /*forceDex*/, false /* defer */, true /* inclDependencies */,
-                    true /* boot complete */, false /*useJit*/);
+                    true /* inclDependencies */);
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -6450,22 +6518,25 @@
         return true;
     }
 
-    private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
-        int[] users = sUserManager.getUserIds();
+    private void createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo)
+            throws PackageManagerException {
         int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
-        if (res < 0) {
-            return res;
+        if (res != 0) {
+            throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                    "Failed to install " + packageName + ": " + res);
         }
+
+        final int[] users = sUserManager.getUserIds();
         for (int user : users) {
             if (user != 0) {
                 res = mInstaller.createUserData(volumeUuid, packageName,
                         UserHandle.getUid(user, uid), user, seinfo);
-                if (res < 0) {
-                    return res;
+                if (res != 0) {
+                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                            "Failed to createUserData " + packageName + ": " + res);
                 }
             }
         }
-        return res;
     }
 
     private int removeDataDirsLI(String volumeUuid, String packageName) {
@@ -7035,18 +7106,6 @@
                                     + pkg.applicationInfo.uid + "; old data erased";
                             reportSettingsProblem(Log.WARN, msg);
                             recovered = true;
-
-                            // And now re-install the app.
-                            ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
-                                    pkg.applicationInfo.seinfo);
-                            if (ret == -1) {
-                                // Ack should not happen!
-                                msg = prefix + pkg.packageName
-                                        + " could not have data directory re-created after delete.";
-                                reportSettingsProblem(Log.WARN, msg);
-                                throw new PackageManagerException(
-                                        INSTALL_FAILED_INSUFFICIENT_STORAGE, msg);
-                            }
                         }
                         if (!recovered) {
                             mHasSystemUidErrors = true;
@@ -7079,6 +7138,10 @@
                     }
                 }
 
+                // Ensure that directories are prepared
+                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+                        pkg.applicationInfo.seinfo);
+
                 if (mShouldRestoreconData) {
                     Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
                     mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName,
@@ -7089,14 +7152,8 @@
                     if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
                         Log.v(TAG, "Want this data dir: " + dataPath);
                 }
-                //invoke installer to do the actual installation
-                int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
                         pkg.applicationInfo.seinfo);
-                if (ret < 0) {
-                    // Error from installer
-                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
-                            "Unable to create data dirs [errorCode=" + ret + "]");
-                }
             }
 
             // Get all of our default paths setup
@@ -7216,7 +7273,7 @@
                     " secondary=" + pkg.applicationInfo.secondaryCpuAbi);
         }
 
-        if ((scanFlags&SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
+        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
             // We don't do this here during boot because we can do it all
             // at once after scanning all existing packages.
             //
@@ -7224,21 +7281,9 @@
             // we can avoid redundant dexopts, and also to make sure we've got the
             // code and package path correct.
             adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
-                    pkg, forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, true /* boot complete */);
+                    pkg, true /* boot complete */);
         }
 
-        if ((scanFlags & SCAN_NO_DEX) == 0) {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
-            int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */,
-                    forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */,
-                    (scanFlags & SCAN_BOOTING) == 0, false /*useJit*/);
-
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
-                throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI");
-            }
-        }
         if (mFactoryTest && pkg.requestedPermissions.contains(
                 android.Manifest.permission.FACTORY_TEST)) {
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
@@ -7290,7 +7335,7 @@
                                     + name + " that is not declared on system image; skipping");
                         }
                     }
-                    if ((scanFlags&SCAN_BOOTING) == 0) {
+                    if ((scanFlags & SCAN_BOOTING) == 0) {
                         // If we are not booting, we need to update any applications
                         // that are clients of our shared library.  If we are booting,
                         // this will all be done once the scan is complete.
@@ -7300,30 +7345,6 @@
             }
         }
 
-        // We also need to dexopt any apps that are dependent on this library.  Note that
-        // if these fail, we should abort the install since installing the library will
-        // result in some apps being broken.
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-        try {
-            if (clientLibPkgs != null) {
-                if ((scanFlags & SCAN_NO_DEX) == 0) {
-                    for (int i = 0; i < clientLibPkgs.size(); i++) {
-                        PackageParser.Package clientPkg = clientLibPkgs.get(i);
-                        int result = mPackageDexOptimizer.performDexOpt(clientPkg,
-                                null /* instruction sets */, forceDex,
-                                (scanFlags & SCAN_DEFER_DEX) != 0, false,
-                                (scanFlags & SCAN_BOOTING) == 0, false /*useJit*/);
-                        if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
-                            throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
-                                    "scanPackageLI failed to dexopt clientLibPkgs");
-                        }
-                    }
-                }
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -7870,8 +7891,7 @@
      * adds unnecessary complexity.
      */
     private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
-            PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt,
-            boolean bootComplete) {
+            PackageParser.Package scannedPackage, boolean bootComplete) {
         String requiredInstructionSet = null;
         if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
             requiredInstructionSet = VMRuntime.getInstructionSet(
@@ -7933,22 +7953,8 @@
                     if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                         ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
                         Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi);
-
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
-                        int result = mPackageDexOptimizer.performDexOpt(ps.pkg,
-                                null /* instruction sets */, forceDexOpt, deferDexOpt, true,
-                                bootComplete, false /*useJit*/);
-
-                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                        if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
-                            ps.primaryCpuAbiString = null;
-                            ps.pkg.applicationInfo.primaryCpuAbi = null;
-                            return;
-                        } else {
-                            mInstaller.rmdex(ps.codePathString,
-                                    getDexCodeInstructionSet(getPreferredInstructionSet()));
-                        }
+                        mInstaller.rmdex(ps.codePathString,
+                                getDexCodeInstructionSet(getPreferredInstructionSet()));
                     }
                 }
             }
@@ -7979,6 +7985,30 @@
         }
     }
 
+    private void setUpEphemeralInstallerActivityLP(ComponentName installerComponent) {
+        final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
+
+        // Set up information for ephemeral installer activity
+        mEphemeralInstallerActivity.applicationInfo = pkg.applicationInfo;
+        mEphemeralInstallerActivity.name = mEphemeralInstallerComponent.getClassName();
+        mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName;
+        mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName;
+        mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+        mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
+                ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+        mEphemeralInstallerActivity.theme = 0;
+        mEphemeralInstallerActivity.exported = true;
+        mEphemeralInstallerActivity.enabled = true;
+        mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity;
+        mEphemeralInstallerInfo.priority = 0;
+        mEphemeralInstallerInfo.preferredOrder = 0;
+        mEphemeralInstallerInfo.match = 0;
+
+        if (DEBUG_EPHEMERAL) {
+            Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent);
+        }
+    }
+
     private static String calculateBundledApkRoot(final String codePathString) {
         final File codePath = new File(codePathString);
         final File codeRoot;
@@ -9535,7 +9565,28 @@
         private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
                 = new ArrayMap<ComponentName, PackageParser.Provider>();
         private int mFlags;
-    };
+    }
+
+    private static final class EphemeralIntentResolver
+            extends IntentResolver<IntentFilter, ResolveInfo> {
+        @Override
+        protected IntentFilter[] newArray(int size) {
+            return new IntentFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, IntentFilter info) {
+            return true;
+        }
+
+        @Override
+        protected ResolveInfo newResult(IntentFilter info, int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            final ResolveInfo res = new ResolveInfo();
+            res.filter = info;
+            return res;
+        }
+    }
 
     private static final Comparator<ResolveInfo> mResolvePrioritySorter =
             new Comparator<ResolveInfo>() {
@@ -9576,8 +9627,8 @@
         }
     };
 
-    final void sendPackageBroadcast(final String action, final String pkg,
-            final Bundle extras, final String targetPkg, final IIntentReceiver finishedReceiver,
+    final void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
+            final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
             final int[] userIds) {
         mHandler.post(new Runnable() {
             @Override
@@ -9607,7 +9658,7 @@
                             intent.putExtra(Intent.EXTRA_UID, uid);
                         }
                         intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
-                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
                         if (DEBUG_BROADCASTS) {
                             RuntimeException here = new RuntimeException("here");
                             here.fillInStackTrace();
@@ -9799,7 +9850,7 @@
         extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
 
         sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                packageName, extras, null, null, new int[] {userId});
+                packageName, extras, 0, null, null, new int[] {userId});
         try {
             IActivityManager am = ActivityManagerNative.getDefault();
             final boolean isSystem =
@@ -12718,19 +12769,6 @@
                 res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
                 return;
             }
-
-            // Run dexopt before old package gets removed, to minimize time when app is unavailable
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
-            int result = mPackageDexOptimizer
-                    .performDexOpt(pkg, null /* instruction sets */, false /* forceDex */,
-                            false /* defer */, false /* inclDependencies */,
-                            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);
-                return;
-            }
         }
 
         if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
@@ -12980,7 +13018,7 @@
         final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
         final int[] users = deleteAllUsers ? sUserManager.getUserIds() : new int[]{ userId };
         if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) {
-            mContext.enforceCallingPermission(
+            mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     "deletePackage for user " + userId);
         }
@@ -13027,8 +13065,14 @@
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
         try {
             if (dpm != null) {
+                final ComponentName deviceOwnerComponentName = dpm.getDeviceOwner();
+                final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
+                        : deviceOwnerComponentName.getPackageName();
                 // Does the package contains the device owner?
-                if (dpm.isDeviceOwnerPackage(packageName)) {
+                // TODO Do we have to do it even if userId != UserHandle.USER_ALL?  Otherwise,
+                // this check is probably not needed, since DO should be registered as a device
+                // admin on some user too. (Original bug for this: b/17657954)
+                if (packageName.equals(deviceOwnerPackageName)) {
                     return true;
                 }
                 // Does it contain a device admin for any user?
@@ -13049,6 +13093,10 @@
         return false;
     }
 
+    private boolean shouldKeepUninstalledPackageLPr(String packageName) {
+        return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+    }
+
     /**
      *  This method is an internal method that could be get invoked either
      *  to delete an installed package or to clean up a failed installation.
@@ -13116,11 +13164,11 @@
                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
 
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, null, null, null);
+                        extras, 0, null, null, null);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                        extras, null, null, null);
+                        extras, 0, null, null, null);
                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
-                        null, packageName, null, null);
+                        null, 0, packageName, null, null);
             }
         }
         // Force a gc here.
@@ -13155,14 +13203,14 @@
             extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
             if (removedPackage != null) {
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null, removedUsers);
+                        extras, 0, null, null, removedUsers);
                 if (fullRemove && !replacing) {
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
-                            extras, null, null, removedUsers);
+                            extras, 0, null, null, removedUsers);
                 }
             }
             if (removedAppId >= 0) {
-                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, 0, null, null,
                         removedUsers);
             }
         }
@@ -13471,7 +13519,9 @@
                         false, // blockUninstall
                         ps.readUserState(userId).domainVerificationStatus, 0);
                 if (!isSystemApp(ps)) {
-                    if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+                    // Do not uninstall the APK if an app should be cached
+                    boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
+                    if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
                         // Other user still have this package installed, so all
                         // we need to do is clear this user's data and save that
                         // it is uninstalled.
@@ -14862,7 +14912,12 @@
         extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
         extras.putInt(Intent.EXTRA_UID, packageUid);
-        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, null, null,
+        // If this is not reporting a change of the overall package, then only send it
+        // to registered receivers.  We don't want to launch a swath of apps for every
+        // little component state change.
+        final int flags = !componentNames.contains(packageName)
+                ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, flags, null, null,
                 new int[] {UserHandle.getUserId(packageUid)});
     }
 
@@ -15050,20 +15105,23 @@
     static class DumpState {
         public static final int DUMP_LIBS = 1 << 0;
         public static final int DUMP_FEATURES = 1 << 1;
-        public static final int DUMP_RESOLVERS = 1 << 2;
-        public static final int DUMP_PERMISSIONS = 1 << 3;
-        public static final int DUMP_PACKAGES = 1 << 4;
-        public static final int DUMP_SHARED_USERS = 1 << 5;
-        public static final int DUMP_MESSAGES = 1 << 6;
-        public static final int DUMP_PROVIDERS = 1 << 7;
-        public static final int DUMP_VERIFIERS = 1 << 8;
-        public static final int DUMP_PREFERRED = 1 << 9;
-        public static final int DUMP_PREFERRED_XML = 1 << 10;
-        public static final int DUMP_KEYSETS = 1 << 11;
-        public static final int DUMP_VERSION = 1 << 12;
-        public static final int DUMP_INSTALLS = 1 << 13;
-        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14;
-        public static final int DUMP_DOMAIN_PREFERRED = 1 << 15;
+        public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+        public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+        public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+        public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+        public static final int DUMP_PERMISSIONS = 1 << 6;
+        public static final int DUMP_PACKAGES = 1 << 7;
+        public static final int DUMP_SHARED_USERS = 1 << 8;
+        public static final int DUMP_MESSAGES = 1 << 9;
+        public static final int DUMP_PROVIDERS = 1 << 10;
+        public static final int DUMP_VERIFIERS = 1 << 11;
+        public static final int DUMP_PREFERRED = 1 << 12;
+        public static final int DUMP_PREFERRED_XML = 1 << 13;
+        public static final int DUMP_KEYSETS = 1 << 14;
+        public static final int DUMP_VERSION = 1 << 15;
+        public static final int DUMP_INSTALLS = 1 << 16;
+        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+        public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
 
         public static final int OPTION_SHOW_FILTERS = 1 << 0;
 
@@ -15162,9 +15220,9 @@
                 pw.println("    -h: print this help");
                 pw.println("  cmd may be one of:");
                 pw.println("    l[ibraries]: list known shared libraries");
-                pw.println("    f[ibraries]: list device features");
+                pw.println("    f[eatures]: list device features");
                 pw.println("    k[eysets]: print known keysets");
-                pw.println("    r[esolvers]: dump intent resolvers");
+                pw.println("    r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
                 pw.println("    perm[issions]: dump permissions");
                 pw.println("    permission [name ...]: dump declaration and use of given permission");
                 pw.println("    pref[erred]: print preferred package settings");
@@ -15231,7 +15289,29 @@
             } else if ("f".equals(cmd) || "features".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_FEATURES);
             } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_RESOLVERS);
+                if (opti >= args.length) {
+                    dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
+                            | DumpState.DUMP_SERVICE_RESOLVERS
+                            | DumpState.DUMP_RECEIVER_RESOLVERS
+                            | DumpState.DUMP_CONTENT_RESOLVERS);
+                } else {
+                    while (opti < args.length) {
+                        String name = args[opti];
+                        if ("a".equals(name) || "activity".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
+                        } else if ("s".equals(name) || "service".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
+                        } else if ("r".equals(name) || "receiver".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
+                        } else if ("c".equals(name) || "content".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
+                        } else {
+                            pw.println("Error: unknown resolver table type: " + name);
+                            return;
+                        }
+                        opti++;
+                    }
+                }
             } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PERMISSIONS);
             } else if ("permission".equals(cmd)) {
@@ -15398,22 +15478,28 @@
                 }
             }
 
-            if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
                 if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
                         : "Activity Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
                 if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
                         : "Receiver Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
                 if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
                         : "Service Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
                 if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
                         : "Provider Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
@@ -15860,7 +15946,7 @@
             }
             String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                     : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
-            sendPackageBroadcast(action, null, extras, null, finishedReceiver, null);
+            sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null);
         }
     }
 
@@ -16177,13 +16263,14 @@
             }
         }
 
+        final StorageManager sm = mContext.getSystemService(StorageManager.class);
         final UserManager um = mContext.getSystemService(UserManager.class);
         for (UserInfo user : um.getUsers()) {
             final File userDir = Environment.getDataUserDirectory(volumeUuid, user.id);
             if (userDir.exists()) continue;
 
             try {
-                UserManagerService.prepareUserDirectory(mContext, volumeUuid, user.id);
+                sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber);
                 UserManagerService.enforceSerialNumber(userDir, user.serialNumber);
             } catch (IOException e) {
                 Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e);
@@ -16564,23 +16651,26 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
-        mDirtyUsers.remove(userHandle);
-        mSettings.removeUserLPw(userHandle);
-        mPendingBroadcasts.remove(userHandle);
-        if (mInstaller != null) {
-            // Technically, we shouldn't be doing this with the package lock
-            // held.  However, this is very rare, and there is already so much
-            // other disk I/O going on, that we'll let it slide for now.
-            final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                final String volumeUuid = vol.getFsUuid();
-                if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
-                mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+    void cleanUpUser(UserManagerService userManager, int userHandle) {
+        synchronized (mPackages) {
+            mDirtyUsers.remove(userHandle);
+            mUserNeedsBadging.delete(userHandle);
+            mSettings.removeUserLPw(userHandle);
+            mPendingBroadcasts.remove(userHandle);
+        }
+        synchronized (mInstallLock) {
+            if (mInstaller != null) {
+                final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                    final String volumeUuid = vol.getFsUuid();
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+                    mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+                }
+            }
+            synchronized (mPackages) {
+                removeUnusedPackagesLILPw(userManager, userHandle);
             }
         }
-        mUserNeedsBadging.delete(userHandle);
-        removeUnusedPackagesLILPw(userManager, userHandle);
     }
 
     /**
@@ -16605,15 +16695,21 @@
             if (DEBUG_CLEAN_APKS) {
                 Slog.i(TAG, "Checking package " + packageName);
             }
-            boolean keep = false;
-            for (int i = 0; i < users.length; i++) {
-                if (users[i] != userHandle && ps.getInstalled(users[i])) {
-                    keep = true;
-                    if (DEBUG_CLEAN_APKS) {
-                        Slog.i(TAG, "  Keeping package " + packageName + " for user "
-                                + users[i]);
+            boolean keep = shouldKeepUninstalledPackageLPr(packageName);
+            if (keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Keeping package " + packageName + " - requested by DO");
+                }
+            } else {
+                for (int i = 0; i < users.length; i++) {
+                    if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                        keep = true;
+                        if (DEBUG_CLEAN_APKS) {
+                            Slog.i(TAG, "  Keeping package " + packageName + " for user "
+                                    + users[i]);
+                        }
+                        break;
                     }
-                    break;
                 }
             }
             if (!keep) {
@@ -16630,12 +16726,18 @@
     }
 
     /** Called by UserManagerService */
-    void createNewUserLILPw(int userHandle) {
+    void createNewUser(int userHandle) {
         if (mInstaller != null) {
-            mInstaller.createUserConfig(userHandle);
-            mSettings.createNewUserLILPw(this, mInstaller, userHandle);
-            applyFactoryDefaultBrowserLPw(userHandle);
-            primeDomainVerificationsLPw(userHandle);
+            synchronized (mInstallLock) {
+                synchronized (mPackages) {
+                    mInstaller.createUserConfig(userHandle);
+                    mSettings.createNewUserLILPw(this, mInstaller, userHandle);
+                }
+            }
+            synchronized (mPackages) {
+                applyFactoryDefaultBrowserLPw(userHandle);
+                primeDomainVerificationsLPw(userHandle);
+            }
         }
     }
 
@@ -16809,24 +16911,20 @@
         }
     }
 
-    public void getUsageStatsIfNoPackageUsageInfo() {
-        if (!mPackageUsage.isHistoricalPackageUsageAvailable()) {
-            UsageStatsManager usm = (UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE);
-            if (usm == null) {
-                throw new IllegalStateException("UsageStatsManager must be initialized");
-            }
-            long now = System.currentTimeMillis();
-            Map<String, UsageStats> stats = usm.queryAndAggregateUsageStats(now - mDexOptLRUThresholdInMills, now);
-            for (Map.Entry<String, UsageStats> entry : stats.entrySet()) {
-                String packageName = entry.getKey();
-                PackageParser.Package pkg = mPackages.get(packageName);
-                if (pkg == null) {
-                    continue;
+    private void deletePackageIfUnusedLPr(final String packageName) {
+        PackageSetting ps = mSettings.mPackages.get(packageName);
+        if (ps == null) {
+            return;
+        }
+        if (!ps.isAnyInstalled(sUserManager.getUserIds())) {
+            // TODO Implement atomic delete if package is unused
+            // It is currently possible that the package will be deleted even if it is installed
+            // after this method returns.
+            mHandler.post(new Runnable() {
+                public void run() {
+                    deletePackageX(packageName, 0, PackageManager.DELETE_ALL_USERS);
                 }
-                UsageStats usage = entry.getValue();
-                pkg.mLastPackageUsageTimeInMills = usage.getLastTimeUsed();
-                mPackageUsage.mIsHistoricalPackageUsageAvailable = true;
-            }
+            });
         }
     }
 
@@ -17067,6 +17165,34 @@
                         packageName, userId);
             }
         }
+
+        @Override
+        public void setKeepUninstalledPackages(final List<String> packageList) {
+            Preconditions.checkNotNull(packageList);
+            List<String> removedFromList = null;
+            synchronized (mPackages) {
+                if (mKeepUninstalledPackages != null) {
+                    final int packagesCount = mKeepUninstalledPackages.size();
+                    for (int i = 0; i < packagesCount; i++) {
+                        String oldPackage = mKeepUninstalledPackages.get(i);
+                        if (packageList != null && packageList.contains(oldPackage)) {
+                            continue;
+                        }
+                        if (removedFromList == null) {
+                            removedFromList = new ArrayList<>();
+                        }
+                        removedFromList.add(oldPackage);
+                    }
+                }
+                mKeepUninstalledPackages = new ArrayList<>(packageList);
+                if (removedFromList != null) {
+                    final int removedCount = removedFromList.size();
+                    for (int i = 0; i < removedCount; i++) {
+                        deletePackageIfUnusedLPr(removedFromList.get(i));
+                    }
+                }
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index c259ac2..dbb5818 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1,33 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.ResolveInfo;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.text.TextUtils;
 
+import android.util.PrintWriterPrinter;
+import com.android.internal.util.SizedInputStream;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.WeakHashMap;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
 
 class PackageManagerShellCommand extends ShellCommand {
     final IPackageManager mInterface;
     final private WeakHashMap<String, Resources> mResourceCache =
             new WeakHashMap<String, Resources>();
+    int mTargetUser;
 
     PackageManagerShellCommand(PackageManagerService service) {
         mInterface = service;
@@ -42,8 +86,27 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch(cmd) {
+                case "install":
+                    return runInstall();
+                case "install-abandon":
+                case "install-destroy":
+                    return runInstallAbandon();
+                case "install-commit":
+                    return runInstallCommit();
+                case "install-create":
+                    return runInstallCreate();
+                case "install-write":
+                    return runInstallWrite();
                 case "list":
                     return runList();
+                case "uninstall":
+                    return runUninstall();
+                case "query-intent-activities":
+                    return runQueryIntentActivities();
+                case "query-intent-services":
+                    return runQueryIntentServices();
+                case "query-intent-receivers":
+                    return runQueryIntentReceivers();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -53,6 +116,65 @@
         return -1;
     }
 
+    private int runInstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams params = makeInstallParams();
+        final int sessionId = doCreateSession(params.sessionParams,
+                params.installerPackageName, params.userId);
+
+        final String inPath = getNextArg();
+        if (inPath == null && params.sessionParams.sizeBytes == 0) {
+            pw.println("Error: must either specify a package size or an APK file");
+            return 1;
+        }
+        if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk") != 0) {
+            return 1;
+        }
+        if (doCommitSession(sessionId) != 0) {
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runInstallAbandon() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doAbandonSession(sessionId);
+    }
+
+    private int runInstallCommit() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doCommitSession(sessionId);
+    }
+
+    private int runInstallCreate() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams installParams = makeInstallParams();
+        final int sessionId = doCreateSession(installParams.sessionParams,
+                installParams.installerPackageName, installParams.userId);
+
+        // NOTE: adb depends on parsing this string
+        pw.println("Success: created install session [" + sessionId + "]");
+        return 0;
+    }
+
+    private int runInstallWrite() throws RemoteException {
+        long sizeBytes = -1;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("-S")) {
+                sizeBytes = Long.parseLong(getNextArg());
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
+
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String splitName = getNextArg();
+        final String path = getNextArg();
+        return doWriteSession(sessionId, path, sizeBytes, splitName);
+    }
+
     private int runList() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         final String type = getNextArg();
@@ -228,7 +350,7 @@
                         listThirdParty = true;
                         break;
                     case "--user":
-                        userId = Integer.parseInt(getNextArg());
+                        userId = UserHandle.parseUserArg(getNextArgRequired());
                         break;
                     default:
                         pw.println("Error: Unknown option: " + opt);
@@ -363,6 +485,377 @@
         return 0;
     }
 
+    private int runUninstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int flags = 0;
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-k":
+                    flags |= PackageManager.DELETE_KEEP_DATA;
+                    break;
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        userId = translateUserId(userId, "runUninstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            flags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            final PackageInfo info = mInterface.getPackageInfo(packageName, 0, userId);
+            if (info == null) {
+                pw.println("Failure - not installed for " + userId);
+                return 1;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                flags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        mInterface.getPackageInstaller().uninstall(packageName, null /*callerPackageName*/, flags,
+                receiver.getIntentSender(), userId);
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
+    private Intent parseIntentAndUser() throws URISyntaxException {
+        mTargetUser = UserHandle.USER_CURRENT;
+        Intent intent = Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
+            @Override
+            public boolean handleOption(String opt, ShellCommand cmd) {
+                if ("--user".equals(opt)) {
+                    mTargetUser = UserHandle.parseUserArg(cmd.getNextArgRequired());
+                    return true;
+                }
+                return false;
+            }
+        });
+        mTargetUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), mTargetUser, false, false, null, null);
+        return intent;
+    }
+
+    private int runQueryIntentActivities() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentActivities(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No activities found");
+            } else {
+                pw.print(result.size()); pw.println(" activities found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Activity #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
+    private int runQueryIntentServices() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentServices(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No services found");
+            } else {
+                pw.print(result.size()); pw.println(" services found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Service #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
+    private int runQueryIntentReceivers() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No receivers found");
+            } else {
+                pw.print(result.size()); pw.println(" receivers found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Receiver #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
+    private static class InstallParams {
+        SessionParams sessionParams;
+        String installerPackageName;
+        int userId = UserHandle.USER_ALL;
+    }
+
+    private InstallParams makeInstallParams() {
+        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+        final InstallParams params = new InstallParams();
+        params.sessionParams = sessionParams;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-l":
+                    sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+                    break;
+                case "-r":
+                    sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+                    break;
+                case "-i":
+                    params.installerPackageName = getNextArg();
+                    if (params.installerPackageName == null) {
+                        throw new IllegalArgumentException("Missing installer package");
+                    }
+                    break;
+                case "-t":
+                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+                    break;
+                case "-s":
+                    sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
+                    break;
+                case "-f":
+                    sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
+                    break;
+                case "-d":
+                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
+                    break;
+                case "-g":
+                    sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
+                    break;
+                case "--originating-uri":
+                    sessionParams.originatingUri = Uri.parse(getNextArg());
+                    break;
+                case "--referrer":
+                    sessionParams.referrerUri = Uri.parse(getNextArg());
+                    break;
+                case "-p":
+                    sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
+                    sessionParams.appPackageName = getNextArg();
+                    if (sessionParams.appPackageName == null) {
+                        throw new IllegalArgumentException("Missing inherit package name");
+                    }
+                    break;
+                case "-S":
+                    sessionParams.setSize(Long.parseLong(getNextArg()));
+                    break;
+                case "--abi":
+                    sessionParams.abiOverride = checkAbiArgument(getNextArg());
+                    break;
+                case "--user":
+                    params.userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--install-location":
+                    sessionParams.installLocation = Integer.parseInt(getNextArg());
+                    break;
+                case "--force-uuid":
+                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
+                    sessionParams.volumeUuid = getNextArg();
+                    if ("internal".equals(sessionParams.volumeUuid)) {
+                        sessionParams.volumeUuid = null;
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown option " + opt);
+            }
+        }
+        return params;
+    }
+
+    private static String checkAbiArgument(String abi) {
+        if (TextUtils.isEmpty(abi)) {
+            throw new IllegalArgumentException("Missing ABI argument");
+        }
+
+        if ("-".equals(abi)) {
+            return abi;
+        }
+
+        final String[] supportedAbis = Build.SUPPORTED_ABIS;
+        for (String supportedAbi : supportedAbis) {
+            if (supportedAbi.equals(abi)) {
+                return abi;
+            }
+        }
+
+        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
+    }
+
+    private int translateUserId(int userId, String logContext) {
+        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, logContext, "pm command");
+    }
+
+    private int doCreateSession(SessionParams params, String installerPackageName, int userId)
+            throws RemoteException {
+        userId = translateUserId(userId, "runInstallCreate");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
+        }
+
+        final int sessionId = mInterface.getPackageInstaller()
+                .createSession(params, installerPackageName, userId);
+        return sessionId;
+    }
+
+    private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName)
+            throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        if ("-".equals(inPath)) {
+            inPath = null;
+        } else if (inPath != null) {
+            final File file = new File(inPath);
+            if (file.isFile()) {
+                sizeBytes = file.length();
+            }
+        }
+
+        final SessionInfo info = mInterface.getPackageInstaller().getSessionInfo(sessionId);
+
+        PackageInstaller.Session session = null;
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+
+            if (inPath != null) {
+                in = new FileInputStream(inPath);
+            } else {
+                in = new SizedInputStream(getInputStream(), sizeBytes);
+            }
+            out = session.openWrite(splitName, 0, sizeBytes);
+
+            int total = 0;
+            byte[] buffer = new byte[65536];
+            int c;
+            while ((c = in.read(buffer)) != -1) {
+                total += c;
+                out.write(buffer, 0, c);
+
+                if (info.sizeBytes > 0) {
+                    final float fraction = ((float) c / (float) info.sizeBytes);
+                    session.addProgress(fraction);
+                }
+            }
+            session.fsync(out);
+
+            pw.println("Success: streamed " + total + " bytes");
+            return 0;
+        } catch (IOException e) {
+            pw.println("Error: failed to write; " + e.getMessage());
+            return 1;
+        } finally {
+            IoUtils.closeQuietly(out);
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private int doCommitSession(int sessionId) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+
+            final LocalIntentReceiver receiver = new LocalIntentReceiver();
+            session.commit(receiver.getIntentSender());
+
+            final Intent result = receiver.getResult();
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                pw.println("Success");
+            } else {
+                pw.println("Failure ["
+                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+                pw.println("Failure details: " + result.getExtras());
+            }
+            return status;
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    private int doAbandonSession(int sessionId) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+            session.abandon();
+            pw.println("Success");
+            return 0;
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
     private void doListPermissions(ArrayList<String> groupList, boolean groups, boolean labels,
             boolean summary, int startProtectionLevel, int endProtectionLevel)
                     throws RemoteException {
@@ -507,7 +1000,7 @@
         pw.println("    the text in FILTER.");
         pw.println("    Options:");
         pw.println("      -f: see their associated file");
-        pw.println("      -d: filter to only show disbled packages");
+        pw.println("      -d: filter to only show disabled packages");
         pw.println("      -e: filter to only show enabled packages");
         pw.println("      -s: filter to only show system packages");
         pw.println("      -3: filter to only show third party packages");
@@ -523,7 +1016,42 @@
         pw.println("      -s: short summary");
         pw.println("      -d: only list dangerous permissions");
         pw.println("      -u: list only the permissions users will see");
-        pw.println("");
+        pw.println("  query-intent-activities [--user USER_ID] INTENT");
+        pw.println("    Prints all activities that can handle the given Intent.");
+        pw.println("  query-intent-services [--user USER_ID] INTENT");
+        pw.println("    Prints all services that can handle the given Intent.");
+        pw.println("  query-intent-receivers [--user USER_ID] INTENT");
+        pw.println("    Prints all broadcast receivers that can handle the given Intent.");
+        pw.println();
+        Intent.printIntentArgsHelp(pw , "");
+    }
+
+    private static class LocalIntentReceiver {
+        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public int send(int code, Intent intent, String resolvedType,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                return 0;
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 }
-
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 5d8b1d2..903d12b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -103,6 +103,9 @@
     // Append privapp to existing seinfo label
     private static final String PRIVILEGED_APP_STR = ":privapp";
 
+    // Append autoplay to existing seinfo label
+    private static final String AUTOPLAY_APP_STR = ":autoplayapp";
+
     /**
      * Load the mac_permissions.xml file containing all seinfo assignments used to
      * label apps. The loaded mac_permissions.xml file is determined by the
@@ -316,6 +319,9 @@
             }
         }
 
+        if (pkg.applicationInfo.isAutoPlayApp())
+            pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;
+
         if (pkg.applicationInfo.isPrivilegedApp())
             pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index de14739..99aa30b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3802,8 +3802,7 @@
         if ((flags & PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS) != 0) {
             return true;
         }
-        if ((flags & PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA) != 0) {
-            // When running with amnesia, we can only run encryption-aware apps
+        if ((flags & PackageManager.MATCH_ENCRYPTION_AWARE_ONLY) != 0) {
             return componentInfo.encryptionAware;
         }
         return true;
@@ -3861,7 +3860,7 @@
             if (pkgSetting.getNotLaunched(userId)) {
                 if (pkgSetting.installerPackageName != null) {
                     yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
-                            pkgSetting.name, null,
+                            pkgSetting.name, null, 0,
                             pkgSetting.installerPackageName, null, new int[] {userId});
                 }
                 pkgSetting.setNotLaunched(false, userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9a87abe..b3c40d3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,6 @@
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +35,7 @@
 import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -48,7 +48,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCommand;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -97,15 +96,15 @@
  *
  * Method naming convention:
  * <ul>
- * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
- * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LP" should be called within the {@link #mPackagesLock} lock.
  * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
  * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
  * </ul>
  */
 public class UserManagerService extends IUserManager.Stub {
     private static final String LOG_TAG = "UserManagerService";
-    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
     private static final String ATTR_FLAGS = "flags";
@@ -124,6 +123,7 @@
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
     private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String ATTR_KEY = "key";
@@ -164,7 +164,6 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
-    private final Object mInstallLock;
     private final Object mPackagesLock;
     // Short-term lock for internal state, when interaction/sync with PM is not required
     private final Object mUsersLock = new Object();
@@ -208,13 +207,28 @@
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
     /**
-     * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}.  We
-     * use it to detect restrictions that have changed since the last
-     * {@link #applyUserRestrictionsLR} call.
+     * User restrictions that have already been applied in
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)}.  We use it to detect restrictions
+     * that have changed since the last
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)} call.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
+     * including guests.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private Bundle mDevicePolicyGlobalUserRestrictions;
+
+    /**
+     * User restrictions set by {@link DevicePolicyManager} for each user.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
+
+    @GuardedBy("mGuestRestrictions")
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -226,6 +240,7 @@
 
     @GuardedBy("mUsersLock")
     private int[] mUserIds;
+    @GuardedBy("mPackagesLock")
     private int mNextSerialNumber;
     private int mUserVersion = 0;
 
@@ -245,11 +260,9 @@
         }
     }
 
-    /**
-     * Available for testing purposes.
-     */
-    UserManagerService(File dataDir, File baseUserPath) {
-        this(null, null, new Object(), new Object(), dataDir, baseUserPath);
+    @VisibleForTesting
+    UserManagerService(File dataDir) {
+        this(null, null, new Object(), dataDir);
     }
 
     /**
@@ -257,78 +270,61 @@
      * associated with the package manager, and the given lock is the
      * package manager's own lock.
      */
-    UserManagerService(Context context, PackageManagerService pm,
-            Object installLock, Object packagesLock) {
-        this(context, pm, installLock, packagesLock,
-                Environment.getDataDirectory(),
-                new File(Environment.getDataDirectory(), "user"));
+    UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
+        this(context, pm, packagesLock, Environment.getDataDirectory());
     }
 
-    /**
-     * Available for testing purposes.
-     */
     private UserManagerService(Context context, PackageManagerService pm,
-            Object installLock, Object packagesLock,
-            File dataDir, File baseUserPath) {
+            Object packagesLock, File dataDir) {
         mContext = context;
         mPm = pm;
-        mInstallLock = installLock;
         mPackagesLock = packagesLock;
         mHandler = new MainHandler();
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                mUsersDir = new File(dataDir, USER_INFO_DIR);
-                mUsersDir.mkdirs();
-                // Make zeroth user directory, for services to migrate their files to that location
-                File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
-                userZeroDir.mkdirs();
-                FileUtils.setPermissions(mUsersDir.toString(),
-                        FileUtils.S_IRWXU|FileUtils.S_IRWXG
-                        |FileUtils.S_IROTH|FileUtils.S_IXOTH,
-                        -1, -1);
-                mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
-                initDefaultGuestRestrictions();
-                readUserListLILP();
-                sInstance = this;
-            }
+        synchronized (mPackagesLock) {
+            mUsersDir = new File(dataDir, USER_INFO_DIR);
+            mUsersDir.mkdirs();
+            // Make zeroth user directory, for services to migrate their files to that location
+            File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
+            userZeroDir.mkdirs();
+            FileUtils.setPermissions(mUsersDir.toString(),
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+                    -1, -1);
+            mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+            initDefaultGuestRestrictions();
+            readUserListLP();
+            sInstance = this;
         }
         mLocalService = new LocalService();
         LocalServices.addService(UserManagerInternal.class, mLocalService);
     }
 
     void systemReady() {
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                synchronized (mUsersLock) {
-                    // Prune out any partially created/partially removed users.
-                    ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
-                    final int userSize = mUsers.size();
-                    for (int i = 0; i < userSize; i++) {
-                        UserInfo ui = mUsers.valueAt(i);
-                        if ((ui.partial || ui.guestToRemove) && i != 0) {
-                            partials.add(ui);
-                        }
-                    }
-                    final int partialsSize = partials.size();
-                    for (int i = 0; i < partialsSize; i++) {
-                        UserInfo ui = partials.get(i);
-                        Slog.w(LOG_TAG, "Removing partially created user " + ui.id
-                                + " (name=" + ui.name + ")");
-                        removeUserStateLILP(ui.id);
-                    }
+        // Prune out any partially created/partially removed users.
+        ArrayList<UserInfo> partials = new ArrayList<>();
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                UserInfo ui = mUsers.valueAt(i);
+                if ((ui.partial || ui.guestToRemove) && i != 0) {
+                    partials.add(ui);
                 }
             }
         }
+        final int partialsSize = partials.size();
+        for (int i = 0; i < partialsSize; i++) {
+            UserInfo ui = partials.get(i);
+            Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+                    + " (name=" + ui.name + ")");
+            removeUserState(ui.id);
+        }
+
         onUserForeground(UserHandle.USER_SYSTEM);
+
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        for (int i = 0; i < mUserIds.length; ++i) {
-            final int userId = mUserIds[i];
-            try {
-                mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-            }
+
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -339,7 +335,7 @@
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i);
-                if (ui.isPrimary()) {
+                if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) {
                     return ui;
                 }
             }
@@ -409,7 +405,7 @@
     @Override
     public int getCredentialOwnerProfile(int userHandle) {
         checkManageUsersPermission("get the credential owner");
-        if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (!StorageManager.isFileBasedEncryptionEnabled()) {
             synchronized (mUsersLock) {
                 UserInfo profileParent = getProfileParentLU(userHandle);
                 if (profileParent != null) {
@@ -627,17 +623,22 @@
 
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
-        synchronized (mPackagesLock) {
-            UserInfo info = getUserInfoNoChecks(userId);
+        boolean scheduleWriteUser = false;
+        UserInfo info;
+        synchronized (mUsersLock) {
+            info = mUsers.get(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
-                // TODO Check if we should return here instead of a null check below
+                return;
             }
-            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+            if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
                 info.flags |= UserInfo.FLAG_INITIALIZED;
-                scheduleWriteUser(info);
+                scheduleWriteUser = true;
             }
         }
+        if (scheduleWriteUser) {
+            scheduleWriteUser(info);
+        }
     }
 
     /**
@@ -645,17 +646,18 @@
      * restrictions.
      */
     private void initDefaultGuestRestrictions() {
-        if (mGuestRestrictions.isEmpty()) {
-            mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
-            mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+        synchronized (mGuestRestrictions) {
+            if (mGuestRestrictions.isEmpty()) {
+                mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+                mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+            }
         }
     }
 
     @Override
     public Bundle getDefaultGuestRestrictions() {
         checkManageUsersPermission("getDefaultGuestRestrictions");
-        // TODO Switch to mGuestRestrictions for locking
-        synchronized (mPackagesLock) {
+        synchronized (mGuestRestrictions) {
             return new Bundle(mGuestRestrictions);
         }
     }
@@ -663,28 +665,81 @@
     @Override
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         checkManageUsersPermission("setDefaultGuestRestrictions");
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                mGuestRestrictions.clear();
-                mGuestRestrictions.putAll(restrictions);
-                writeUserListLILP();
+        synchronized (mGuestRestrictions) {
+            mGuestRestrictions.clear();
+            mGuestRestrictions.putAll(restrictions);
+        }
+        synchronized (mPackagesLock) {
+            writeUserListLP();
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#setDevicePolicyUserRestrictions(int, Bundle, Bundle)}
+     */
+    void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle local,
+            @Nullable Bundle global) {
+        Preconditions.checkNotNull(local);
+        boolean globalChanged = false;
+        boolean localChanged;
+        synchronized (mRestrictionsLock) {
+            if (global != null) {
+                // Update global.
+                globalChanged = !UserRestrictionsUtils.areEqual(
+                        mDevicePolicyGlobalUserRestrictions, global);
+                if (globalChanged) {
+                    mDevicePolicyGlobalUserRestrictions = global;
+                }
+            }
+            {
+                // Update local.
+                final Bundle prev = mDevicePolicyLocalUserRestrictions.get(userId);
+                localChanged = !UserRestrictionsUtils.areEqual(prev, local);
+                if (localChanged) {
+                    mDevicePolicyLocalUserRestrictions.put(userId, local);
+                }
+            }
+        }
+        if (DBG) {
+            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
+                            + " global=" + global + (globalChanged ? " (changed)" : "")
+                            + " local=" + local + (localChanged ? " (changed)" : "")
+            );
+        }
+        // Don't call them within the mRestrictionsLock.
+        synchronized (mPackagesLock) {
+            if (globalChanged) {
+                writeUserListLP();
+            }
+            if (localChanged) {
+                writeUserLP(getUserInfoNoChecks(userId));
+            }
+        }
+
+        synchronized (mRestrictionsLock) {
+            if (globalChanged) {
+                applyUserRestrictionsForAllUsersLR();
+            } else if (localChanged) {
+                applyUserRestrictionsLR(userId);
             }
         }
     }
 
     @GuardedBy("mRestrictionsLock")
     private Bundle computeEffectiveUserRestrictionsLR(int userId) {
-        final DevicePolicyManagerInternal dpmi =
-                LocalServices.getService(DevicePolicyManagerInternal.class);
-        final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+        final Bundle baseRestrictions =
+                UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
+        final Bundle global = mDevicePolicyGlobalUserRestrictions;
+        final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
 
-        final Bundle effective;
-        if (dpmi == null) {
-            // TODO Make sure it's because DPMS is disabled and not because we called it too early.
-            effective = systemRestrictions;
-        } else {
-            effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+        if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
+            // Common case first.
+            return baseRestrictions;
         }
+        final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
+        UserRestrictionsUtils.merge(effective, global);
+        UserRestrictionsUtils.merge(effective, local);
+
         return effective;
     }
 
@@ -720,14 +775,13 @@
      */
     @Override
     public Bundle getUserRestrictions(int userId) {
-        Bundle restrictions = getEffectiveUserRestrictions(userId);
-        return restrictions != null ? new Bundle(restrictions) : new Bundle();
+        return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+        if (!UserRestrictionsUtils.isSystemControlled(key)) {
             setUserRestrictionNoCheck(key, value, userId);
         }
     }
@@ -742,8 +796,8 @@
         synchronized (mRestrictionsLock) {
             // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
             // a copy.
-            final Bundle newRestrictions = new Bundle();
-            UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+            final Bundle newRestrictions = UserRestrictionsUtils.clone(
+                    mBaseUserRestrictions.get(userId));
             newRestrictions.putBoolean(key, value);
 
             updateUserRestrictionsInternalLR(newRestrictions, userId);
@@ -751,75 +805,70 @@
     }
 
     /**
-     * Optionally updating user restrictions, calculate the effective user restrictions by
-     * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
-     * apply it to {@link com.android.server.AppOpsService}.
-     * TODO applyUserRestrictionsLocked() should also apply to system settings.
+     * Optionally updating user restrictions, calculate the effective user restrictions and also
+     * propagate to other services and system settings.
      *
-     * @param newRestrictions User restrictions to set.  If null, only the effective restrictions
-     *     will be updated.  Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
-     *     or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+     * @param newRestrictions User restrictions to set.
+     *      If null, will not update user restrictions and only does the propagation.
      * @param userId target user ID.
      */
     @GuardedBy("mRestrictionsLock")
     private void updateUserRestrictionsInternalLR(
             @Nullable Bundle newRestrictions, int userId) {
-        if (DBG) {
-            Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
-                    + " bundle=" + newRestrictions);
-        }
-        // Update system restrictions.
+
+        final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
+                mAppliedUserRestrictions.get(userId));
+
+        // Update base restrictions.
         if (newRestrictions != null) {
             // If newRestrictions == the current one, it's probably a bug.
-            Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+            final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
+
+            Preconditions.checkState(prevBaseRestrictions != newRestrictions);
             Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
                     != newRestrictions);
-            mBaseUserRestrictions.put(userId, newRestrictions);
-            scheduleWriteUser(mUsers.get(userId));
+
+            if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
+                mBaseUserRestrictions.put(userId, newRestrictions);
+                scheduleWriteUser(getUserInfoNoChecks(userId));
+            }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
         mCachedEffectiveUserRestrictions.put(userId, effective);
 
-        applyUserRestrictionsLR(userId, effective);
-    }
-
-    @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
-        if (newRestrictions == null) {
-            newRestrictions = Bundle.EMPTY;
-        }
-
-        Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
-        if (prevRestrictions == null) {
-            prevRestrictions = Bundle.EMPTY;
-        }
-
+        // Apply the new restrictions.
         if (DBG) {
-            Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
-                    + " new=" + newRestrictions + " prev=" + prevRestrictions);
+            debug("Applying user restrictions: userId=" + userId
+                    + " new=" + effective + " prev=" + prevAppliedRestrictions);
         }
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mAppOpsService.setUserRestrictions(newRestrictions, userId);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-        } finally {
-            Binder.restoreCallingIdentity(token);
+        if (mAppOpsService != null) { // We skip it until system-ready.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppOpsService.setUserRestrictions(effective, userId);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
-        UserRestrictionsUtils.applyUserRestrictionsLR(
-                mContext, userId, newRestrictions, prevRestrictions);
+        propagateUserRestrictionsLR(userId, effective, prevAppliedRestrictions);
 
-        notifyUserRestrictionsListeners(userId, newRestrictions, prevRestrictions);
-
-        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
+        mAppliedUserRestrictions.put(userId, new Bundle(effective));
     }
 
-    private void notifyUserRestrictionsListeners(final int userId,
+    private void propagateUserRestrictionsLR(final int userId,
             Bundle newRestrictions, Bundle prevRestrictions) {
+        // Note this method doesn't touch any state, meaning it doesn't require mRestrictionsLock
+        // actually, but we still need some kind of synchronization otherwise we might end up
+        // calling listeners out-of-order, thus "LR".
+
+        if (UserRestrictionsUtils.areEqual(newRestrictions, prevRestrictions)) {
+            return;
+        }
 
         final Bundle newRestrictionsFinal = new Bundle(newRestrictions);
         final Bundle prevRestrictionsFinal = new Bundle(prevRestrictions);
@@ -827,6 +876,11 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
+                synchronized (mRestrictionsLock) {
+                    UserRestrictionsUtils.applyUserRestrictionsLR(
+                            mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
+                }
+
                 final UserRestrictionsListener[] listeners;
                 synchronized (mUserRestrictionsListeners) {
                     listeners = new UserRestrictionsListener[mUserRestrictionsListeners.size()];
@@ -840,13 +894,17 @@
         });
     }
 
-    @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsLR(int userId) {
+    // Package private for the inner class.
+    void applyUserRestrictionsLR(int userId) {
         updateUserRestrictionsInternalLR(null, userId);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsForAllUsersLR() {
+    // Package private for the inner class.
+    void applyUserRestrictionsForAllUsersLR() {
+        if (DBG) {
+            debug("applyUserRestrictionsForAllUsersLR");
+        }
         // First, invalidate all cached values.
         mCachedEffectiveUserRestrictions.clear();
 
@@ -867,10 +925,9 @@
                 // It's okay if a new user has started after the getRunningUserIds() call,
                 // because we'll do the same thing (re-calculate the restrictions and apply)
                 // when we start a user.
-                // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
                 synchronized (mRestrictionsLock) {
                     for (int i = 0; i < runningUsers.length; i++) {
-                        updateUserRestrictionsInternalLR(null, runningUsers[i]);
+                        applyUserRestrictionsLR(runningUsers[i]);
                     }
                 }
             }
@@ -996,9 +1053,9 @@
         }
     }
 
-    private void readUserListLILP() {
+    private void readUserListLP() {
         if (!mUserListFile.exists()) {
-            fallbackToSingleUserLILP();
+            fallbackToSingleUserLP();
             return;
         }
         FileInputStream fis = null;
@@ -1015,7 +1072,7 @@
 
             if (type != XmlPullParser.START_TAG) {
                 Slog.e(LOG_TAG, "Unable to read user list");
-                fallbackToSingleUserLILP();
+                fallbackToSingleUserLP();
                 return;
             }
 
@@ -1031,12 +1088,14 @@
                 }
             }
 
+            final Bundle newDevicePolicyGlobalUserRestrictions = new Bundle();
+
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 if (type == XmlPullParser.START_TAG) {
                     final String name = parser.getName();
                     if (name.equals(TAG_USER)) {
                         String id = parser.getAttributeValue(null, ATTR_ID);
-                        UserInfo user = readUserLILP(Integer.parseInt(id));
+                        UserInfo user = readUserLP(Integer.parseInt(id));
 
                         if (user != null) {
                             synchronized (mUsersLock) {
@@ -1051,8 +1110,14 @@
                                 && type != XmlPullParser.END_TAG) {
                             if (type == XmlPullParser.START_TAG) {
                                 if (parser.getName().equals(TAG_RESTRICTIONS)) {
-                                    UserRestrictionsUtils
-                                            .readRestrictions(parser, mGuestRestrictions);
+                                    synchronized (mGuestRestrictions) {
+                                        UserRestrictionsUtils
+                                                .readRestrictions(parser, mGuestRestrictions);
+                                    }
+                                } else if (parser.getName().equals(TAG_DEVICE_POLICY_RESTRICTIONS)
+                                        ) {
+                                    UserRestrictionsUtils.readRestrictions(parser,
+                                            newDevicePolicyGlobalUserRestrictions);
                                 }
                                 break;
                             }
@@ -1060,26 +1125,23 @@
                     }
                 }
             }
-            updateUserIds();
-            upgradeIfNecessaryLILP();
-        } catch (IOException ioe) {
-            fallbackToSingleUserLILP();
-        } catch (XmlPullParserException pe) {
-            fallbackToSingleUserLILP();
-        } finally {
-            if (fis != null) {
-                try {
-                    fis.close();
-                } catch (IOException e) {
-                }
+            synchronized (mRestrictionsLock) {
+                mDevicePolicyGlobalUserRestrictions = newDevicePolicyGlobalUserRestrictions;
             }
+            updateUserIds();
+            upgradeIfNecessaryLP();
+        } catch (IOException | XmlPullParserException e) {
+            fallbackToSingleUserLP();
+        } finally {
+            IoUtils.closeQuietly(fis);
         }
     }
 
     /**
      * Upgrade steps between versions, either for fixing bugs or changing the data format.
      */
-    private void upgradeIfNecessaryLILP() {
+    private void upgradeIfNecessaryLP() {
+        final int originalVersion = mUserVersion;
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
@@ -1132,11 +1194,14 @@
                     + USER_VERSION);
         } else {
             mUserVersion = userVersion;
-            writeUserListLILP();
+
+            if (originalVersion < mUserVersion) {
+                writeUserListLP();
+            }
         }
     }
 
-    private void fallbackToSingleUserLILP() {
+    private void fallbackToSingleUserLP() {
         int flags = UserInfo.FLAG_INITIALIZED;
         // In split system user mode, the admin and primary flags are assigned to the first human
         // user.
@@ -1161,11 +1226,14 @@
         updateUserIds();
         initDefaultGuestRestrictions();
 
-        writeUserListLILP();
+        writeUserListLP();
         writeUserLP(system);
     }
 
     private void scheduleWriteUser(UserInfo userInfo) {
+        if (DBG) {
+            debug("scheduleWriteUser");
+        }
         // No need to wrap it within a lock -- worst case, we'll just post the same message
         // twice.
         if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
@@ -1182,6 +1250,9 @@
      * </user>
      */
     private void writeUserLP(UserInfo userInfo) {
+        if (DBG) {
+            debug("writeUserLP " + userInfo);
+        }
         FileOutputStream fos = null;
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
         try {
@@ -1221,13 +1292,12 @@
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
-            Bundle restrictions;
             synchronized (mRestrictionsLock) {
-                restrictions = mBaseUserRestrictions.get(userInfo.id);
-            }
-            if (restrictions != null) {
-                UserRestrictionsUtils
-                        .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+                        TAG_DEVICE_POLICY_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1247,8 +1317,10 @@
      *   <user id="2"></user>
      * </users>
      */
-    private void writeUserListLILP() {
-        // TODO Investigate removing a dependency on mInstallLock
+    private void writeUserListLP() {
+        if (DBG) {
+            debug("writeUserList");
+        }
         FileOutputStream fos = null;
         AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
@@ -1266,9 +1338,15 @@
             serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
 
             serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-            UserRestrictionsUtils
-                    .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+            synchronized (mGuestRestrictions) {
+                UserRestrictionsUtils
+                        .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+            }
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyGlobalUserRestrictions, TAG_DEVICE_POLICY_RESTRICTIONS);
+            }
             int[] userIdsToWrite;
             synchronized (mUsersLock) {
                 userIdsToWrite = new int[mUsers.size()];
@@ -1293,7 +1371,7 @@
         }
     }
 
-    private UserInfo readUserLILP(int id) {
+    private UserInfo readUserLP(int id) {
         int flags = 0;
         int serialNumber = id;
         String name = null;
@@ -1304,7 +1382,8 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean guestToRemove = false;
-        Bundle restrictions = new Bundle();
+        Bundle baseRestrictions = new Bundle();
+        Bundle localRestrictions = new Bundle();
 
         FileInputStream fis = null;
         try {
@@ -1361,7 +1440,9 @@
                             name = parser.getText();
                         }
                     } else if (TAG_RESTRICTIONS.equals(tag)) {
-                        UserRestrictionsUtils.readRestrictions(parser, restrictions);
+                        UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+                    } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+                        UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
                     }
                 }
             }
@@ -1375,7 +1456,8 @@
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
             synchronized (mRestrictionsLock) {
-                mBaseUserRestrictions.append(id, restrictions);
+                mBaseUserRestrictions.put(id, baseRestrictions);
+                mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
             }
             return userInfo;
 
@@ -1468,8 +1550,7 @@
     }
 
     private UserInfo createUserInternal(String name, int flags, int parentId) {
-        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
-                UserManager.DISALLOW_ADD_USER, false)) {
+        if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
             Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
             return null;
         }
@@ -1480,118 +1561,114 @@
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
         final long ident = Binder.clearCallingIdentity();
-        UserInfo userInfo = null;
+        UserInfo userInfo;
         final int userId;
         try {
-            synchronized (mInstallLock) {
-                synchronized (mPackagesLock) {
-                    UserInfo parent = null;
-                    if (parentId != UserHandle.USER_NULL) {
-                        synchronized (mUsersLock) {
-                            parent = getUserInfoLU(parentId);
-                        }
-                        if (parent == null) return null;
+            synchronized (mPackagesLock) {
+                UserInfo parent = null;
+                if (parentId != UserHandle.USER_NULL) {
+                    synchronized (mUsersLock) {
+                        parent = getUserInfoLU(parentId);
                     }
-                    if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
-                        Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+                    if (parent == null) return null;
+                }
+                if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
+                    Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+                    return null;
+                }
+                if (!isGuest && !isManagedProfile && isUserLimitReached()) {
+                    // If we're not adding a guest user or a managed profile and the limit has
+                    // been reached, cannot add a user.
+                    return null;
+                }
+                // If we're adding a guest and there already exists one, bail.
+                if (isGuest && findCurrentGuestUser() != null) {
+                    return null;
+                }
+                // In legacy mode, restricted profile's parent can only be the owner user
+                if (isRestricted && !UserManager.isSplitSystemUser()
+                        && (parentId != UserHandle.USER_SYSTEM)) {
+                    Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+                    return null;
+                }
+                if (isRestricted && UserManager.isSplitSystemUser()) {
+                    if (parent == null) {
+                        Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+                                + "specified");
                         return null;
                     }
-                    if (!isGuest && !isManagedProfile && isUserLimitReached()) {
-                        // If we're not adding a guest user or a managed profile and the limit has
-                        // been reached, cannot add a user.
+                    if (!parent.canHaveProfile()) {
+                        Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+                                + "created for the specified parent user id " + parentId);
                         return null;
                     }
-                    // If we're adding a guest and there already exists one, bail.
-                    if (isGuest && findCurrentGuestUser() != null) {
-                        return null;
+                }
+                // In split system user mode, we assign the first human user the primary flag.
+                // And if there is no device owner, we also assign the admin flag to primary user.
+                if (UserManager.isSplitSystemUser()
+                        && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
+                    flags |= UserInfo.FLAG_PRIMARY;
+                    DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+                            mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                    if (devicePolicyManager == null
+                            || devicePolicyManager.getDeviceOwner() == null) {
+                        flags |= UserInfo.FLAG_ADMIN;
                     }
-                    // In legacy mode, restricted profile's parent can only be the owner user
-                    if (isRestricted && !UserManager.isSplitSystemUser()
-                            && (parentId != UserHandle.USER_SYSTEM)) {
-                        Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
-                        return null;
-                    }
-                    if (isRestricted && UserManager.isSplitSystemUser()) {
-                        if (parent == null) {
-                            Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
-                                    + "specified");
-                            return null;
-                        }
-                        if (!parent.canHaveProfile()) {
-                            Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
-                                    + "created for the specified parent user id " + parentId);
-                            return null;
-                        }
-                    }
-                    // In split system user mode, we assign the first human user the primary flag.
-                    // And if there is no device owner, we also assign the admin flag to primary
-                    // user.
-                    if (UserManager.isSplitSystemUser()
-                            && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
-                        flags |= UserInfo.FLAG_PRIMARY;
-                        DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
-                                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-                        if (devicePolicyManager == null
-                                || devicePolicyManager.getDeviceOwner() == null) {
-                            flags |= UserInfo.FLAG_ADMIN;
-                        }
-                    }
-                    userId = getNextAvailableId();
-                    userInfo = new UserInfo(userId, name, null, flags);
-                    userInfo.serialNumber = mNextSerialNumber++;
-                    long now = System.currentTimeMillis();
-                    userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
-                    userInfo.partial = true;
-                    Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+                }
+                userId = getNextAvailableId();
+                userInfo = new UserInfo(userId, name, null, flags);
+                userInfo.serialNumber = mNextSerialNumber++;
+                long now = System.currentTimeMillis();
+                userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+                userInfo.partial = true;
+                Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+                synchronized (mUsersLock) {
                     mUsers.put(userId, userInfo);
-                    writeUserListLILP();
-                    if (parent != null) {
-                        if (isManagedProfile) {
-                            if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
-                                parent.profileGroupId = parent.id;
-                                scheduleWriteUser(parent);
-                            }
-                            userInfo.profileGroupId = parent.profileGroupId;
-                        } else if (isRestricted) {
-                            if (!parent.canHaveProfile()) {
-                                Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
-                            }
-                            if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
-                                parent.restrictedProfileParentId = parent.id;
-                                scheduleWriteUser(parent);
-                            }
-                            userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
+                }
+                writeUserListLP();
+                if (parent != null) {
+                    if (isManagedProfile) {
+                        if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.profileGroupId = parent.id;
+                            writeUserLP(parent);
                         }
-                    }
-                    final StorageManager storage = mContext.getSystemService(StorageManager.class);
-                    for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                        final String volumeUuid = vol.getFsUuid();
-                        try {
-                            final File userDir = Environment.getDataUserDirectory(volumeUuid,
-                                    userId);
-                            prepareUserDirectory(mContext, volumeUuid, userId);
-                            enforceSerialNumber(userDir, userInfo.serialNumber);
-                        } catch (IOException e) {
-                            Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+                        userInfo.profileGroupId = parent.profileGroupId;
+                    } else if (isRestricted) {
+                        if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.restrictedProfileParentId = parent.id;
+                            writeUserLP(parent);
                         }
-                    }
-                    mPm.createNewUserLILPw(userId);
-                    userInfo.partial = false;
-                    scheduleWriteUser(userInfo);
-                    updateUserIds();
-                    Bundle restrictions = new Bundle();
-                    synchronized (mRestrictionsLock) {
-                        mBaseUserRestrictions.append(userId, restrictions);
+                        userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
                     }
                 }
             }
-            mPm.newUserCreated(userId);
-            if (userInfo != null) {
-                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
-                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
-                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
-                        android.Manifest.permission.MANAGE_USERS);
+            final StorageManager storage = mContext.getSystemService(StorageManager.class);
+            storage.createUserKey(userId, userInfo.serialNumber);
+            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                final String volumeUuid = vol.getFsUuid();
+                try {
+                    final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
+                    storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
+                    enforceSerialNumber(userDir, userInfo.serialNumber);
+                } catch (IOException e) {
+                    Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+                }
             }
+            mPm.createNewUser(userId);
+            userInfo.partial = false;
+            synchronized (mPackagesLock) {
+                writeUserLP(userInfo);
+            }
+            updateUserIds();
+            Bundle restrictions = new Bundle();
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.append(userId, restrictions);
+            }
+            mPm.newUserCreated(userId);
+            Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+                    android.Manifest.permission.MANAGE_USERS);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1780,11 +1857,7 @@
                                     // Clean up any ActivityManager state
                                     LocalServices.getService(ActivityManagerInternal.class)
                                             .onUserRemoved(userHandle);
-                                    synchronized (mInstallLock) {
-                                        synchronized (mPackagesLock) {
-                                            removeUserStateLILP(userHandle);
-                                        }
-                                    }
+                                    removeUserState(userHandle);
                                 }
                             }.start();
                         }
@@ -1796,11 +1869,10 @@
         }
     }
 
-    private void removeUserStateLILP(final int userHandle) {
-        mContext.getSystemService(StorageManager.class)
-            .deleteUserKey(userHandle);
+    private void removeUserState(final int userHandle) {
+        mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
         // Cleanup package manager settings
-        mPm.cleanUpUserLILPw(this, userHandle);
+        mPm.cleanUpUser(this, userHandle);
 
         // Remove this user from the list
         synchronized (mUsersLock) {
@@ -1810,7 +1882,9 @@
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
         userFile.delete();
         // Update the user list
-        writeUserListLILP();
+        synchronized (mPackagesLock) {
+            writeUserListLP();
+        }
         updateUserIds();
         removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
     }
@@ -2141,21 +2215,28 @@
     }
 
     /**
+     * Called right before a user starts.  This will not be called for the system user.
+     */
+    public void onBeforeStartUser(int userId) {
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(userId);
+        }
+    }
+
+    /**
      * Make a note of the last started time of a user and do some cleanup.
      * @param userId the user that was just foregrounded
      */
     public void onUserForeground(int userId) {
-        synchronized (mPackagesLock) {
-            UserInfo user = getUserInfoNoChecks(userId);
-            long now = System.currentTimeMillis();
-            if (user == null || user.partial) {
-                Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
-                return;
-            }
-            if (now > EPOCH_PLUS_30_YEARS) {
-                user.lastLoggedInTime = now;
-                scheduleWriteUser(user);
-            }
+        UserInfo user = getUserInfoNoChecks(userId);
+        if (user == null || user.partial) {
+            Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
+            return;
+        }
+        long now = System.currentTimeMillis();
+        if (now > EPOCH_PLUS_30_YEARS) {
+            user.lastLoggedInTime = now;
+            scheduleWriteUser(user);
         }
     }
 
@@ -2163,7 +2244,6 @@
      * Returns the next available user id, filling in any holes in the ids.
      * TODO: May not be a good idea to recycle ids, in case it results in confusion
      * for data and battery stats collection, or unexpected cross-talk.
-     * @return
      */
     private int getNextAvailableId() {
         synchronized (mUsersLock) {
@@ -2183,16 +2263,6 @@
     }
 
     /**
-     * Create new {@code /data/user/[id]} directory and sets default
-     * permissions.
-     */
-    public static void prepareUserDirectory(Context context, String volumeUuid, int userId) {
-        final StorageManager storage = context.getSystemService(StorageManager.class);
-        final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
-        storage.createNewUserDir(userId, userDir);
-    }
-
-    /**
      * Enforce that serial number stored in user directory inode matches the
      * given expected value. Gracefully sets the serial number if currently
      * undefined.
@@ -2349,15 +2419,26 @@
                     synchronized (mRestrictionsLock) {
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mBaseUserRestrictions.get(user.id));
+                        pw.println("    Device policy local restrictions:");
+                        UserRestrictionsUtils.dumpRestrictions(
+                                pw, "      ", mDevicePolicyLocalUserRestrictions.get(user.id));
                         pw.println("    Effective restrictions:");
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
                     }
+                    pw.println();
                 }
             }
+            pw.println("  Device policy global restrictions:");
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils
+                        .dumpRestrictions(pw, "    ", mDevicePolicyGlobalUserRestrictions);
+            }
             pw.println();
-            pw.println("Guest restrictions:");
-            UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
+            pw.println("  Guest restrictions:");
+            synchronized (mGuestRestrictions) {
+                UserRestrictionsUtils.dumpRestrictions(pw, "    ", mGuestRestrictions);
+            }
         }
     }
 
@@ -2388,22 +2469,11 @@
     }
 
     private class LocalService extends UserManagerInternal {
-
         @Override
-        public Object getUserRestrictionsLock() {
-            return mRestrictionsLock;
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsLR(int userId) {
-            UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsForAllUsersLR() {
-            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
+        public void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle localRestrictions,
+                @Nullable Bundle globalRestrictions) {
+            UserManagerService.this.setDevicePolicyUserRestrictions(userId, localRestrictions,
+                    globalRestrictions);
         }
 
         @Override
@@ -2468,4 +2538,9 @@
             pw.println("    Prints all users on the system.");
         }
     }
+
+    private static void debug(String message) {
+        Log.d(LOG_TAG, message +
+                (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, "  ") : ""));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 56e8b3e..129cbd3 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,6 +18,10 @@
 
 import com.google.android.collect.Sets;
 
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
@@ -26,6 +30,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -79,22 +84,64 @@
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
             UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_CAMERA,
     };
 
     /**
      * 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);
+            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);
+    private 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 {
+    /**
+     * User restrictions that can not be set by profile owners.
+     */
+    private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_USB_FILE_TRANSFER,
+            UserManager.DISALLOW_CONFIG_TETHERING,
+            UserManager.DISALLOW_NETWORK_RESET,
+            UserManager.DISALLOW_FACTORY_RESET,
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+            UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+            UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_FUN,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.DISALLOW_CREATE_WINDOWS
+    );
+
+    /**
+     * User restrictions that can't be changed by device owner or profile owner.
+     */
+    private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_WALLPAPER
+    );
+
+    /**
+     * Special user restrictions that can be applied to a user as well as to all users globally,
+     * depending on callers.  When device owner sets them, they'll be applied to all users.
+     */
+    private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADJUST_VOLUME,
+            UserManager.DISALLOW_UNMUTE_MICROPHONE
+    );
+
+    public static void writeRestrictions(@NonNull XmlSerializer serializer,
+            @Nullable Bundle restrictions, @NonNull String tag) throws IOException {
+        if (restrictions == null) {
+            return;
+        }
+
         serializer.startTag(null, tag);
         for (String key : USER_RESTRICTIONS) {
             if (restrictions.getBoolean(key)
@@ -115,7 +162,31 @@
         }
     }
 
-    public static void merge(Bundle dest, Bundle in) {
+    /**
+     * @return {@code in} itself when it's not null, or an empty bundle (which can writable).
+     */
+    public static Bundle nonNull(@Nullable Bundle in) {
+        return in != null ? in : new Bundle();
+    }
+
+    public static boolean isEmpty(@Nullable Bundle in) {
+        return (in == null) || (in.size() == 0);
+    }
+
+    /**
+     * Creates a copy of the {@code in} Bundle.  If {@code in} is null, it'll return an empty
+     * bundle.
+     *
+     * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
+     * {@link Bundle#EMPTY})
+     */
+    public static @NonNull Bundle clone(@Nullable Bundle in) {
+        return (in != null) ? new Bundle(in) : new Bundle();
+    }
+
+    public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
+        Preconditions.checkNotNull(dest);
+        Preconditions.checkArgument(dest != in);
         if (in == null) {
             return;
         }
@@ -127,6 +198,77 @@
     }
 
     /**
+     * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
+     * {@link UserManager#setUserRestriction}.
+     */
+    public static boolean isSystemControlled(String restriction) {
+        return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by device owner.
+     */
+    public static boolean canDeviceOwnerChange(String restriction) {
+        return !IMMUTABLE_BY_OWNERS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by profile owner.
+     */
+    public static boolean canProfileOwnerChange(String restriction) {
+        return !(IMMUTABLE_BY_OWNERS.contains(restriction)
+                || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction));
+    }
+
+    /**
+     * Takes restrictions that can be set by device owner, and sort them into what should be applied
+     * globally and what should be applied only on the current user.
+     */
+    public static void sortToGlobalAndLocal(@Nullable Bundle in, @NonNull Bundle global,
+            @NonNull Bundle local) {
+        if (in == null || in.size() == 0) {
+            return;
+        }
+        for (String key : in.keySet()) {
+            if (!in.getBoolean(key)) {
+                continue;
+            }
+            if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)) {
+                global.putBoolean(key, true);
+            } else {
+                local.putBoolean(key, true);
+            }
+        }
+    }
+
+    /**
+     * @return true if two Bundles contain the same user restriction.
+     * A null bundle and an empty bundle are considered to be equal.
+     */
+    public static boolean areEqual(@Nullable Bundle a, @Nullable Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (isEmpty(a)) {
+            return isEmpty(b);
+        }
+        if (isEmpty(b)) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        for (String key : b.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Takes a new use restriction set and the previous set, and apply the restrictions that have
      * changed.
      *
@@ -145,7 +287,7 @@
             }
         }
     }
-
+    
     /**
      * Apply each user restriction.
      *
@@ -155,6 +297,10 @@
      */
     private static void applyUserRestrictionLR(Context context, int userId, String key,
             boolean newValue) {
+        if (UserManagerService.DBG) {
+            Log.d(TAG, "Applying user restriction: userId=" + userId
+                    + " key=" + key + " value=" + newValue);
+        }
         // When certain restrictions are cleared, we don't update the system settings,
         // because these settings are changeable on the Settings UI and we don't know the original
         // value -- for example LOCATION_MODE might have been off already when the restriction was
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 121ef21..ae6874f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5318,11 +5318,9 @@
     }
 
     private boolean shouldDispatchInputWhenNonInteractive() {
-        if (mDisplay == null || mDisplay.getState() == Display.STATE_OFF) {
-            return false;
-        }
-        // Send events to keyguard while the screen is on and it's showing.
-        if (isKeyguardShowingAndNotOccluded()) {
+        // Send events to keyguard while the screen is on.
+        if (isKeyguardShowingAndNotOccluded() && mDisplay != null
+                && mDisplay.getState() != Display.STATE_OFF) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 8fc979c..cc25c8c 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -17,6 +17,7 @@
 package com.android.server.updates;
 
 import com.android.server.EventLogTags;
+import com.android.internal.util.HexDump;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -155,7 +156,7 @@
         try {
             MessageDigest dgst = MessageDigest.getInstance("SHA512");
             byte[] fingerprint = dgst.digest(content);
-            return IntegralToString.bytesToHexString(fingerprint, false);
+            return HexDump.toHexString(fingerprint, false);
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError(e);
         }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ac79b36..97713fc 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -54,6 +54,15 @@
                 @Override
                 public void onReceive(Context context, Intent intent) {
 
+                    // When a package is replaced we will receive two intents, one representing the
+                    // removal of the old package and one representing the addition of the new
+                    // package. We here ignore the intent representing the removed package to make
+                    // sure we don't change WebView provider twice.
+                    if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
+                            && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
+                        return;
+                    }
+
                     for (String packageName : WebViewFactory.getWebViewPackageNames()) {
                         String webviewPackage = "package:" + packageName;
 
@@ -73,7 +82,8 @@
                 }
         };
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addDataScheme("package");
         getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index d713751..c246609 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -409,6 +409,7 @@
 
             private final Region mMagnifiedBounds = new Region();
             private final Region mOldMagnifiedBounds = new Region();
+            private final Region mOldAvailableBounds = new Region();
 
             private final Path mCircularPath;
 
@@ -537,29 +538,39 @@
                         screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
                         Region.Op.INTERSECT);
 
-                if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
-                    Region bounds = Region.obtain();
-                    bounds.set(magnifiedBounds);
-                    mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
-                            bounds).sendToTarget();
+                final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds);
+                final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds);
+                if (magnifiedChanged || availableChanged) {
+                    if (magnifiedChanged) {
+                        mWindow.setBounds(magnifiedBounds);
+                        Rect dirtyRect = mTempRect1;
+                        if (mFullRedrawNeeded) {
+                            mFullRedrawNeeded = false;
+                            dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+                                    screenWidth - mDrawBorderInset,
+                                    screenHeight - mDrawBorderInset);
+                            mWindow.invalidate(dirtyRect);
+                        } else {
+                            Region dirtyRegion = mTempRegion3;
+                            dirtyRegion.set(magnifiedBounds);
+                            dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+                            dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+                            dirtyRegion.getBounds(dirtyRect);
+                            mWindow.invalidate(dirtyRect);
+                        }
 
-                    mWindow.setBounds(magnifiedBounds);
-                    Rect dirtyRect = mTempRect1;
-                    if (mFullRedrawNeeded) {
-                        mFullRedrawNeeded = false;
-                        dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
-                                screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset);
-                        mWindow.invalidate(dirtyRect);
-                    } else {
-                        Region dirtyRegion = mTempRegion3;
-                        dirtyRegion.set(magnifiedBounds);
-                        dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
-                        dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
-                        dirtyRegion.getBounds(dirtyRect);
-                        mWindow.invalidate(dirtyRect);
+                        mOldMagnifiedBounds.set(magnifiedBounds);
                     }
 
-                    mOldMagnifiedBounds.set(magnifiedBounds);
+                    if (availableChanged) {
+                        mOldAvailableBounds.set(availableBounds);
+                    }
+
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = Region.obtain(magnifiedBounds);
+                    args.arg2 = Region.obtain(availableBounds);
+                    mHandler.obtainMessage(
+                            MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
                 }
             }
 
@@ -867,9 +878,12 @@
             public void handleMessage(Message message) {
                 switch (message.what) {
                     case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
-                        Region bounds = (Region) message.obj;
-                        mCallbacks.onMagnifedBoundsChanged(bounds);
-                        bounds.recycle();
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region magnifiedBounds = (Region) args.arg1;
+                        final Region availableBounds = (Region) args.arg2;
+                        mCallbacks.onMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+                        magnifiedBounds.recycle();
+                        availableBounds.recycle();
                     } break;
 
                     case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 68034c8..9a88c60 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -139,7 +139,7 @@
     private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
-    private final Handler mH;
+    private final WindowManagerService mService;
 
     private int mNextAppTransition = TRANSIT_UNSET;
 
@@ -164,6 +164,7 @@
     // Used for thumbnail transitions. True if we're scaling up, false if scaling down
     private boolean mNextAppTransitionScaleUp;
     private IRemoteCallback mNextAppTransitionCallback;
+    private IRemoteCallback mNextAppTransitionFutureCallback;
     private IRemoteCallback mAnimationFinishedCallback;
     private int mNextAppTransitionEnter;
     private int mNextAppTransitionExit;
@@ -207,15 +208,10 @@
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
-    private final Object mServiceLock;
-    private final WindowSurfacePlacer mWindowSurfacePlacer;
 
-    AppTransition(Context context, Handler h, Object serviceLock,
-            WindowSurfacePlacer windowSurfacePlacer) {
+    AppTransition(Context context, WindowManagerService service) {
         mContext = context;
-        mH = h;
-        mServiceLock = serviceLock;
-        mWindowSurfacePlacer = windowSurfacePlacer;
+        mService = service;
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -295,6 +291,9 @@
 
     Bitmap getAppTransitionThumbnailHeader(int taskId) {
         AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null) {
+            spec = mDefaultNextAppTransitionAnimationSpec;
+        }
         return spec != null ? spec.bitmap : null;
     }
 
@@ -545,6 +544,9 @@
 
     void getNextAppTransitionStartRect(int taskId, Rect rect) {
         AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null) {
+            spec = mDefaultNextAppTransitionAnimationSpec;
+        }
         if (spec == null || spec.rect == null) {
             Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available",
                     new Throwable());
@@ -554,9 +556,10 @@
         }
     }
 
-    private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height) {
+    private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
+            Bitmap bitmap) {
         mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
-                null /* bitmap */, new Rect(left, top, left + width, top + height));
+                bitmap, new Rect(left, top, left + width, top + height));
     }
 
     private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
@@ -963,7 +966,7 @@
 
                 @Override
                 public void onAnimationEnd(Animation animation) {
-                    mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
+                    mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
                 }
 
                 @Override
@@ -1318,22 +1321,29 @@
 
     void postAnimationCallback() {
         if (mNextAppTransitionCallback != null) {
-            mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+            mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
+                    mNextAppTransitionCallback));
             mNextAppTransitionCallback = null;
         }
     }
 
+    private void clearAppTransitionState() {
+        mNextAppTransitionPackage = null;
+        mNextAppTransitionAnimationsSpecs.clear();
+        mDefaultNextAppTransitionAnimationSpec = null;
+        mAnimationFinishedCallback = null;
+    }
+
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
             IRemoteCallback startedCallback) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
             mNextAppTransitionPackage = packageName;
-            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-            mAnimationFinishedCallback = null;
         } else {
             postAnimationCallback();
         }
@@ -1342,40 +1352,34 @@
     void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
             int startHeight) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
-            mNextAppTransitionPackage = null;
-            mNextAppTransitionAnimationsSpecs.clear();
             putDefaultNextAppTransitionCoordinates(startX, startY, startX + startWidth,
-                    startY + startHeight);
+                    startY + startHeight, null);
             postAnimationCallback();
-            mNextAppTransitionCallback = null;
-            mAnimationFinishedCallback = null;
         }
     }
 
     void overridePendingAppTransitionClipReveal(int startX, int startY,
                                                 int startWidth, int startHeight) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
-            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight);
+            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
             postAnimationCallback();
-            mNextAppTransitionCallback = null;
-            mAnimationFinishedCallback = null;
         }
     }
 
     void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
                                            IRemoteCallback startedCallback, boolean scaleUp) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
-            mNextAppTransitionPackage = null;
-            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            putDefaultNextAppTransitionCoordinates(startX, startY, 0 ,0);
+            putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-            mAnimationFinishedCallback = null;
         } else {
             postAnimationCallback();
         }
@@ -1384,15 +1388,14 @@
     void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, int startY,
             int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
-            mNextAppTransitionPackage = null;
-            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight);
+            putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight,
+                    srcThumb);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-            mAnimationFinishedCallback = null;
         } else {
             postAnimationCallback();
         }
@@ -1402,22 +1405,22 @@
             IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
             boolean scaleUp) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
-            mNextAppTransitionPackage = null;
-            mDefaultNextAppTransitionAnimationSpec = null;
-            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            for (int i = 0; i < specs.length; i++) {
-                AppTransitionAnimationSpec spec = specs[i];
-                if (spec != null) {
-                    mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
-                    if (i == 0) {
-                        // In full screen mode, the transition code depends on the default spec to
-                        // be set.
-                        Rect rect = spec.rect;
-                        putDefaultNextAppTransitionCoordinates(rect.left, rect.top, rect.width(),
-                                rect.height());
+            if (specs != null) {
+                for (int i = 0; i < specs.length; i++) {
+                    AppTransitionAnimationSpec spec = specs[i];
+                    if (spec != null) {
+                        mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
+                        if (i == 0) {
+                            // In full screen mode, the transition code depends on the default spec
+                            // to be set.
+                            Rect rect = spec.rect;
+                            putDefaultNextAppTransitionCoordinates(rect.left, rect.top,
+                                    rect.width(), rect.height(), spec.bitmap);
+                        }
                     }
                 }
             }
@@ -1433,26 +1436,21 @@
             IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
             boolean scaleUp) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
-            mNextAppTransitionPackage = null;
-            mDefaultNextAppTransitionAnimationSpec = null;
-            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionAnimationsSpecsFuture = specsFuture;
             mNextAppTransitionScaleUp = scaleUp;
-            postAnimationCallback();
-            mNextAppTransitionCallback = callback;
-        } else {
-            postAnimationCallback();
+            mNextAppTransitionFutureCallback = callback;
         }
     }
 
     void overrideInPlaceAppTransition(String packageName, int anim) {
         if (isTransitionSet()) {
+            clearAppTransitionState();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
             mNextAppTransitionPackage = packageName;
             mNextAppTransitionInPlace = anim;
-            mAnimationFinishedCallback = null;
         } else {
             postAnimationCallback();
         }
@@ -1476,12 +1474,17 @@
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to fetch app transition specs: " + e);
                     }
-                    synchronized (mServiceLock) {
+                    synchronized (mService.mWindowMap) {
                         mNextAppTransitionAnimationsSpecsPending = false;
-                        overridePendingAppTransitionMultiThumb(specs, mNextAppTransitionCallback,
-                                null /* finishedCallback */, mNextAppTransitionScaleUp);
-                        mWindowSurfacePlacer.requestTraversal();
+                        overridePendingAppTransitionMultiThumb(specs,
+                                mNextAppTransitionFutureCallback, null /* finishedCallback */,
+                                mNextAppTransitionScaleUp);
+                        mNextAppTransitionFutureCallback = null;
+                        if (specs != null) {
+                            mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+                        }
                     }
+                    mService.requestTraversal();
                 }
             });
         }
@@ -1668,8 +1671,8 @@
         }
         boolean prepared = prepare();
         if (isTransitionSet()) {
-            mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
-            mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
+            mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
         }
         return prepared;
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2905269..b32ec2d 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -37,6 +37,10 @@
 public class AppWindowAnimator {
     static final String TAG = "AppWindowAnimator";
 
+    private static final int PROLONG_ANIMATION_DISABLED = 0;
+    static final int PROLONG_ANIMATION_AT_END = 1;
+    static final int PROLONG_ANIMATION_AT_START = 2;
+
     final AppWindowToken mAppToken;
     final WindowManagerService mService;
     final WindowAnimator mAnimator;
@@ -85,7 +89,7 @@
     // If true when the animation hits the last frame, it will keep running on that last frame.
     // This is used to synchronize animation with Recents and we wait for Recents to tell us to
     // finish or for a new animation be set as fail-safe mechanism.
-    private boolean mProlongAnimation;
+    private int mProlongAnimation;
     // Whether the prolong animation can be removed when animation is set. The purpose of this is
     // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
     // when new animation is set.
@@ -142,7 +146,7 @@
             anim.setBackgroundColor(0);
         }
         if (mClearProlongedAnimation) {
-            mProlongAnimation = false;
+            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
         } else {
             mClearProlongedAnimation = true;
         }
@@ -224,7 +228,8 @@
 
     private void stepThumbnailAnimation(long currentTime) {
         thumbnailTransformation.clear();
-        thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation);
+        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
+        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
         thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
 
         ScreenRotationAnimation screenRotationAnimation =
@@ -261,12 +266,26 @@
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
     }
 
+    /**
+     * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
+     * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
+     * and keep producing the first frame of the animation.
+     */
+    private long getAnimationFrameTime(Animation animation, long currentTime) {
+        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+            animation.setStartTime(currentTime);
+            return currentTime + 1;
+        }
+        return currentTime;
+    }
+
     private boolean stepAnimation(long currentTime) {
         if (animation == null) {
             return false;
         }
         transformation.clear();
-        boolean hasMoreFrames = animation.getTransformation(currentTime, transformation);
+        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
+        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
         if (!hasMoreFrames) {
             if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
                 // We are deferring the thumbnail destruction, so extend the animation for one more
@@ -278,14 +297,14 @@
                         "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
                         ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
                 deferFinalFrameCleanup = false;
-                if (mProlongAnimation) {
+                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
                     hasMoreFrames = true;
                 } else {
                     animation = null;
+                    clearThumbnail();
+                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
+                            + currentTime);
                 }
-                clearThumbnail();
-                if (DEBUG_ANIM) Slog.v(TAG,
-                        "Finished animation in " + mAppToken + " @ " + currentTime);
             }
         }
         hasTransformation = hasMoreFrames;
@@ -434,13 +453,13 @@
         }
     }
 
-    void startProlongAnimation() {
-        mProlongAnimation = true;
+    void startProlongAnimation(int prolongType) {
+        mProlongAnimation = prolongType;
         mClearProlongedAnimation = false;
     }
 
     void endProlongedAnimation() {
-        mProlongAnimation = false;
+        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
     }
 
     // This is an animation that does nothing: it just immediately finishes
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index f048da0..3f4eaac 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,11 +128,12 @@
     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;
+    // Whether the new window has replaced the old one, so the old one can be removed without
+    // blinking.
+    boolean mHasReplacedWindow;
 
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 85a12db..4b3620f 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -70,7 +70,7 @@
         /** Returns the display info. of the dim layer user. */
         DisplayInfo getDisplayInfo();
         /** Gets the bounds of the dim layer user. */
-        void getBounds(Rect outBounds);
+        void getDimBounds(Rect outBounds);
         String toShortString();
     }
     /** The user of this dim layer. */
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 921d27c..bd30bd5 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -43,7 +43,7 @@
 
     /** Updates the dim layer bounds, recreating it if needed. */
     void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
         final boolean previousFullscreen = state.dimLayer != null
                 && state.dimLayer == mSharedFullScreenDimLayer;
         DimLayer newDimLayer;
@@ -63,7 +63,7 @@
                     // Create new full screen dim layer.
                     newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId);
                 }
-                dimLayerUser.getBounds(mTmpBounds);
+                dimLayerUser.getDimBounds(mTmpBounds);
                 newDimLayer.setBounds(mTmpBounds);
                 mSharedFullScreenDimLayer = newDimLayer;
             } else if (state.dimLayer != null) {
@@ -73,14 +73,13 @@
             newDimLayer = (state.dimLayer == null || previousFullscreen)
                     ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId)
                     : state.dimLayer;
-            dimLayerUser.getBounds(mTmpBounds);
+            dimLayerUser.getDimBounds(mTmpBounds);
             newDimLayer.setBounds(mTmpBounds);
         }
         state.dimLayer = newDimLayer;
     }
 
-    private DimLayerState getOrCreateDimLayerState(
-            DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
+    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
                 + dimLayerUser.toShortString());
         DimLayerState state = mState.get(dimLayerUser);
@@ -88,7 +87,6 @@
             state = new DimLayerState();
             mState.put(dimLayerUser, state);
         }
-        state.dimAbove = aboveApp;
         return state;
     }
 
@@ -127,7 +125,8 @@
             WindowStateAnimator newWinAnimator, boolean aboveApp) {
         // Only set dim params on the highest dimmed layer.
         // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
+        state.dimAbove = aboveApp;
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " newWinAnimator=" + newWinAnimator
@@ -161,7 +160,7 @@
                 + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
         if (!state.continueDimming && state.dimLayer.isDimming()) {
             state.animator = null;
-            dimLayerUser.getBounds(mTmpBounds);
+            dimLayerUser.getDimBounds(mTmpBounds);
             state.dimLayer.setBounds(mTmpBounds);
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8c00a57..328c043 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -22,7 +22,6 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 
 import android.app.ActivityManager.StackId;
 import android.graphics.Rect;
@@ -55,17 +54,6 @@
      * from mDisplayWindows; */
     private final WindowList mWindows = new WindowList();
 
-    // This protects the following display size properties, so that
-    // getDisplaySize() doesn't need to acquire the global lock.  This is
-    // needed because the window manager sometimes needs to use ActivityThread
-    // while it has its global state locked (for example to load animation
-    // resources), but the ActivityThread also needs get the current display
-    // size sometimes when it has its package lock held.
-    //
-    // These will only be modified with both mWindowMap and mDisplaySizeLock
-    // held (in that order) so the window manager doesn't need to acquire this
-    // lock when needing these values in its normal operation.
-    final Object mDisplaySizeLock = new Object();
     int mInitialDisplayWidth = 0;
     int mInitialDisplayHeight = 0;
     int mInitialDisplayDensity = 0;
@@ -202,18 +190,16 @@
     }
 
     void initializeDisplayBaseInfo() {
-        synchronized(mDisplaySizeLock) {
-            // Bootstrap the default logical display from the display manager.
-            final DisplayInfo newDisplayInfo =
-                    mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
-            if (newDisplayInfo != null) {
-                mDisplayInfo.copyFrom(newDisplayInfo);
-            }
-            mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
-            mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
-            mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
-            mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+        // Bootstrap the default logical display from the display manager.
+        final DisplayInfo newDisplayInfo =
+                mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
+        if (newDisplayInfo != null) {
+            mDisplayInfo.copyFrom(newDisplayInfo);
         }
+        mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+        mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+        mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
     }
 
     void getLogicalDisplayRect(Rect out) {
@@ -291,16 +277,18 @@
             final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 final Task task = tasks.get(taskNdx);
-                // 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
+                final WindowState win = task.getTopVisibleAppMainWindow();
+                if (win == null) {
+                    continue;
+                }
+                // We need to use the task's dim bounds (which is derived from the visible
+                // bounds of its apps windows) for any touch-related tests. Can't use
+                // the task's original bounds because it 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;
-                    }
+                task.getDimBounds(mTmpRect);
+                if (mTmpRect.contains(x, y)) {
+                    return task.mTaskId;
                 }
             }
         }
@@ -308,10 +296,10 @@
     }
 
     /**
-     * Find the window whose outside touch area (for resizing) (x, y) falls within.
+     * Find the task whose outside touch area (for resizing) (x, y) falls within.
      * Returns null if the touch doesn't fall into a resizing area.
      */
-    WindowState findWindowForControlPoint(int x, int y) {
+    Task findTaskForControlPoint(int x, int y) {
         final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             TaskStack stack = mStacks.get(stackNdx);
@@ -325,24 +313,22 @@
                     return null;
                 }
 
-                // 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. (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.)
-                final WindowState win = task.getTopAppMainWindow();
-                if (win != null) {
-                    win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
-                    mTmpRect.inset(-delta, -delta);
-                    if (mTmpRect.contains(x, y)) {
-                        mTmpRect.inset(delta, delta);
-                        if (!mTmpRect.contains(x, y)) {
-                            return win;
-                        }
-                        // User touched inside the task. No need to look further,
-                        // focus transfer will be handled in ACTION_UP.
-                        return null;
+                // We need to use the task's dim bounds (which is derived from the visible
+                // bounds of its apps windows) for any touch-related tests. Can't use
+                // the task's original bounds because it might be adjusted to fit the
+                // content frame. One example is when the task is put to top-left quadrant,
+                // the actual visible area would not start at (0,0) after it's adjusted
+                // for the status bar.
+                task.getDimBounds(mTmpRect);
+                mTmpRect.inset(-delta, -delta);
+                if (mTmpRect.contains(x, y)) {
+                    mTmpRect.inset(delta, delta);
+                    if (!mTmpRect.contains(x, y)) {
+                        return task;
                     }
+                    // User touched inside the task. No need to look further,
+                    // focus transfer will be handled in ACTION_UP.
+                    return null;
                 }
             }
         }
@@ -351,12 +337,18 @@
 
     void setTouchExcludeRegion(Task focusedTask) {
         mTouchExcludeRegion.set(mBaseDisplayRect);
-        WindowList windows = getWindowList();
         final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-        for (int i = windows.size() - 1; i >= 0; --i) {
-            final WindowState win = windows.get(i);
-            final Task task = win.getTask();
-            if (win.isVisibleLw() && task != null) {
+        boolean addBackFocusedTask = false;
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            TaskStack stack = mStacks.get(stackNdx);
+            final ArrayList<Task> tasks = stack.getTasks();
+            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                final Task task = tasks.get(taskNdx);
+                final WindowState win = task.getTopVisibleAppMainWindow();
+                if (win == null) {
+                    continue;
+                }
+
                 /**
                  * Exclusion region is the region that TapDetector doesn't care about.
                  * Here we want to remove all non-focused tasks from the exclusion region.
@@ -368,13 +360,17 @@
                  */
                 final boolean isFreeformed = task.inFreeformWorkspace();
                 if (task != focusedTask || isFreeformed) {
-                    mTmpRect.set(win.mVisibleFrame);
-                    mTmpRect.intersect(win.mVisibleInsets);
-                    /**
-                     * If the task is freeformed, enlarge the area to account for outside
-                     * touch area for resize.
-                     */
+                    task.getDimBounds(mTmpRect);
                     if (isFreeformed) {
+                        // If we're removing a freeform, focused app from the exclusion region,
+                        // we need to add back its touchable frame later. Remember the touchable
+                        // frame now.
+                        if (task == focusedTask) {
+                            addBackFocusedTask = true;
+                            mTmpRect2.set(mTmpRect);
+                        }
+                        // If the task is freeformed, enlarge the area to account for outside
+                        // touch area for resize.
                         mTmpRect.inset(-delta, -delta);
                         // Intersect with display content rect. If we have system decor (status bar/
                         // navigation bar), we want to exclude that from the tap detection.
@@ -385,17 +381,14 @@
                     }
                     mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
                 }
-                /**
-                 * If we removed the focused task above, add it back and only leave its
-                 * outside touch area in the exclusion. TapDectector is not interested in
-                 * any touch inside the focused task itself.
-                 */
-                if (task == focusedTask && isFreeformed) {
-                    mTmpRect.inset(delta, delta);
-                    mTouchExcludeRegion.op(mTmpRect, Region.Op.UNION);
-                }
             }
         }
+        // If we removed the focused task above, add it back and only leave its
+        // outside touch area in the exclusion. TapDectector is not interested in
+        // any touch inside the focused task itself.
+        if (addBackFocusedTask) {
+            mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+        }
         if (mTapDetector != null) {
             mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
         }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 63b58b8..6b62467 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.util.Slog;
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -38,6 +39,7 @@
     private boolean mResizing;
     private WindowState mWindow;
     private final Rect mTmpRect = new Rect();
+    private final Rect mLastRect = new Rect();
 
     DockedStackDividerController(Context context, DisplayContent displayContent) {
         mDisplayContent = displayContent;
@@ -79,11 +81,15 @@
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
-            // divider was deferred to WM thread and hasn't happened yet.
+            // divider was deferred to WM thread and hasn't happened yet. In that case let's just
+            // keep putting it in the same place it was before the stack was removed to have
+            // continuity and prevent it from jumping to the center. It will get hidden soon.
+            frame.set(mLastRect);
             return;
+        } else {
+            stack.getDimBounds(mTmpRect);
         }
         int side = stack.getDockSide();
-        stack.getBounds(mTmpRect);
         switch (side) {
             case DOCKED_LEFT:
                 frame.set(mTmpRect.right - mDividerInsets, frame.top,
@@ -102,5 +108,6 @@
                         frame.right, mTmpRect.top + mDividerInsets);
                 break;
         }
+        mLastRect.set(frame);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 400cc5e..74750a9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,7 +16,14 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import android.graphics.Matrix;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
@@ -46,6 +53,8 @@
  * Drag/drop state
  */
 class DragState {
+    private static final long ANIMATION_DURATION_MS = 500;
+
     final WindowManagerService mService;
     IBinder mToken;
     SurfaceControl mSurfaceControl;
@@ -56,6 +65,8 @@
     ClipData mData;
     ClipDescription mDataDescription;
     boolean mDragResult;
+    float mOriginalAlpha;
+    float mOriginalX, mOriginalY;
     float mCurrentX, mCurrentY;
     float mThumbOffsetX, mThumbOffsetY;
     InputChannel mServerChannel, mClientChannel;
@@ -70,6 +81,10 @@
     private final Region mTmpRegion = new Region();
     private final Rect mTmpRect = new Rect();
 
+    private Animation mAnimation;
+    final Transformation mTransformation = new Transformation();
+    private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+
     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
             int flags, IBinder localWin) {
         mService = service;
@@ -185,6 +200,9 @@
     /* call out to each visible window/session informing it about the drag
      */
     void broadcastDragStartedLw(final float touchX, final float touchY) {
+        mOriginalX = mCurrentX = touchX;
+        mOriginalY = mCurrentY = touchY;
+
         // Cache a base-class instance of the clip metadata so that parceling
         // works correctly in calling out to the apps.
         mDataDescription = (mData != null) ? mData.getDescription() : null;
@@ -295,19 +313,32 @@
     }
 
     void endDragLw() {
-        mService.mDragState.broadcastDragEndedLw();
+        if (!mDragResult) {
+            mAnimation = createReturnAnimationLocked();
+            mService.scheduleAnimationLocked();
+            return;  // Will call cleanUpDragLw when the animation is done.
+        }
+        cleanUpDragLw();
+    }
+
+
+    void cleanUpDragLw() {
+        broadcastDragEndedLw();
 
         // stop intercepting input
-        mService.mDragState.unregister();
+        unregister();
 
         // free our resources and drop all the object references
-        mService.mDragState.reset();
+        reset();
         mService.mDragState = null;
 
         mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
     }
 
     void notifyMoveLw(float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+
         final int myPid = Process.myPid();
 
         // Move the surface to the given touch
@@ -379,6 +410,9 @@
     // result from the recipient.
     boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder,
             float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+
         if (touchedWin == null) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
@@ -435,7 +469,7 @@
                 continue;
             }
 
-            child.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
+            child.getVisibleBounds(mTmpRect);
             if (!mTmpRect.contains(x, y)) {
                 // outside of this window's activity stack == don't tell about drags
                 continue;
@@ -470,4 +504,38 @@
         return DragEvent.obtain(action, winX, winY, localState, description, data,
                 dropPermissionHolder, result);
     }
+
+    boolean stepAnimationLocked(long currentTimeMs) {
+        if (mAnimation == null) {
+            return false;
+        }
+
+        mTransformation.clear();
+        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
+            cleanUpDragLw();
+            return false;
+        }
+
+        final float tmpFloats[] = mService.mTmpFloats;
+        mTransformation.getMatrix().getValues(tmpFloats);
+        mSurfaceControl.setPosition(
+                tmpFloats[Matrix.MTRANS_X] - mThumbOffsetX,
+                tmpFloats[Matrix.MTRANS_Y] - mThumbOffsetY);
+        mSurfaceControl.setAlpha(mTransformation.getAlpha());
+        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+        return true;
+    }
+
+    private Animation createReturnAnimationLocked() {
+        final AnimationSet set = new AnimationSet(false);
+        set.addAnimation(new TranslateAnimation(
+                mCurrentX, mOriginalX, mCurrentY, mOriginalY));
+        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
+        set.setDuration(ANIMATION_DURATION_MS);
+        set.setInterpolator(mCubicEaseOutInterpolator);
+        set.initialize(0, 0, 0, 0);
+        set.start();  // Will start on the first call to getTransformation.
+        return set;
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 3c3123f..1f351cb 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -171,10 +171,10 @@
 
     private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
             final WindowState child, int flags, final int type, final boolean isVisible,
-            final boolean hasFocus, final boolean hasWallpaper, DisplayContent displayContent) {
+            final boolean hasFocus, final boolean hasWallpaper) {
         // Add a window to our list of input windows.
         inputWindowHandle.name = child.toString();
-        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags, this);
+        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags);
         inputWindowHandle.layoutParamsFlags = flags;
         inputWindowHandle.layoutParamsType = type;
         inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
@@ -308,8 +308,8 @@
                     mService.mDragState.sendDragStartedIfNeededLw(child);
                 }
 
-                addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus,
-                        hasWallpaper, displayContent);
+                addInputWindowHandleLw(
+                        inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c47c377..f23fcdb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -188,9 +188,10 @@
     }
 
     @Override
-    public void repositionChild(IWindow window, int x, int y, long deferTransactionUntilFrame,
-             Rect outFrame) {
-        mService.repositionChild(this, window, x, y, deferTransactionUntilFrame, outFrame);
+    public void repositionChild(IWindow window, int left, int top, int right, int bottom,
+             long deferTransactionUntilFrame, Rect outFrame) {
+        mService.repositionChild(this, window, left, top, right, bottom,
+                deferTransactionUntilFrame, outFrame);
     }
 
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
@@ -324,8 +325,6 @@
             }
 
             mService.mDragState.mData = data;
-            mService.mDragState.mCurrentX = touchX;
-            mService.mDragState.mCurrentY = touchY;
             mService.mDragState.broadcastDragStartedLw(touchX, touchY);
 
             // remember the thumb offsets for later
@@ -400,6 +399,34 @@
         }
     }
 
+    public void cancelDragAndDrop(IBinder dragToken) {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "cancelDragAndDrop");
+        }
+
+        synchronized (mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (mService.mDragState == null) {
+                    Slog.w(WindowManagerService.TAG, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+                }
+
+                if (mService.mDragState.mToken != dragToken) {
+                    Slog.w(WindowManagerService.TAG,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                }
+
+                mService.mDragState.mDragResult = false;
+                mService.mDragState.endDragLw();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     public void dragRecipientEntered(IWindow window) {
         if (WindowManagerService.DEBUG_DRAG) {
             Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6aaf3c7..46cd7cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -135,7 +135,7 @@
         stack.addTask(this, toTop);
     }
 
-    void positionTaskInStack(TaskStack stack, int position) {
+    void positionTaskInStack(TaskStack stack, int position, Rect bounds, Configuration config) {
         if (mStack != null && stack != mStack) {
             if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
                     + " from stack=" + mStack);
@@ -143,6 +143,7 @@
             mStack.removeTask(this);
         }
         stack.positionTask(this, position, showForAllUsers());
+        setBounds(bounds, config);
     }
 
     boolean removeAppToken(AppWindowToken wtoken) {
@@ -252,8 +253,7 @@
         return false;
     }
 
-    /** Bounds of the task with other system factors taken into consideration. */
-    @Override
+    /** Original bounds of the task if applicable, otherwise fullscreen rect. */
     public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
@@ -268,6 +268,70 @@
         mStack.getDisplayContent().getLogicalDisplayRect(out);
     }
 
+
+    /**
+     * Calculate the maximum visible area of this task. If the task has only one app,
+     * the result will be visible frame of that app. If the task has more than one apps,
+     * we search from top down if the next app got different visible area.
+     *
+     * This effort is to handle the case where some task (eg. GMail composer) might pop up
+     * a dialog that's different in size from the activity below, in which case we should
+     * be dimming the entire task area behind the dialog.
+     *
+     * @param out Rect containing the max visible bounds.
+     * @return true if the task has some visible app windows; false otherwise.
+     */
+    boolean getMaxVisibleBounds(Rect out) {
+        boolean foundTop = false;
+        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mAppTokens.get(i);
+            // skip hidden (or about to hide) apps
+            if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
+                continue;
+            }
+            final WindowState win = token.findMainWindow();
+            if (win == null) {
+                continue;
+            }
+            if (!foundTop) {
+                out.set(win.mVisibleFrame);
+                foundTop = true;
+                continue;
+            }
+            if (win.mVisibleFrame.left < out.left) {
+                out.left = win.mVisibleFrame.left;
+            }
+            if (win.mVisibleFrame.top < out.top) {
+                out.top = win.mVisibleFrame.top;
+            }
+            if (win.mVisibleFrame.right > out.right) {
+                out.right = win.mVisibleFrame.right;
+            }
+            if (win.mVisibleFrame.bottom > out.bottom) {
+                out.bottom = win.mVisibleFrame.bottom;
+            }
+        }
+        return foundTop;
+    }
+
+    /** Bounds of the task to be used for dimming, as well as touch related tests. */
+    @Override
+    public void getDimBounds(Rect out) {
+        if (useCurrentBounds()) {
+            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+                return;
+            }
+
+            out.set(mBounds);
+            return;
+        }
+
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
+        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
+        // system.
+        mStack.getDisplayContent().getLogicalDisplayRect(out);
+    }
+
     void setDragResizing(boolean dragResizing) {
         mDragResizing = dragResizing;
     }
@@ -354,14 +418,20 @@
         return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
     }
 
-    WindowState getTopAppMainWindow() {
-        final int tokensCount = mAppTokens.size();
-        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
+    WindowState getTopVisibleAppMainWindow() {
+        final AppWindowToken token = getTopVisibleAppToken();
+        return token != null ? token.findMainWindow() : null;
     }
 
-    AppWindowToken getTopAppWindowToken() {
-        final int tokensCount = mAppTokens.size();
-        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null;
+    AppWindowToken getTopVisibleAppToken() {
+        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mAppTokens.get(i);
+            // skip hidden (or about to hide) apps
+            if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
+                return token;
+            }
+        }
+        return null;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index aae3bd2..f5e4e3b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -149,7 +149,7 @@
                         }
                         synchronized (mService.mWindowMap) {
                             mDragEnded = notifyMoveLocked(newX, newY);
-                            mTask.getBounds(mTmpRect);
+                            mTask.getDimBounds(mTmpRect);
                         }
                         if (!mTmpRect.equals(mWindowDragBounds)) {
                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
@@ -332,28 +332,34 @@
                 + ", {" + startX + ", " + startY + "}");
         }
         mCtrlType = CTRL_NONE;
+        mTask = win.getTask();
+        mStartDragX = startX;
+        mStartDragY = startY;
+
+        // Use the dim bounds, not the original task bounds. The cursor
+        // movement should be calculated relative to the visible bounds.
+        // Also, use the dim bounds of the task which accounts for
+        // multiple app windows. Don't use any bounds from win itself as it
+        // may not be the same size as the task.
+        mTask.getDimBounds(mTmpRect);
+
         if (resize) {
-            final Rect visibleFrame = win.mVisibleFrame;
-            if (startX < visibleFrame.left) {
+            if (startX < mTmpRect.left) {
                 mCtrlType |= CTRL_LEFT;
             }
-            if (startX > visibleFrame.right) {
+            if (startX > mTmpRect.right) {
                 mCtrlType |= CTRL_RIGHT;
             }
-            if (startY < visibleFrame.top) {
+            if (startY < mTmpRect.top) {
                 mCtrlType |= CTRL_TOP;
             }
-            if (startY > visibleFrame.bottom) {
+            if (startY > mTmpRect.bottom) {
                 mCtrlType |= CTRL_BOTTOM;
             }
             mResizing = true;
         }
 
-        mTask = win.getTask();
-        mStartDragX = startX;
-        mStartDragY = startY;
-
-        mService.getTaskBounds(mTask.mTaskId, mWindowOriginalBounds);
+        mWindowOriginalBounds.set(mTmpRect);
     }
 
     private void endDragLocked() {
@@ -393,7 +399,7 @@
         }
 
         // This is a moving operation.
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight);
         if (!mTmpRect.contains((int) x, (int) y)) {
             // We end the moving operation if position is outside the stack bounds.
@@ -438,7 +444,7 @@
             return CTRL_NONE;
         }
 
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         if (x - mSideMargin <= mTmpRect.left) {
             return CTRL_LEFT;
         }
@@ -450,7 +456,7 @@
     }
 
     private void showDimLayer() {
-        mTask.mStack.getBounds(mTmpRect);
+        mTask.mStack.getDimBounds(mTmpRect);
         if (mCurrentDimSide == CTRL_LEFT) {
             mTmpRect.right = mTmpRect.centerX();
         } else if (mCurrentDimSide == CTRL_RIGHT) {
@@ -473,7 +479,7 @@
     }
 
     @Override
-    public void getBounds(Rect out) {
+    public void getDimBounds(Rect out) {
         // This dim layer user doesn't need this.
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7c02b43..04ab544 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -189,8 +189,6 @@
         return false;
     }
 
-    /** Bounds of the stack with other system factors taken into consideration. */
-    @Override
     public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
@@ -205,6 +203,12 @@
         mDisplayContent.getLogicalDisplayRect(out);
     }
 
+    /** Bounds of the stack with other system factors taken into consideration. */
+    @Override
+    public void getDimBounds(Rect out) {
+        getBounds(out);
+    }
+
     void updateDisplayInfo(Rect bounds) {
         if (mDisplayContent != null) {
             for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -511,10 +515,11 @@
         for (int i = 0; i < count; i++) {
             final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
             final int otherStackId = otherStack.mStackId;
-            if (StackId.isResizeableByDockedStack(otherStackId)) {
+            if (StackId.isResizeableByDockedStack(otherStackId)
+                    && !otherStack.mBounds.equals(bounds)) {
                 mService.mH.sendMessage(
                         mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
-                                1 /*allowResizeInDockedMode*/, bounds));
+                                1 /*allowResizeInDockedMode*/, fullscreen ? null : bounds));
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index a33fb13..f5b83bb 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -90,11 +90,11 @@
             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) {
+                final Task task = mDisplayContent.findTaskForControlPoint(x, y);
+                if (task == null) {
                     break;
                 }
-                window.getVisibleBounds(mTmpRect, false);
+                task.getDimBounds(mTmpRect);
                 if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
                     int iconShape = STYLE_DEFAULT;
                     if (x < mTmpRect.left) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 228da0f..46fab2a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -18,29 +18,35 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerService.DEBUG_KEYGUARD;
-import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
-import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
+import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerService.DEBUG_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
+import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
 
 import android.content.Context;
-import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManagerPolicy;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
-import android.view.Choreographer;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -61,7 +67,7 @@
     private final WindowSurfacePlacer mWindowPlacerLocked;
 
     /** Is any window animating? */
-    boolean mAnimating;
+    private boolean mAnimating;
 
     /** Is any app window animating? */
     boolean mAppWindowAnimating;
@@ -101,6 +107,10 @@
     static final int KEYGUARD_ANIMATING_OUT = 2;
     int mForceHiding = KEYGUARD_NOT_SHOWN;
 
+    // When set to true the animator will go over all windows after an animation frame is posted and
+    // check if some got replaced and can be removed.
+    private boolean mRemoveReplacedWindows = false;
+
     private String forceHidingToString() {
         switch (mForceHiding) {
             case KEYGUARD_NOT_SHOWN:    return "KEYGUARD_NOT_SHOWN";
@@ -158,13 +168,14 @@
                     appAnimator.wasAnimating = appAnimator.animating;
                     if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
                         appAnimator.animating = true;
-                        mAnimating = mAppWindowAnimating = true;
+                        setAnimating(true);
+                        mAppWindowAnimating = true;
                     } else if (appAnimator.wasAnimating) {
                         // stopped animating, do one more pass through the layout
                         setAppLayoutChanges(appAnimator,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
                                 "appToken " + appAnimator.mAppToken + " done", displayId);
-                        if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                        if (DEBUG_ANIM) Slog.v(TAG,
                                 "updateWindowsApps...: done animating " + appAnimator.mAppToken);
                     }
                 }
@@ -176,13 +187,14 @@
                 final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator;
                 appAnimator.wasAnimating = appAnimator.animating;
                 if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
-                    mAnimating = mAppWindowAnimating = true;
+                    setAnimating(true);
+                    mAppWindowAnimating = true;
                 } else if (appAnimator.wasAnimating) {
                     // stopped animating, do one more pass through the layout
                     setAppLayoutChanges(appAnimator,
                             WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
                             "exiting appToken " + appAnimator.mAppToken + " done", displayId);
-                    if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                    if (DEBUG_ANIM) Slog.v(TAG,
                             "updateWindowsApps...: done animating exiting "
                                     + appAnimator.mAppToken);
                 }
@@ -272,9 +284,9 @@
                 final boolean wasAnimating = winAnimator.mWasAnimating;
                 final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
                 winAnimator.mWasAnimating = nowAnimating;
-                mAnimating |= nowAnimating;
+                orAnimating(nowAnimating);
 
-                if (WindowManagerService.DEBUG_WALLPAPER) {
+                if (DEBUG_WALLPAPER) {
                     Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
                             ", nowAnimating=" + nowAnimating);
                 }
@@ -284,7 +296,7 @@
                     mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
                     setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
                             WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                    if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                    if (DEBUG_LAYOUT_REPEATS) {
                         mWindowPlacerLocked.debugLayoutRepeats(
                                 "updateWindowsAndWallpaperLocked 2",
                                 getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
@@ -293,13 +305,13 @@
 
                 if (mPolicy.isForceHiding(win.mAttrs)) {
                     if (!wasAnimating && nowAnimating) {
-                        if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_ANIM ||
-                                WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                        if (DEBUG_KEYGUARD || DEBUG_ANIM ||
+                                DEBUG_VISIBILITY) Slog.v(TAG,
                                 "Animation started that could impact force hide: " + win);
                         mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED;
                         setPendingLayoutChanges(displayId,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        if (DEBUG_LAYOUT_REPEATS) {
                             mWindowPlacerLocked.debugLayoutRepeats(
                                     "updateWindowsAndWallpaperLocked 3",
                                     getPendingLayoutChanges(displayId));
@@ -318,7 +330,7 @@
                             mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
                         }
                     }
-                    if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                    if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
                             "Force hide " + forceHidingToString()
                             + " hasSurface=" + win.mHasSurface
                             + " policyVis=" + win.mPolicyVisibility
@@ -333,7 +345,7 @@
                             // Was already hidden
                             continue;
                         }
-                        if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                        if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
                                 "Now policy hidden: " + win);
                     } else {
                         boolean applyExistingExitAnimation = mPostKeyguardExitAnimation != null
@@ -355,7 +367,7 @@
                             win.hideLw(false, false);
                             continue;
                         }
-                        if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                        if (DEBUG_KEYGUARD || DEBUG_VISIBILITY) Slog.v(TAG,
                                 "Now policy shown: " + win);
                         if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
                                 && win.mAttachedWindow == null) {
@@ -385,7 +397,7 @@
                             // We are showing on top of the current
                             // focus, so re-evaluate focus to make
                             // sure it is correct.
-                            if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.v(TAG,
+                            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG,
                                     "updateWindowsLocked: setting mFocusMayChange true");
                             mService.mFocusMayChange = true;
                         }
@@ -394,7 +406,7 @@
                         mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
                         setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        if (DEBUG_LAYOUT_REPEATS) {
                             mWindowPlacerLocked.debugLayoutRepeats(
                                     "updateWindowsAndWallpaperLocked 4",
                                     getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
@@ -419,7 +431,7 @@
                     if (winAnimator.performShowLocked()) {
                         setPendingLayoutChanges(displayId,
                                 WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
-                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        if (DEBUG_LAYOUT_REPEATS) {
                             mWindowPlacerLocked.debugLayoutRepeats(
                                     "updateWindowsAndWallpaperLocked 5",
                                     getPendingLayoutChanges(displayId));
@@ -536,7 +548,7 @@
                         }
                     }
                 }
-                mAnimating = true;
+                setAnimating(true);
             }
 
             // If this window's app token is running a detached wallpaper
@@ -561,7 +573,7 @@
         } // end forall windows
 
         if (mWindowDetachedWallpaper != detachedWallpaper) {
-            if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG,
+            if (DEBUG_WALLPAPER) Slog.v(TAG,
                     "Detached wallpaper changed from " + mWindowDetachedWallpaper
                     + " to " + detachedWallpaper);
             mWindowDetachedWallpaper = detachedWallpaper;
@@ -591,7 +603,7 @@
                         if (appAnimator.freezingScreen) {
                             appAnimator.showAllWindowsLocked();
                             mService.unsetAppFreezingScreenLocked(wtoken, false, true);
-                            if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG,
+                            if (DEBUG_ORIENTATION) Slog.i(TAG,
                                     "Setting mOrientationChangeComplete=true because wtoken "
                                     + wtoken + " numInteresting=" + wtoken.numInterestingWindows
                                     + " numDrawn=" + wtoken.numDrawnWindows);
@@ -607,7 +619,7 @@
 
                             // We can now show all of the drawn windows!
                             if (!mService.mOpeningApps.contains(wtoken)) {
-                                mAnimating |= appAnimator.showAllWindowsLocked();
+                                orAnimating(appAnimator.showAllWindowsLocked());
                             }
                         }
                     }
@@ -626,13 +638,13 @@
         mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
         mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
         boolean wasAnimating = mAnimating;
-        mAnimating = false;
+        setAnimating(false);
         mAppWindowAnimating = false;
-        if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+        if (DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
         }
 
-        if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+        if (SHOW_TRANSACTIONS) Slog.i(
                 TAG, ">>> OPEN TRANSACTION animateLocked");
         SurfaceControl.openTransaction();
         SurfaceControl.setAnimationTransaction();
@@ -647,7 +659,7 @@
                         displayAnimator.mScreenRotationAnimation;
                 if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
                     if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
-                        mAnimating = true;
+                        setAnimating(true);
                     } else {
                         mBulkUpdateParams |= SET_UPDATE_ROTATION;
                         screenRotationAnimation.kill();
@@ -687,7 +699,7 @@
                     screenRotationAnimation.updateSurfacesInTransaction();
                 }
 
-                mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();
+                orAnimating(mService.getDisplayContentLocked(displayId).animateDimLayers());
 
                 //TODO (multidisplay): Magnification is supported only for the default display.
                 if (mService.mAccessibilityController != null
@@ -696,6 +708,10 @@
                 }
             }
 
+            if (mService.mDragState != null) {
+                mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
+            }
+
             if (mAnimating) {
                 mService.scheduleAnimationLocked();
             }
@@ -707,7 +723,7 @@
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
             SurfaceControl.closeTransaction();
-            if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+            if (SHOW_TRANSACTIONS) Slog.i(
                     TAG, "<<< CLOSE TRANSACTION animateLocked");
         }
 
@@ -744,16 +760,40 @@
             }
         }
 
+        if (mRemoveReplacedWindows) {
+            removeReplacedWindowsLocked();
+        }
+
         mService.destroyPreservedSurfaceLocked();
 
-        if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+        if (DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
-                + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
-                + " mPendingLayoutChanges(DEFAULT_DISPLAY)="
-                + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY)));
+                    + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+                    + " mPendingLayoutChanges(DEFAULT_DISPLAY)="
+                    + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY)));
         }
     }
 
+    private void removeReplacedWindowsLocked() {
+        if (SHOW_TRANSACTIONS) Slog.i(
+                TAG, ">>> OPEN TRANSACTION removeReplacedWindows");
+        SurfaceControl.openTransaction();
+        try {
+            for (int i = mService.mDisplayContents.size() - 1; i >= 0; i--) {
+                DisplayContent display = mService.mDisplayContents.get(i);
+                final WindowList windows = mService.getWindowListLocked(display.getDisplayId());
+                for (int j = windows.size() - 1; j >= 0; j--) {
+                    windows.get(j).maybeRemoveReplacedWindow();
+                }
+            }
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (SHOW_TRANSACTIONS) Slog.i(
+                    TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
+        }
+        mRemoveReplacedWindows = false;
+    }
+
     private static String bulkUpdateParamsToString(int bulkUpdateParams) {
         StringBuilder builder = new StringBuilder(128);
         if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
@@ -844,7 +884,7 @@
         for (int i = windows.size() - 1; i >= 0; i--) {
             if (displayId == windows.get(i).getDisplayId()) {
                 setPendingLayoutChanges(displayId, changes);
-                if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                if (DEBUG_LAYOUT_REPEATS) {
                     mWindowPlacerLocked.debugLayoutRepeats(reason,
                             getPendingLayoutChanges(displayId));
                 }
@@ -875,7 +915,27 @@
         return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation;
     }
 
+    void requestRemovalOfReplacedWindows(WindowState win) {
+        final AppWindowToken token = win.mAppToken;
+        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == win) {
+            token.mHasReplacedWindow = true;
+        }
+        mRemoveReplacedWindows = true;
+    }
+
     private class DisplayContentsAnimator {
         ScreenRotationAnimation mScreenRotationAnimation = null;
     }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    void setAnimating(boolean animating) {
+        mAnimating = animating;
+    }
+
+    void orAnimating(boolean animating) {
+        mAnimating |= animating;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5e68aa2..e741c45 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -50,14 +50,16 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 
 import android.Manifest;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
-import android.app.StatusBarManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -226,6 +228,7 @@
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
+    static final boolean SHOW_VERBOSE_TRANSACTIONS = false && SHOW_TRANSACTIONS;
     static final boolean HIDE_STACK_CRAWLS = true;
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
@@ -306,6 +309,8 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
+    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+
     final private KeyguardDisableHandler mKeyguardDisableHandler;
 
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -750,9 +755,6 @@
     private boolean completeDropLw(float x, float y) {
         WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y);
 
-        mDragState.mCurrentX = x;
-        mDragState.mCurrentY = y;
-
         DropPermissionHolder dropPermissionHolder = null;
         if (dropTargetWin != null &&
                 (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
@@ -910,7 +912,7 @@
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
 
-        mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked);
+        mAppTransition = new AppTransition(context, this);
         mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
 
         mActivityManager = ActivityManagerNative.getDefault();
@@ -2077,7 +2079,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -2242,7 +2244,8 @@
                     mAccessibilityController.onWindowTransitionLocked(win, transit);
                 }
             }
-            final boolean isAnimating = win.mWinAnimator.isAnimating();
+            final boolean isAnimating = win.mWinAnimator.isAnimating()
+                    && !win.mWinAnimator.isDummyAnimation();
             // The starting window is the last window in this app token and it isn't animating.
             // Allow it to be removed now as there is no additional window or animation that will
             // trigger its removal.
@@ -2504,7 +2507,8 @@
     }
 
     void repositionChild(Session session, IWindow client,
-            int x, int y, long deferTransactionUntilFrame, Rect outFrame) {
+            int top, int left, int right, int bottom,
+            long deferTransactionUntilFrame, Rect outFrame) {
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
         long origId = Binder.clearCallingIdentity();
 
@@ -2520,8 +2524,10 @@
                             + "attached to a parent win=" + win);
                 }
 
-                win.mFrame.left = x;
-                win.mFrame.top = y;
+                win.mFrame.left = left;
+                win.mFrame.top = top;
+                win.mFrame.right = right;
+                win.mFrame.bottom = bottom;
 
                 win.mWinAnimator.computeShownFrameLocked();
 
@@ -2594,6 +2600,15 @@
                     throw new IllegalArgumentException(
                             "Window type can not be changed after the window is added.");
                 }
+
+                // Odd choice but less odd than embedding in copyFrom()
+                if ((attrs.flags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY) != 0) {
+                    attrs.x = win.mAttrs.x;
+                    attrs.y = win.mAttrs.y;
+                    attrs.width = win.mAttrs.width;
+                    attrs.height = win.mAttrs.height;
+                }
+
                 flagChanges = win.mAttrs.flags ^= attrs.flags;
                 attrChanges = win.mAttrs.copyFrom(attrs);
                 if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
@@ -2724,6 +2739,10 @@
             if (win.mAppToken != null) {
                 win.mAppToken.updateReportedVisibilityLocked();
             }
+            if (winAnimator.mReportSurfaceResized) {
+                winAnimator.mReportSurfaceResized = false;
+                result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
+            }
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
             outContentInsets.set(win.mContentInsets);
@@ -2843,7 +2862,11 @@
         // notifying the client to render to with an offset from the surface's top-left.
         if (win.isDragResizeChanged()) {
             win.setDragResizing();
-            if (win.mHasSurface) {
+            // We can only change top level windows to the full-screen surface when
+            // resizing (as we only have one full-screen surface). So there is no need
+            // to preserve and destroy windows which are attached to another, they 
+            // will keep their surface and its size may change over time.
+            if (win.mHasSurface && win.mAttachedWindow == null) {
                 winAnimator.preserveSurfaceLocked();
                 result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
             }
@@ -3676,22 +3699,26 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            if (!scaleUp) {
-                // This is used by freeform to recents windows transition. We need to synchronize
-                // the animation with the appearance of the content of recents, so we will make
-                // animation stay on the last frame a little longer.
-                mTmpTaskIds.clear();
-                for (int i = specs.length - 1; i >= 0; i--) {
-                    mTmpTaskIds.put(specs[i].taskId, 0);
-                }
-                for (final WindowState win : mWindowMap.values()) {
-                    final Task task = win.getTask();
-                    if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
-                        final AppWindowToken appToken = win.mAppToken;
-                        if (appToken != null && appToken.mAppAnimator != null) {
-                            appToken.mAppAnimator.startProlongAnimation();
-                        }
-                    }
+            prolongAnimationsFromSpecs(specs, scaleUp);
+
+        }
+    }
+
+    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+        // This is used by freeform <-> recents windows transition. We need to synchronize
+        // the animation with the appearance of the content of recents, so we will make
+        // animation stay on the first or last frame a little longer.
+        mTmpTaskIds.clear();
+        for (int i = specs.length - 1; i >= 0; i--) {
+            mTmpTaskIds.put(specs[i].taskId, 0);
+        }
+        for (final WindowState win : mWindowMap.values()) {
+            final Task task = win.getTask();
+            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
+                final AppWindowToken appToken = win.mAppToken;
+                if (appToken != null && appToken.mAppAnimator != null) {
+                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
+                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
                 }
             }
         }
@@ -4794,7 +4821,8 @@
         }
     }
 
-    public void positionTaskInStack(int taskId, int stackId, int position) {
+    public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
+            Configuration config) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: positioning taskId=" + taskId
                     + " in stackId=" + stackId + " at " + position);
@@ -4810,7 +4838,7 @@
                         "positionTaskInStack: could not find stackId=" + stackId);
                 return;
             }
-            task.positionTaskInStack(stack, position);
+            task.positionTaskInStack(stack, position, bounds, config);
             final DisplayContent displayContent = stack.getDisplayContent();
             displayContent.layoutNeeded = true;
             mWindowPlacerLocked.performSurfacePlacement();
@@ -5627,7 +5655,7 @@
                 }
             }
 
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+            if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG,
                     ">>> OPEN TRANSACTION showStrictModeViolation");
             SurfaceControl.openTransaction();
             try {
@@ -5639,7 +5667,7 @@
                 mStrictModeFlash.setVisibility(on);
             } finally {
                 SurfaceControl.closeTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION showStrictModeViolation");
             }
         }
@@ -5836,7 +5864,7 @@
                         int right = wf.right - cr.right;
                         int bottom = wf.bottom - cr.bottom;
                         frame.union(left, top, right, bottom);
-                        ws.getVisibleBounds(stackBounds, !BOUNDS_FOR_TOUCH);
+                        ws.getVisibleBounds(stackBounds);
                         if (!frame.intersect(stackBounds)) {
                             // Set frame empty if there's no intersection.
                             frame.setEmpty();
@@ -6890,27 +6918,25 @@
         final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
         final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayInfo.rotation = mRotation;
-            displayInfo.logicalWidth = dw;
-            displayInfo.logicalHeight = dh;
-            displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
-            displayInfo.appWidth = appWidth;
-            displayInfo.appHeight = appHeight;
-            displayInfo.getLogicalMetrics(mRealDisplayMetrics,
-                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
-            displayInfo.getAppMetrics(mDisplayMetrics);
-            if (displayContent.mDisplayScalingDisabled) {
-                displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
-            } else {
-                displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
-            }
-
-            mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                    displayContent.getDisplayId(), displayInfo);
-
-            displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
+        displayInfo.rotation = mRotation;
+        displayInfo.logicalWidth = dw;
+        displayInfo.logicalHeight = dh;
+        displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
+        displayInfo.appWidth = appWidth;
+        displayInfo.appHeight = appHeight;
+        displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+        displayInfo.getAppMetrics(mDisplayMetrics);
+        if (displayContent.mDisplayScalingDisabled) {
+            displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+        } else {
+            displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
         }
+
+        mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
+                displayContent.getDisplayId(), displayInfo);
+
+        displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
         if (false) {
             Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
         }
@@ -7055,15 +7081,16 @@
     }
 
     private void startResizingTask(DisplayContent displayContent, int startX, int startY) {
-        WindowState win = null;
+        Task task = null;
         synchronized (mWindowMap) {
-            win = displayContent.findWindowForControlPoint(startX, startY);
-            if (win == null || !startPositioningLocked(win, true /*resize*/, startX, startY)) {
+            task = displayContent.findTaskForControlPoint(startX, startY);
+            if (task == null || !startPositioningLocked(
+                    task.getTopVisibleAppMainWindow(), true /*resize*/, startX, startY)) {
                 return;
             }
         }
         try {
-            mActivityManager.setFocusedTask(win.getTask().mTaskId);
+            mActivityManager.setFocusedTask(task.mTaskId);
         } catch(RemoteException e) {}
     }
 
@@ -7147,9 +7174,11 @@
                         SurfaceControl surface = new SurfaceControl(session, "drag surface",
                                 width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
                         surface.setLayerStack(display.getLayerStack());
+                        float alpha = 1;
                         if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
-                            surface.setAlpha(.7071f);
+                            alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
                         }
+                        surface.setAlpha(alpha);
 
                         if (SHOW_TRANSACTIONS) Slog.i(TAG, "  DRAG "
                                 + surface + ": CREATE");
@@ -7159,6 +7188,7 @@
                         mDragState = new DragState(this, token, surface, flags, winBinder);
                         mDragState.mPid = callerPid;
                         mDragState.mUid = callerUid;
+                        mDragState.mOriginalAlpha = alpha;
                         token = mDragState.mToken = new Binder();
 
                         // 5 second timeout for this window to actually begin the drag
@@ -7668,7 +7698,7 @@
                     synchronized (mWindowMap) {
                         // Since we're holding both mWindowMap and mAnimator we don't need to
                         // hold mAnimator.mLayoutToAnim.
-                        if (mAnimator.mAnimating || mAnimationScheduled) {
+                        if (mAnimator.isAnimating() || mAnimationScheduled) {
                             // If we are animating, don't do the gc now but
                             // delay a bit so we don't interrupt the animation.
                             sendEmptyMessageDelayed(H.FORCE_GC, 2000);
@@ -8041,10 +8071,8 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    size.x = displayContent.mInitialDisplayWidth;
-                    size.y = displayContent.mInitialDisplayHeight;
-                }
+                size.x = displayContent.mInitialDisplayWidth;
+                size.y = displayContent.mInitialDisplayHeight;
             }
         }
     }
@@ -8054,10 +8082,8 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    size.x = displayContent.mBaseDisplayWidth;
-                    size.y = displayContent.mBaseDisplayHeight;
-                }
+                size.x = displayContent.mBaseDisplayWidth;
+                size.y = displayContent.mBaseDisplayHeight;
             }
         }
     }
@@ -8126,13 +8152,9 @@
         }
     }
 
-    private void setForcedDisplayScalingModeLocked(DisplayContent displayContent,
-            int mode) {
+    private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
         Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mDisplayScalingDisabled = (mode != 0);
-        }
+        displayContent.mDisplayScalingDisabled = (mode != 0);
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8150,13 +8172,11 @@
                 try {
                     width = Integer.parseInt(sizeStr.substring(0, pos));
                     height = Integer.parseInt(sizeStr.substring(pos+1));
-                    synchronized(displayContent.mDisplaySizeLock) {
-                        if (displayContent.mBaseDisplayWidth != width
-                                || displayContent.mBaseDisplayHeight != height) {
-                            Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
-                            displayContent.mBaseDisplayWidth = width;
-                            displayContent.mBaseDisplayHeight = height;
-                        }
+                    if (displayContent.mBaseDisplayWidth != width
+                            || displayContent.mBaseDisplayHeight != height) {
+                        Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
+                        displayContent.mBaseDisplayWidth = width;
+                        displayContent.mBaseDisplayHeight = height;
                     }
                 } catch (NumberFormatException ex) {
                 }
@@ -8173,11 +8193,9 @@
             int density;
             try {
                 density = Integer.parseInt(densityStr);
-                synchronized(displayContent.mDisplaySizeLock) {
-                    if (displayContent.mBaseDisplayDensity != density) {
-                        Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
-                        displayContent.mBaseDisplayDensity = density;
-                    }
+                if (displayContent.mBaseDisplayDensity != density) {
+                    Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
+                    displayContent.mBaseDisplayDensity = density;
                 }
             } catch (NumberFormatException ex) {
             }
@@ -8187,21 +8205,16 @@
         int mode = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DISPLAY_SCALING_FORCE, 0);
         if (mode != 0) {
-            synchronized(displayContent.mDisplaySizeLock) {
-                Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
-                displayContent.mDisplayScalingDisabled = true;
-            }
+            Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
+            displayContent.mDisplayScalingDisabled = true;
         }
     }
 
     // displayContent must not be null
     private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
         Slog.i(TAG, "Using new display size: " + width + "x" + height);
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mBaseDisplayWidth = width;
-            displayContent.mBaseDisplayHeight = height;
-        }
+        displayContent.mBaseDisplayWidth = width;
+        displayContent.mBaseDisplayHeight = height;
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8237,9 +8250,7 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    return displayContent.mInitialDisplayDensity;
-                }
+                return displayContent.mInitialDisplayDensity;
             }
         }
         return -1;
@@ -8250,9 +8261,7 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    return displayContent.mBaseDisplayDensity;
-                }
+                return displayContent.mBaseDisplayDensity;
             }
         }
         return -1;
@@ -8287,10 +8296,7 @@
     // displayContent must not be null
     private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) {
         Slog.i(TAG, "Using new display density: " + density);
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mBaseDisplayDensity = density;
-        }
+        displayContent.mBaseDisplayDensity = density;
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8381,12 +8387,10 @@
     private void setOverscanLocked(DisplayContent displayContent,
             int left, int top, int right, int bottom) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        synchronized (displayContent.mDisplaySizeLock) {
-            displayInfo.overscanLeft = left;
-            displayInfo.overscanTop = top;
-            displayInfo.overscanRight = right;
-            displayInfo.overscanBottom = bottom;
-        }
+        displayInfo.overscanLeft = left;
+        displayInfo.overscanTop = top;
+        displayInfo.overscanRight = right;
+        displayInfo.overscanBottom = bottom;
 
         mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
                 right, bottom);
@@ -8565,8 +8569,7 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
-                        wtoken.mReplacingWindow != w) {
+                if (wtoken.mWillReplaceWindow && 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.
@@ -8726,9 +8729,11 @@
                 // we need to go through the process of getting informed by the
                 // application when it has finished drawing.
                 if (w.mOrientationChanging || dragResizingChanged) {
-                    if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION) Slog.v(TAG,
-                            "Orientation start waiting for draw mDrawState=DRAW_PENDING in "
-                            + w + ", surfaceController " + winAnimator.mSurfaceController);
+                    if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+                        Slog.v(TAG, "Orientation or resize start waiting for draw"
+                                + ", mDrawState=DRAW_PENDING in " + w
+                                + ", surfaceController " + winAnimator.mSurfaceController);
+                    }
                     winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
                     if (w.mAppToken != null) {
                         w.mAppToken.allDrawn = false;
@@ -9971,14 +9976,11 @@
         DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final Rect rect = new Rect();
         mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
-        synchronized (displayContent.mDisplaySizeLock) {
-            displayInfo.overscanLeft = rect.left;
-            displayInfo.overscanTop = rect.top;
-            displayInfo.overscanRight = rect.right;
-            displayInfo.overscanBottom = rect.bottom;
-            mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                    displayId, displayInfo);
-        }
+        displayInfo.overscanLeft = rect.left;
+        displayInfo.overscanTop = rect.top;
+        displayInfo.overscanRight = rect.right;
+        displayInfo.overscanBottom = rect.bottom;
+        mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId, displayInfo);
         configureDisplayPolicyLocked(displayContent);
 
         // TODO: Create an input channel for each display with touch capability.
@@ -10104,9 +10106,8 @@
      * Hint to a token that its activity will relaunch, which will trigger removal and addition of
      * a window.
      * @param token Application token for which the activity will be relaunched.
-     * @param animate Whether to animate the addition of the new window.
      */
-    public void setReplacingWindow(IBinder token, boolean animate) {
+    public void setReplacingWindow(IBinder token) {
         synchronized (mWindowMap) {
             AppWindowToken appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null) {
@@ -10116,7 +10117,14 @@
             if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken
                     + " as replacing window.");
             appWindowToken.mWillReplaceWindow = true;
-            appWindowToken.mAnimateReplacingWindow = animate;
+            appWindowToken.mHasReplacedWindow = false;
+
+            // Set-up dummy animation so we can start treating windows associated with this token
+            // like they are in transition before the new app window is ready for us to run the
+            // real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+            appWindowToken.mAppAnimator.setDummyAnimation();
         }
     }
 
@@ -10185,7 +10193,7 @@
         }
 
         @Override
-        public void setMagnificationCallbacks(MagnificationCallbacks callbacks) {
+        public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
             synchronized (mWindowMap) {
                 if (mAccessibilityController == null) {
                     mAccessibilityController = new AccessibilityController(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a7aa9e1..c6f7f4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,11 +16,57 @@
 
 package com.android.server.wm;
 
+import com.android.server.input.InputWindowHandle;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IWindow;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
@@ -36,8 +82,6 @@
 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_ANIM;
-import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
@@ -46,47 +90,6 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.graphics.Point;
-import android.os.PowerManager;
-import android.os.RemoteCallbackList;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.WorkSource;
-import android.util.DisplayMetrics;
-import android.util.TimeUtils;
-import android.view.Display;
-import android.view.IWindowFocusObserver;
-import android.view.IWindowId;
-
-import com.android.server.input.InputWindowHandle;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Matrix;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.view.IApplicationToken;
-import android.view.IWindow;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
 class WindowList extends ArrayList<WindowState> {
 }
 
@@ -106,8 +109,6 @@
     // to capture touch events in that area.
     static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
 
-    static final boolean BOUNDS_FOR_TOUCH = true;
-
     static final int DRAG_RESIZE_MODE_FREEFORM = 0;
     static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
 
@@ -729,8 +730,18 @@
             mVisibleFrame.set(mContentFrame);
             mStableFrame.set(mContentFrame);
         } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
-            mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
-            mContentFrame.set(mFrame);
+            if (isVisibleLw()) {
+                // We don't adjust the dock divider frame for reasons other than performance. The
+                // real reason is that if it gets adjusted before it is shown for the first time,
+                // it would get size (0, 0). This causes a problem when we finally show the dock
+                // divider and try to draw to it. We do set the surface size at that moment to
+                // the correct size, but it's too late for the Surface Flinger to make it
+                // available for view rendering and as a result the renderer receives size 1, 1.
+                // This way we just keep the divider at the original size and Surface Flinger
+                // will return the correct value to the renderer.
+                mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
+                mContentFrame.set(mFrame);
+            }
         } else {
             mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                     Math.max(mContentFrame.top, mFrame.top),
@@ -962,17 +973,15 @@
     /**
      * Retrieves the visible bounds of the window.
      * @param bounds The rect which gets the bounds.
-     * @param forTouch Pass in BOUNDS_FOR_TOUCH to get touch related bounds, otherwise visible
-     *        bounds will be returned.
      */
-    void getVisibleBounds(Rect bounds, boolean forTouch) {
+    void getVisibleBounds(Rect bounds) {
         boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
         bounds.setEmpty();
         mTmpRect.setEmpty();
         if (intersectWithStackBounds) {
             final TaskStack stack = getStack();
             if (stack != null) {
-                stack.getBounds(mTmpRect);
+                stack.getDimBounds(mTmpRect);
             } else {
                 intersectWithStackBounds = false;
             }
@@ -990,12 +999,6 @@
             }
             return;
         }
-        if (forTouch && inFreeformWorkspace()) {
-            final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
-            final int delta = WindowManagerService.dipToPixel(
-                    RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
-            bounds.inset(-delta, -delta);
-        }
     }
 
     public long getInputDispatchingTimeoutNanos() {
@@ -1355,6 +1358,14 @@
     }
 
     void applyDimLayerIfNeeded() {
+        // When the app is terminated (eg. from Recents), the task might have already been
+        // removed with the window pending removal. Don't apply dim in such cases, as there
+        // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
+        final AppWindowToken token = mAppToken;
+        if (token != null && token.removed) {
+            return;
+        }
+
         if (!mExiting && mAppDied) {
             // If app died visible, apply a dim over the window to indicate that it's inactive
             mDisplayContent.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
@@ -1374,14 +1385,15 @@
 
     void maybeRemoveReplacedWindow() {
         AppWindowToken token = mAppToken;
-        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) {
+        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this
+                && token.mHasReplacedWindow) {
             if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
             token.mWillReplaceWindow = false;
-            token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
             token.mReplacingWindow = null;
+            token.mHasReplacedWindow = false;
             for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
-                WindowState win = token.allAppWindows.get(i);
+                final WindowState win = token.allAppWindows.get(i);
                 if (win.mExiting) {
                     mService.removeWindowInnerLocked(win);
                 }
@@ -1399,30 +1411,32 @@
         return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace();
     }
 
-    int getTouchableRegion(Region region, int flags, InputMonitor inputMonitor) {
-        final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0;
+    int getTouchableRegion(Region region, int flags) {
+        final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
         if (modal && mAppToken != null) {
             // Limit the outer touch to the activity stack region.
-            flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            if (!inFreeformWorkspace()) {
-                // If this is a modal window we need to dismiss it if it's not full screen and the
-                // touch happens outside of the frame that displays the content. This means we
-                // need to intercept touches outside of that window. The dim layer user
-                // associated with the window (task or stack) will give us the good bounds, as
-                // they would be used to display the dim layer.
-                final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-                if (dimLayerUser != null) {
-                    dimLayerUser.getBounds(mTmpRect);
-                } else {
-                    getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
-                }
+            flags |= FLAG_NOT_TOUCH_MODAL;
+            // If this is a modal window we need to dismiss it if it's not full screen and the
+            // touch happens outside of the frame that displays the content. This means we
+            // need to intercept touches outside of that window. The dim layer user
+            // associated with the window (task or stack) will give us the good bounds, as
+            // they would be used to display the dim layer.
+            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+            if (dimLayerUser != null) {
+                dimLayerUser.getDimBounds(mTmpRect);
             } else {
+                getVisibleBounds(mTmpRect);
+            }
+            if (inFreeformWorkspace()) {
                 // For freeform windows we the touch region to include the whole surface for the
                 // shadows.
-                getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+                final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
+                final int delta = WindowManagerService.dipToPixel(
+                        RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
+                mTmpRect.inset(-delta, -delta);
             }
             region.set(mTmpRect);
+            cropRegionToStackBoundsIfNeeded(region);
         } else {
             // Not modal or full screen modal
             getTouchableRegion(region);
@@ -1694,7 +1708,7 @@
 
         Task task = getTask();
         if (task == null || task.inHomeStack()
-                || task.getTopAppWindowToken() != mAppToken) {
+                || task.getTopVisibleAppToken() != mAppToken) {
             // 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.
             // Don't save if the window is not the topmost window.
@@ -1758,26 +1772,41 @@
                 frame.right - inset.right, frame.bottom - inset.bottom);
     }
 
-    public void getTouchableRegion(Region outRegion) {
+    void getTouchableRegion(Region outRegion) {
         final Rect frame = mFrame;
         switch (mTouchableInsets) {
             default:
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+            case TOUCHABLE_INSETS_FRAME:
                 outRegion.set(frame);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+            case TOUCHABLE_INSETS_CONTENT:
                 applyInsets(outRegion, frame, mGivenContentInsets);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+            case TOUCHABLE_INSETS_VISIBLE:
                 applyInsets(outRegion, frame, mGivenVisibleInsets);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
+            case TOUCHABLE_INSETS_REGION: {
                 final Region givenTouchableRegion = mGivenTouchableRegion;
                 outRegion.set(givenTouchableRegion);
                 outRegion.translate(frame.left, frame.top);
                 break;
             }
         }
+        cropRegionToStackBoundsIfNeeded(outRegion);
+    }
+
+    void cropRegionToStackBoundsIfNeeded(Region region) {
+        if (mAppToken == null || !mAppToken.mCropWindowsToStack) {
+            return;
+        }
+
+        final TaskStack stack = getStack();
+        if (stack == null) {
+            return;
+        }
+
+        stack.getDimBounds(mTmpRect);
+        region.op(mTmpRect, Region.Op.INTERSECT);
     }
 
     WindowList getWindowList() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 91755ac..004307e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYERS;
@@ -24,12 +26,12 @@
 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.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.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowState.*;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
 
@@ -37,22 +39,18 @@
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Debug;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
-import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
@@ -61,7 +59,6 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Keep track of animations and surface operations for a single WindowState.
@@ -101,6 +98,11 @@
      * we must tell them application to resize (and thus redraw itself).
      */
     boolean mSurfaceResized;
+    /**
+     * Whether we should inform the client on next relayoutWindow that
+     * the surface has been resized since last time.
+     */
+    boolean mReportSurfaceResized;
     WindowSurfaceController mSurfaceController;
     private WindowSurfaceController mPendingDestroySurface;
 
@@ -121,16 +123,12 @@
     Rect mLastClipRect = new Rect();
     Rect mTmpStackBounds = new Rect();
 
-    // Used to save animation distances between the time they are calculated and when they are
-    // used.
-    int mAnimDw;
-    int mAnimDh;
+    // Used to save animation distances between the time they are calculated and when they are used.
+    private int mAnimDx;
+    private int mAnimDy;
 
     /** Is the next animation to be started a window move animation? */
-    boolean mAnimateMove = false;
-
-    /** Are we currently running a window move animation? */
-    boolean mAnimatingMove = false;
+    private boolean mAnimateMove = false;
 
     float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
     float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
@@ -183,6 +181,8 @@
 
     int mAttrType;
 
+    private final Rect mTmpSize = new Rect();
+
     WindowStateAnimator(final WindowState win) {
         final WindowManagerService service = win.mService;
 
@@ -193,8 +193,8 @@
         final DisplayContent displayContent = win.getDisplayContent();
         if (displayContent != null) {
             final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            mAnimDw = displayInfo.appWidth;
-            mAnimDh = displayInfo.appHeight;
+            mAnimDx = displayInfo.appWidth;
+            mAnimDy = displayInfo.appHeight;
         } else {
             Slog.w(TAG, "WindowStateAnimator ctor: Display has been removed");
             // This is checked on return and dealt with.
@@ -294,19 +294,19 @@
                         TAG, "Starting animation in " + this +
                         " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
                         " wh=" + mWin.mFrame.height() +
-                        " dw=" + mAnimDw + " dh=" + mAnimDh +
+                        " dx=" + mAnimDx + " dy=" + mAnimDy +
                         " scale=" + mService.getWindowAnimationScaleLocked());
                     final DisplayInfo displayInfo = displayContent.getDisplayInfo();
                     if (mAnimateMove) {
                         mAnimateMove = false;
                         mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
-                                mAnimDw, mAnimDh);
+                                mAnimDx, mAnimDy);
                     } else {
                         mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
                                 displayInfo.appWidth, displayInfo.appHeight);
                     }
-                    mAnimDw = displayInfo.appWidth;
-                    mAnimDh = displayInfo.appHeight;
+                    mAnimDx = displayInfo.appWidth;
+                    mAnimDy = displayInfo.appHeight;
                     mAnimation.setStartTime(mAnimationStartTime != -1
                             ? mAnimationStartTime
                             : currentTime);
@@ -363,7 +363,6 @@
 
         mAnimating = false;
         mKeyguardGoingAwayAnimation = false;
-        mAnimatingMove = false;
         mLocalAnimating = false;
         if (mAnimation != null) {
             mAnimation.cancel();
@@ -442,7 +441,7 @@
         if (!isWindowAnimating()) {
             //TODO (multidisplay): Accessibility is supported only for the default display.
             if (mService.mAccessibilityController != null
-                    && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    && mWin.getDisplayId() == DEFAULT_DISPLAY) {
                 mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
             }
         }
@@ -475,7 +474,9 @@
         if (!mLastHidden) {
             //dump();
             mLastHidden = true;
-            mSurfaceController.hideInTransaction(reason);
+            if (mSurfaceController != null) {
+                mSurfaceController.hideInTransaction(reason);
+            }
         }
     }
 
@@ -494,7 +495,7 @@
         }
         if (mDrawState == DRAW_PENDING) {
             if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
-                Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
+                Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
                         + mSurfaceController);
             if (DEBUG_STARTING_WINDOW && startingWindow) {
                 Slog.v(TAG, "Draw state now committed in " + mWin);
@@ -526,7 +527,7 @@
                 mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
             result = performShowLocked();
         }
-        if (mDestroyPreservedSurfaceUponRedraw && result) {
+        if (mDestroyPreservedSurfaceUponRedraw) {
             mService.mDestroyPreservedSurface.add(mWin);
         }
         return result;
@@ -537,7 +538,9 @@
             return;
         }
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", null);
-        mSurfaceController.setLayer(mAnimLayer + 1);
+        if (mSurfaceController != null) {
+            mSurfaceController.setLayer(mAnimLayer + 1);
+        }
         mDestroyPreservedSurfaceUponRedraw = true;
         mSurfaceDestroyDeferred = true;
         destroySurfaceLocked();
@@ -577,52 +580,16 @@
                 flags |= SurfaceControl.SECURE;
             }
 
-            float left = w.mFrame.left + w.mXOffset;
-            float top = w.mFrame.top + w.mYOffset;
-
-            int width;
-            int height;
-            if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-                // for a scaled surface, we always want the requested
-                // size.
-                width = w.mRequestedWidth;
-                height = w.mRequestedHeight;
-            } else {
-                // When we're doing a drag-resizing, request a surface that's fullscreen size,
-                // so that we don't need to reallocate during the process. This also prevents
-                // buffer drops due to size mismatch.
-                final DisplayInfo displayInfo = w.getDisplayInfo();
-                if (displayInfo != null && w.isDragResizing()) {
-                    left = 0;
-                    top = 0;
-                    width = displayInfo.logicalWidth;
-                    height = displayInfo.logicalHeight;
-                } else {
-                    width = w.mCompatFrame.width();
-                    height = w.mCompatFrame.height();
-                }
-            }
-
-            // Something is wrong and SurfaceFlinger will not like this,
-            // try to revert to sane values
-            if (width <= 0) {
-                width = 1;
-            }
-            if (height <= 0) {
-                height = 1;
-            }
-
-            // Adjust for surface insets.
-            width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
-            height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
-            left -= attrs.surfaceInsets.left;
-            top -= attrs.surfaceInsets.top;
+            mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
+            calculateSurfaceBounds(w, attrs);
+            final int width = mTmpSize.width();
+            final int height = mTmpSize.height();
 
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG, "Creating surface in session "
                         + mSession.mSurfaceSession + " window " + this
                         + " w=" + width + " h=" + height
-                        + " x=" + left + " y=" + top
+                        + " x=" + mTmpSize.left + " y=" + mTmpSize.top
                         + " format=" + attrs.format + " flags=" + flags);
             }
 
@@ -688,15 +655,15 @@
                 Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
                 WindowManagerService.logSurface(w, "CREATE pos=("
                         + w.mFrame.left + "," + w.mFrame.top + ") ("
-                        + w.mCompatFrame.width() + "x" + w.mCompatFrame.height()
-                        + "), layer=" + mAnimLayer + " HIDE", null);
+                        + width + "x" + height + "), layer=" + mAnimLayer + " HIDE", null);
             }
 
             // Start a new transaction and apply position & offset.
             final int layerStack = w.getDisplayContent().getDisplay().getLayerStack();
             if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
-                    "POS " + left + ", " + top, null);
-            mSurfaceController.setPositionAndLayer(left, top, layerStack, mAnimLayer);
+                    "POS " + mTmpSize.left + ", " + mTmpSize.top, null);
+            mSurfaceController.setPositionAndLayer(mTmpSize.left, mTmpSize.top, layerStack,
+                    mAnimLayer);
             mLastHidden = true;
 
             if (WindowManagerService.localLOGV) Slog.v(
@@ -705,6 +672,57 @@
         return mSurfaceController;
     }
 
+    private void calculateSurfaceBounds(WindowState w, LayoutParams attrs) {
+        if ((attrs.flags & FLAG_SCALED) != 0) {
+            // For a scaled surface, we always want the requested size.
+            mTmpSize.right = mTmpSize.left + w.mRequestedWidth;
+            mTmpSize.bottom = mTmpSize.top + w.mRequestedHeight;
+        } else {
+            // When we're doing a drag-resizing, request a surface that's fullscreen size,
+            // so that we don't need to reallocate during the process. This also prevents
+            // buffer drops due to size mismatch.
+            if (w.isDragResizing()) {
+                if (w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
+                    mTmpSize.left = 0;
+                    mTmpSize.top = 0;
+                }
+                final DisplayInfo displayInfo = w.getDisplayInfo();
+                mTmpSize.right = mTmpSize.left + displayInfo.logicalWidth;
+                mTmpSize.bottom = mTmpSize.top + displayInfo.logicalHeight;
+            } else {
+                mTmpSize.right = mTmpSize.left + w.mCompatFrame.width();
+                mTmpSize.bottom = mTmpSize.top + w.mCompatFrame.height();
+            }
+        }
+
+        // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
+        if (mTmpSize.width() < 1) {
+            Slog.w(TAG, "Width of " + w + " is not positive " + mTmpSize.width());
+            mTmpSize.right = mTmpSize.left + 1;
+        }
+        if (mTmpSize.height() < 1) {
+            Slog.w(TAG, "Height of " + w + " is not positive " + mTmpSize.height());
+            mTmpSize.bottom = mTmpSize.top + 1;
+        }
+
+        final int displayId = w.getDisplayId();
+        float scale = 1.0f;
+        // Magnification is supported only for the default display.
+        if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+            final MagnificationSpec spec =
+                    mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
+            if (spec != null && !spec.isNop()) {
+                scale = spec.scale;
+            }
+        }
+
+        // Adjust for surface insets.
+        mTmpSize.left -= scale * attrs.surfaceInsets.left;
+        mTmpSize.top -= scale * attrs.surfaceInsets.top;
+        mTmpSize.right += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
+        mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
+    }
+
     void destroySurfaceLocked() {
         final AppWindowToken wtoken = mWin.mAppToken;
         if (wtoken != null) {
@@ -717,7 +735,12 @@
 
         if (mSurfaceController != null) {
             int i = mWin.mChildWindows.size();
-            while (i > 0) {
+            // When destroying a surface we want to make sure child windows
+            // are hidden. If we are preserving the surface until redraw though
+            // we intend to swap it out with another surface for resizing. In this case
+            // the window always remains visible to the user and the child windows
+            // should likewise remain visable.
+            while (!mDestroyPreservedSurfaceUponRedraw && i > 0) {
                 i--;
                 WindowState c = mWin.mChildWindows.get(i);
                 c.mAttachedHidden = true;
@@ -749,10 +772,21 @@
                         mPendingDestroySurface = mSurfaceController;
                     }
                 } else {
-                    WindowManagerService.logSurface(mWin, "DESTROY", null);
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                        RuntimeException e = null;
+                        if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                            e = new RuntimeException();
+                            e.fillInStackTrace();
+                        }
+                        WindowManagerService.logSurface(mWin, "DESTROY", null);
+                    }
                     destroySurface();
                 }
-                mWallpaperControllerLocked.hideWallpapers(mWin);
+                // Don't hide wallpaper if we're deferring the surface destroy
+                // because of a surface change.
+                if (!(mSurfaceDestroyDeferred && mDestroyPreservedSurfaceUponRedraw)) {
+                    mWallpaperControllerLocked.hideWallpapers(mWin);
+                }
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Exception thrown when destroying Window " + this
                     + " surface " + mSurfaceController + " session " + mSession
@@ -783,7 +817,11 @@
                     WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
                 }
                 mPendingDestroySurface.destroyInTransaction();
-                mWallpaperControllerLocked.hideWallpapers(mWin);
+                // Don't hide wallpaper if we're destroying a deferred surface
+                // after a surface mode change.
+                if (!mDestroyPreservedSurfaceUponRedraw) {
+                    mWallpaperControllerLocked.hideWallpapers(mWin);
+                }
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Exception thrown when destroying Window "
@@ -879,7 +917,7 @@
             tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
 
             //TODO (multidisplay): Magnification is supported only for the default display.
-            if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+            if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
                 MagnificationSpec spec = mService.mAccessibilityController
                         .getMagnificationSpecForWindowLocked(mWin);
                 if (spec != null && !spec.isNop()) {
@@ -962,7 +1000,7 @@
 
         MagnificationSpec spec = null;
         //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
+        if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
             spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
         }
         if (spec != null) {
@@ -1057,7 +1095,7 @@
         } else if (w.mDecorFrame.isEmpty()) {
             // Windows without policy decor aren't cropped.
             w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
-        } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) {
+        } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.isAnimating()) {
             // If we're animating, the wallpaper crop should only be updated at the end of the
             // animation.
             mTmpClipRect.set(w.mSystemDecorRect);
@@ -1097,12 +1135,8 @@
         // 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 for docked window, otherwise the animating window will be suddenly cut off.
 
-        if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
-            adjustCropToStackBounds(w, clipRect, isFreeformResizing);
-        }
+        adjustCropToStackBounds(w, clipRect, isFreeformResizing);
 
         w.transformFromScreenToSurfaceSpace(clipRect);
 
@@ -1115,100 +1149,57 @@
     private void adjustCropToStackBounds(WindowState w, Rect clipRect, boolean isFreeformResizing) {
         final AppWindowToken appToken = w.mAppToken;
         final Task task = w.getTask();
-        // We don't apply the the stack bounds to the window that is being replaced, because it was
-        // 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 (task != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
-            TaskStack stack = task.mStack;
-            stack.getBounds(mTmpStackBounds);
-            // 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 int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
-                    w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
-            final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
-                    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, frameX + clipRect.left) - frameX);
-            clipRect.top = Math.max(0,
-                    Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
-            clipRect.right = Math.max(0,
-                    Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
-            clipRect.bottom = Math.max(0,
-                    Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+        if (task == null || !appToken.mCropWindowsToStack) {
+            return;
         }
+
+        // We don't apply the stack bounds crop if:
+        // 1. The window is currently animating docked mode, otherwise the animating window will be
+        // suddenly cut off.
+        // 2. The window that is being replaced during animation, because it was 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 (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace())) {
+            return;
+        }
+
+        final TaskStack stack = task.mStack;
+        stack.getDimBounds(mTmpStackBounds);
+        // 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 int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
+                w.mFrame.left + mWin.mXOffset - w.getAttrs().surfaceInsets.left;
+        final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
+                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, frameX + clipRect.left) - frameX);
+        clipRect.top = Math.max(0,
+                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
+        clipRect.right = Math.max(0,
+                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
+        clipRect.bottom = Math.max(0,
+                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
     }
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
 
-        float left = w.mShownPosition.x;
-        float top = w.mShownPosition.y;
+        mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
+        calculateSurfaceBounds(w, w.getAttrs());
 
-        int width;
-        int height;
-        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-            // for a scaled surface, we always want the requested
-            // size.
-            width  = w.mRequestedWidth;
-            height = w.mRequestedHeight;
-        } else {
-            // When we're doing a drag-resizing, request a surface that's fullscreen size,
-            // so that we don't need to reallocate during the process. This also prevents
-            // buffer drops due to size mismatch.
-            final DisplayInfo displayInfo = w.getDisplayInfo();
-
-            // In freeform resize mode, put surface at 0/0.
-            if (w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
-                left = 0;
-                top = 0;
-            }
-            if (displayInfo != null && w.isDragResizing()) {
-                width = displayInfo.logicalWidth;
-                height = displayInfo.logicalHeight;
-            } else {
-                width = w.mCompatFrame.width();
-                height = w.mCompatFrame.height();
-            }
-        }
-
-        // Something is wrong and SurfaceFlinger will not like this,
-        // try to revert to sane values
-        if (width < 1) {
-            width = 1;
-        }
-        if (height < 1) {
-            height = 1;
-        }
-
-        // Adjust for surface insets.
-        final LayoutParams attrs = w.getAttrs();
-        final int displayId = w.getDisplayId();
-        float scale = 1.0f;
-        // Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
-            MagnificationSpec spec =
-                    mService.mAccessibilityController.getMagnificationSpecForWindowLocked(w);
-            if (spec != null && !spec.isNop()) {
-                scale = spec.scale;
-            }
-        }
-
-        width += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
-        height += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
-        left -= scale * attrs.surfaceInsets.left;
-        top -= scale * attrs.surfaceInsets.top;
-
-        mSurfaceController.setPositionInTransaction(left, top, recoveringMemory);
-        mSurfaceResized = mSurfaceController.setSizeInTransaction(width, height,
+        mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, recoveringMemory);
+        mSurfaceResized = mSurfaceController.setSizeInTransaction(
+                mTmpSize.width(), mTmpSize.height(),
                 mDsDx * w.mHScale, mDtDx * w.mVScale,
                 mDsDy * w.mHScale, mDtDy * w.mVScale,
                 recoveringMemory);
 
         if (mSurfaceResized) {
+            mReportSurfaceResized = true;
             mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
             w.applyDimLayerIfNeeded();
@@ -1287,6 +1278,7 @@
 
             if (prepared && mLastHidden && mDrawState == HAS_DRAWN) {
                 if (showSurfaceRobustlyLocked()) {
+                    mAnimator.requestRemovalOfReplacedWindows(w);
                     mLastHidden = false;
                     if (mIsWallpaper) {
                         mWallpaperControllerLocked.dispatchWallpaperVisibility(w, true);
@@ -1327,6 +1319,10 @@
     }
 
     void setTransparentRegionHintLocked(final Region region) {
+        if (mSurfaceController == null) {
+            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
+            return;
+        }
         mSurfaceController.setTransparentRegionHint(region);
     }
 
@@ -1373,10 +1369,16 @@
     }
 
     void setOpaqueLocked(boolean isOpaque) {
+        if (mSurfaceController == null) {
+            return;
+        }
         mSurfaceController.setOpaque(isOpaque);
     }
 
     void setSecureLocked(boolean isSecure) {
+        if (mSurfaceController == null) {
+            return;
+        }
         mSurfaceController.setSecure(isSecure);
     }
 
@@ -1426,7 +1428,7 @@
             // Force the show in the next prepareSurfaceLocked() call.
             mLastAlpha = -1;
             if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
-                Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);
+                Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + mWin);
             mDrawState = HAS_DRAWN;
             mService.scheduleAnimationLocked();
 
@@ -1472,12 +1474,8 @@
                 }
                 mWin.mAppToken.updateReportedVisibilityLocked();
             }
-
-            mWin.maybeRemoveReplacedWindow();
-
             return true;
         }
-
         return false;
     }
 
@@ -1513,7 +1511,7 @@
         applyAnimationLocked(transit, true);
         //TODO (multidisplay): Magnification is supported only for the default display.
         if (mService.mAccessibilityController != null
-                && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                && mWin.getDisplayId() == DEFAULT_DISPLAY) {
             mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
         }
     }
@@ -1614,7 +1612,7 @@
         fadeOut.setDuration(fadeDuration);
         fadeOut.setStartOffset(elapsed);
         newAnimation.addAnimation(fadeOut);
-        newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh);
+        newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDx, mAnimDy);
         mAnimation = newAnimation;
     }
 
@@ -1688,4 +1686,13 @@
         mSurfaceController.destroyInTransaction();
         mSurfaceController = null;
     }
+
+    void setMoveAnimation(int left, int top) {
+        final Animation a = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.window_move_from_decor);
+        setAnimation(a);
+        mAnimDx = mWin.mLastFrame.left - left;
+        mAnimDy = mWin.mLastFrame.top - top;
+        mAnimateMove = true;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 2e2be5c..f8b8d6c 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -23,7 +23,6 @@
 import static com.android.server.wm.WindowManagerService.HIDE_STACK_CRAWLS;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -57,7 +56,6 @@
     private float mSurfaceAlpha = 0;
 
     private int mSurfaceLayer = 0;
-    private int mSurfaceFormat;
 
     // Surface flinger doesn't support crop rectangles where width or height is non-positive.
     // However, we need to somehow handle the situation where the cropping would completely hide
@@ -72,7 +70,6 @@
 
         mSurfaceW = w;
         mSurfaceH = h;
-        mSurfaceFormat = format;
 
         title = name;
 
@@ -389,6 +386,11 @@
         pw.print(" x "); pw.println(mSurfaceH);
     }
 
+    @Override
+    public String toString() {
+        return mSurfaceControl.toString();
+    }
+
     static class SurfaceTrace extends SurfaceControl {
         private final static String SURFACE_TAG = "SurfaceTrace";
         private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 1f49e97..979f55b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,7 +17,6 @@
 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;
@@ -57,7 +56,6 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -542,11 +540,8 @@
 
         mService.scheduleAnimationLocked();
 
-        if (DEBUG_WINDOW_TRACE) {
-            Slog.e(TAG,
-                    "performSurfacePlacementInner exit: animating="
-                            + mService.mAnimator.mAnimating);
-        }
+        if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
+                "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
     }
 
     private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays,
@@ -682,20 +677,17 @@
                 final WindowStateAnimator winAnimator = w.mWinAnimator;
 
                 // If the window has moved due to its containing content frame changing, then
-                // notify the listeners and optionally animate it.
+                // notify the listeners and optionally animate it. Simply checking a change of
+                // position is not enough, because being move due to dock divider is not a trigger
+                // for animation.
                 if (w.hasMoved()) {
                     // Frame has moved, containing content frame has also moved, and we're not
                     // currently animating... let's do something.
                     final int left = w.mFrame.left;
                     final int top = w.mFrame.top;
-                    if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) {
-                        Animation a = AnimationUtils.loadAnimation(mService.mContext,
-                                com.android.internal.R.anim.window_move_from_decor);
-                        winAnimator.setAnimation(a);
-                        winAnimator.mAnimDw = w.mLastFrame.left - left;
-                        winAnimator.mAnimDh = w.mLastFrame.top - top;
-                        winAnimator.mAnimateMove = true;
-                        winAnimator.mAnimatingMove = true;
+                    if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+                            && !w.isDragResizing()) {
+                        winAnimator.setMoveAnimation(left, top);
                     }
 
                     //TODO (multidisplay): Accessibility supported only for the default display.
@@ -932,6 +924,13 @@
                     win.prelayout();
                     mService.mPolicy.layoutWindowLw(win, null);
                     win.mLayoutSeq = seq;
+
+                    // Window frames may have changed. Update dim layer with the new bounds.
+                    final Task task = win.getTask();
+                    if (task != null) {
+                        displayContent.mDimLayerController.updateDimLayer(task);
+                    }
+
                     if (DEBUG_LAYOUT) Slog.v(TAG,
                             "  LAYOUT: mFrame="
                             + win.mFrame + " mContainingFrame="
@@ -986,7 +985,7 @@
             }
         }
 
-        // Window frames may have changed.  Tell the input dispatcher about it.
+        // Window frames may have changed. Tell the input dispatcher about it.
         mService.mInputMonitor.setUpdateInputWindowsNeededLw();
         if (updateInputWindows) {
             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
@@ -1177,7 +1176,7 @@
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             SurfaceControl.openTransaction();
             try {
-                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
                 SurfaceControl.closeTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
@@ -1190,9 +1189,13 @@
                 int layer = -1;
                 for (int j = 0; j < wtoken.windows.size(); j++) {
                     final WindowState win = wtoken.windows.get(j);
-                    // Clearing the mExiting flag before entering animation. It will be set
-                    // to true if app window is removed, or window relayout to invisible.
-                    win.mExiting = false;
+                    // Clearing the mExiting flag before entering animation. It will be set to true
+                    // if app window is removed, or window relayout to invisible. We don't want to
+                    // clear it out for windows that get replaced, because the animation depends on
+                    // the flag to remove the replaced window.
+                    if (win.mAppToken == null || !win.mAppToken.mWillReplaceWindow) {
+                        win.mExiting = false;
+                    }
                     if (win.mWinAnimator.mAnimLayer > layer) {
                         layer = win.mWinAnimator.mAnimLayer;
                     }
@@ -1232,9 +1235,7 @@
                 true /*updateInputWindows*/);
         mService.mFocusMayChange = false;
         mService.notifyActivityDrawnForKeyguard();
-        return FINISH_LAYOUT_REDO_LAYOUT
-                | FINISH_LAYOUT_REDO_CONFIG;
-
+        return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
     }
 
     private boolean transitionGoodToGo(int appsCount) {
@@ -1458,7 +1459,7 @@
                     appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
                 }
                 mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             }
         }
     }
@@ -1472,6 +1473,7 @@
         final int taskId = appToken.mTask.mTaskId;
         Bitmap thumbnailHeader = mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
         if (thumbnailHeader == null || thumbnailHeader.getConfig() == Bitmap.Config.ALPHA_8) {
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
             return;
         }
         // This thumbnail animation is very special, we need to have
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 64278ed..03fbd19 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -22,32 +22,69 @@
 
 #include <utils/misc.h>
 #include <utils/Log.h>
-#include <hardware_legacy/vibrator.h>
+#include <hardware/vibrator.h>
 
 #include <stdio.h>
 
 namespace android
 {
 
+static hw_module_t *gVibraModule = NULL;
+static vibrator_device_t *gVibraDevice = NULL;
+
+static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
+{
+    if (gVibraModule != NULL) {
+        return;
+    }
+
+    int err = hw_get_module(VIBRATOR_HARDWARE_MODULE_ID, (hw_module_t const**)&gVibraModule);
+
+    if (err) {
+        ALOGE("Couldn't load %s module (%s)", VIBRATOR_HARDWARE_MODULE_ID, strerror(-err));
+    } else {
+        if (gVibraModule) {
+            vibrator_open(gVibraModule, &gVibraDevice);
+        }
+    }
+}
+
 static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
 {
-    return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE;
+    if (gVibraModule && gVibraDevice) {
+        return JNI_TRUE;
+    } else {
+        return JNI_FALSE;
+    }
 }
 
 static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
 {
-    // ALOGI("vibratorOn\n");
-    vibrator_on(timeout_ms);
+    if (gVibraDevice) {
+        int err = gVibraDevice->vibrator_on(gVibraDevice, timeout_ms);
+        if (err != 0) {
+            ALOGE("The hw module failed in vibrator_on: %s", strerror(-err));
+        }
+    } else {
+        ALOGW("Tried to vibrate but there is no vibrator device.");
+    }
 }
 
 static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
 {
-    // ALOGI("vibratorOff\n");
-    vibrator_off();
+    if (gVibraDevice) {
+        int err = gVibraDevice->vibrator_off(gVibraDevice);
+        if (err != 0) {
+            ALOGE("The hw module failed in vibrator_off(): %s", strerror(-err));
+        }
+    } else {
+        ALOGW("Tried to stop vibrating but there is no vibrator device.");
+    }
 }
 
 static const JNINativeMethod method_table[] = {
     { "vibratorExists", "()Z", (void*)vibratorExists },
+    { "vibratorInit", "()V", (void*)vibratorInit },
     { "vibratorOn", "(J)V", (void*)vibratorOn },
     { "vibratorOff", "()V", (void*)vibratorOff }
 };
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index be190cb..b5654ee 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -152,18 +152,23 @@
             getInputWindowHandleObjLocalRef(env);
 }
 
-static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
-        SpriteIcon* outSpriteIcon) {
-    PointerIcon pointerIcon;
+static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextObj, int32_t style,
+        PointerIcon* outPointerIcon, SpriteIcon* outSpriteIcon) {
     status_t status = android_view_PointerIcon_loadSystemIcon(env,
-            contextObj, style, &pointerIcon);
+            contextObj, style, outPointerIcon);
     if (!status) {
-        pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, kN32_SkColorType);
-        outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
-        outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+        outPointerIcon->bitmap.copyTo(&outSpriteIcon->bitmap, kN32_SkColorType);
+        outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX;
+        outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY;
     }
 }
 
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+                                   SpriteIcon* outSpriteIcon) {
+    PointerIcon pointerIcon;
+    loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
+}
+
 enum {
     WM_ACTION_PASS_TO_USER = 1,
 };
@@ -238,7 +243,8 @@
     /* --- PointerControllerPolicyInterface implementation --- */
 
     virtual void loadPointerResources(PointerResources* outResources);
-    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources);
+    virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
+            std::map<int32_t, PointerAnimation>* outAnimationResources);
     virtual int32_t getDefaultPointerIconId();
 
 private:
@@ -1041,14 +1047,31 @@
             &outResources->spotAnchor);
 }
 
-void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources) {
+void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
+        std::map<int32_t, PointerAnimation>* outAnimationResources) {
     JNIEnv* env = jniEnv();
 
     for (int iconId = POINTER_ICON_STYLE_CONTEXT_MENU; iconId <= POINTER_ICON_STYLE_GRABBING;
              ++iconId) {
-        loadSystemIconAsSprite(env, mContextObj, iconId, &((*outResources)[iconId]));
+        PointerIcon pointerIcon;
+        loadSystemIconAsSpriteWithPointerIcon(
+                env, mContextObj, iconId, &pointerIcon, &((*outResources)[iconId]));
+        if (!pointerIcon.bitmapFrames.empty()) {
+            PointerAnimation& animationData = (*outAnimationResources)[iconId];
+            size_t numFrames = pointerIcon.bitmapFrames.size() + 1;
+            animationData.durationPerFrame =
+                    milliseconds_to_nanoseconds(pointerIcon.durationPerFrame);
+            animationData.animationFrames.reserve(numFrames);
+            animationData.animationFrames.push_back(SpriteIcon(
+                    pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            for (size_t i = 0; i < numFrames - 1; ++i) {
+              animationData.animationFrames.push_back(SpriteIcon(
+                      pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            }
+        }
     }
-    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_NULL, &((*outResources)[POINTER_ICON_STYLE_NULL]));
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_NULL,
+            &((*outResources)[POINTER_ICON_STYLE_NULL]));
 }
 
 int32_t NativeInputManager::getDefaultPointerIconId() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eaccbc5..4c15809 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -60,6 +60,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -201,31 +202,6 @@
     private static final int STATUS_BAR_DISABLE2_MASK =
             StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-    private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
-    static {
-        DEVICE_OWNER_USER_RESTRICTIONS = new ArraySet<>();
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_USB_FILE_TRANSFER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_TETHERING);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_NETWORK_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FACTORY_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADD_USER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FUN);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CREATE_WINDOWS);
-    }
-
-    // The following user restrictions cannot be changed by any active admin, including device
-    // owner and profile owner.
-    private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
-    static {
-        IMMUTABLE_USER_RESTRICTIONS = new ArraySet<>();
-        IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
-    }
-
     private static final Set<String> SECURE_SETTINGS_WHITELIST;
     private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
     private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
@@ -306,6 +282,11 @@
         public void onBootPhase(int phase) {
             mService.systemReady(phase);
         }
+
+        @Override
+        public void onStartUser(int userHandle) {
+            mService.onStartUser(userHandle);
+        }
     }
 
     public static class DevicePolicyData {
@@ -436,7 +417,7 @@
                 "cross-profile-widget-providers";
         private static final String TAG_PROVIDER = "provider";
         private static final String TAG_PACKAGE_LIST_ITEM  = "item";
-
+        private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
         private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
 
         final DeviceAdminInfo info;
@@ -509,6 +490,9 @@
         // allowed.
         List<String> permittedInputMethods;
 
+        // List of package names to keep cached.
+        List<String> keepUninstalledPackages;
+
         // TODO: review implementation decisions with frameworks team
         boolean specifiesGlobalProxy = false;
         String globalProxySpec = null;
@@ -694,6 +678,7 @@
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
             writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+            writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
             if (hasUserRestrictions()) {
                 UserRestrictionsUtils.writeRestrictions(
                         out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -807,6 +792,8 @@
                     permittedAccessiblityServices = readPackageList(parser, tag);
                 } else if (TAG_PERMITTED_IMES.equals(tag)) {
                     permittedInputMethods = readPackageList(parser, tag);
+                } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
+                    keepUninstalledPackages = readPackageList(parser, tag);
                 } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
                     UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions());
                 } else {
@@ -1001,20 +988,23 @@
                     pw.println(disabledKeyguardFeatures);
             pw.print(prefix); pw.print("crossProfileWidgetProviders=");
                     pw.println(crossProfileWidgetProviders);
-            if (!(permittedAccessiblityServices == null)) {
+            if (permittedAccessiblityServices != null) {
                 pw.print(prefix); pw.print("permittedAccessibilityServices=");
-                        pw.println(permittedAccessiblityServices.toString());
+                    pw.println(permittedAccessiblityServices);
             }
-            if (!(permittedInputMethods == null)) {
+            if (permittedInputMethods != null) {
                 pw.print(prefix); pw.print("permittedInputMethods=");
-                        pw.println(permittedInputMethods.toString());
+                    pw.println(permittedInputMethods);
+            }
+            if (keepUninstalledPackages != null) {
+                pw.print(prefix); pw.print("keepUninstalledPackages=");
+                    pw.println(keepUninstalledPackages);
             }
             pw.print(prefix); pw.println("userRestrictions:");
             UserRestrictionsUtils.dumpRestrictions(pw, prefix + "  ", userRestrictions);
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1060,11 +1050,8 @@
             }
         }
         if (removed) {
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                synchronized (DevicePolicyManagerService.this) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
+            // The removed admin might have disabled camera, so update user restrictions.
+            pushUserRestrictions(userHandle);
         }
     }
 
@@ -1092,6 +1079,10 @@
             return LocalServices.getService(UserManagerInternal.class);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -1350,8 +1341,6 @@
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
             updateDeviceOwnerLocked();
-
-            // TODO Notify UM to update restrictions (?)
         }
     }
 
@@ -1403,6 +1392,9 @@
             migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
                     /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
 
+            // Push DO user restrictions to user manager.
+            pushUserRestrictions(UserHandle.USER_SYSTEM);
+
             mOwners.setDeviceOwnerUserRestrictionsMigrated();
         }
 
@@ -1432,6 +1424,13 @@
 
                     migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
                             exceptionList);
+
+                    // Note if a secondary user has no PO but has a DA that disables camera, we
+                    // don't get here and won't push the camera user restriction to UserManager
+                    // here.  That's okay because we'll push user restrictions anyway when a user
+                    // starts.  But we still do it because we want to let user manager persist
+                    // upon migration.
+                    pushUserRestrictions(userId);
                 }
 
                 mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
@@ -1612,25 +1611,17 @@
     @VisibleForTesting
     boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
             int userId) {
-        boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
-        boolean ownsProfile = (getProfileOwner(userId) != null
-                && getProfileOwner(userId).getPackageName()
-                    .equals(admin.info.getPackageName()));
+        final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
+        final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
 
         if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || (ownsDevice && ownsProfile)) {
-                return true;
-            }
+            return ownsDevice;
         } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
-            if ((userId == UserHandle.USER_SYSTEM && ownsDevice) || ownsProfile) {
-                return true;
-            }
+            // DO always has the PO power.
+            return ownsDevice || ownsProfile;
         } else {
-            if (admin.info.usesPolicy(reqPolicy)) {
-                return true;
-            }
+            return admin.info.usesPolicy(reqPolicy);
         }
-        return false;
     }
 
     void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -1717,12 +1708,9 @@
                                 updateMaximumTimeToLockLocked(policy);
                                 policy.mRemovingAdmins.remove(adminReceiver);
                             }
-                            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                                synchronized (DevicePolicyManagerService.this) {
-                                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(
-                                            userHandle);
-                                }
-                            }
+                            // The removed admin might have disabled camera, so update user
+                            // restrictions.
+                            pushUserRestrictions(userHandle);
                         }
                     });
         }
@@ -2121,18 +2109,20 @@
         getUserData(UserHandle.USER_SYSTEM);
         loadOwners();
         cleanUpOldUsers();
+
+        onStartUser(UserHandle.USER_SYSTEM);
+
         // Register an observer for watching for user setup complete.
         new SetupContentObserver(mHandler).register(mContext.getContentResolver());
         // Initialize the user setup state, to handle the upgrade case.
         updateUserSetupComplete();
 
-        // Update the screen capture disabled cache in the window manager
-        List<UserInfo> users = mUserManager.getUsers(true);
-        final int N = users.size();
-        for (int i = 0; i < N; i++) {
-            int userHandle = users.get(i).id;
-            updateScreenCaptureDisabledInWindowManager(userHandle,
-                    getScreenCaptureDisabled(null, userHandle));
+        List<String> packageList;
+        synchronized (this) {
+            packageList = getKeepUninstalledPackagesLocked();
+        }
+        if (packageList != null) {
+            mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
         }
     }
 
@@ -2155,6 +2145,12 @@
         }
     }
 
+    private void onStartUser(int userId) {
+        updateScreenCaptureDisabledInWindowManager(userId,
+                getScreenCaptureDisabled(null, userId));
+        pushUserRestrictions(userId);
+    }
+
     private void cleanUpOldUsers() {
         // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
         // before reboot
@@ -2441,8 +2437,11 @@
                 return;
             }
             if (admin.getUid() != mInjector.binderGetCallingUid()) {
-                // Active device owners must remain active admins.
-                if (isDeviceOwner(adminReceiver)) {
+                // Active device/profile owners must remain active admins.
+                if (isDeviceOwner(adminReceiver, userHandle)
+                        || isProfileOwner(adminReceiver, userHandle)) {
+                    Slog.e(LOG_TAG, "Device/profile owner cannot be removed: component=" +
+                            adminReceiver);
                     return;
                 }
                 mContext.enforceCallingOrSelfPermission(
@@ -3187,21 +3186,64 @@
     }
 
     @Override
-    public boolean resetPassword(String passwordOrNull, int flags) {
+    public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
         if (!mHasFeature) {
             return false;
         }
-        final int userHandle = UserHandle.getCallingUserId();
-        enforceNotManagedProfile(userHandle, "reset the password");
+        final int callingUid = mInjector.binderGetCallingUid();
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            if (mUserManager.getCredentialOwnerProfile(userHandle) != userHandle) {
+                throw new SecurityException("You can not change password for this profile because"
+                    + " it shares the password with the owner profile");
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
 
         String password = passwordOrNull != null ? passwordOrNull : "";
 
         int quality;
         synchronized (this) {
-            // This api can only be called by an active device admin,
-            // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(null,
-                    DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
+            // If caller has PO (or DO), it can clear the password, so see if that's the case
+            // first.
+            ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked(
+                    null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid);
+            if (admin == null) {
+                // Otherwise, make sure the caller has any active admin with the right policy.
+                admin = getActiveAdminForCallerLocked(null,
+                        DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
+            }
+
+            final ComponentName adminComponent = admin.info.getComponent();
+
+            // As of N, only profile owners and device owners can reset the password.
+            if (!(isProfileOwner(adminComponent, userHandle)
+                    || isDeviceOwner(adminComponent, userHandle))) {
+                final boolean preN = getTargetSdk(admin.info.getPackageName(), userHandle)
+                        < android.os.Build.VERSION_CODES.N;
+                // As of N, password resetting to empty/null is not allowed anymore.
+                // TODO Should we allow DO/PO to set an empty password?
+                if (TextUtils.isEmpty(password)) {
+                    if (!preN) {
+                        throw new SecurityException("Cannot call with null password");
+                    } else {
+                        Slog.e(LOG_TAG, "Cannot call with null password");
+                        return false;
+                    }
+                }
+                // As of N, password cannot be changed by the admin if it is already set.
+                if (isLockScreenSecureUnchecked(userHandle)) {
+                    if (!preN) {
+                        throw new SecurityException("Admin cannot change current password");
+                    } else {
+                        Slog.e(LOG_TAG, "Admin cannot change current password");
+                        return false;
+                    }
+                }
+            }
             quality = getPasswordQuality(null, userHandle);
             if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                 int realQuality = LockPatternUtils.computePasswordQuality(password);
@@ -3287,7 +3329,6 @@
             }
         }
 
-        int callingUid = mInjector.binderGetCallingUid();
         DevicePolicyData policy = getUserData(userHandle);
         if (policy.mPasswordOwner >= 0 && policy.mPasswordOwner != callingUid) {
             Slog.w(LOG_TAG, "resetPassword: already set by another uid and not entered by user");
@@ -3303,9 +3344,9 @@
 
         // Don't do this with the lock held, because it is going to call
         // back in to the service.
-        long ident = mInjector.binderClearCallingIdentity();
+        ident = mInjector.binderClearCallingIdentity();
         try {
-            LockPatternUtils utils = new LockPatternUtils(mContext);
+            LockPatternUtils utils = mInjector.newLockPatternUtils();
             if (!TextUtils.isEmpty(password)) {
                 utils.saveLockPassword(password, null, quality, userHandle);
             } else {
@@ -3330,6 +3371,15 @@
         return true;
     }
 
+    private boolean isLockScreenSecureUnchecked(int userId) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            return mInjector.newLockPatternUtils().isSecure(userId);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     private void setDoNotAskCredentialsOnBoot() {
         synchronized (this) {
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
@@ -3685,10 +3735,11 @@
     }
 
     @Override
-    public void wipeData(int flags, final int userHandle) {
+    public void wipeData(int flags) {
         if (!mHasFeature) {
             return;
         }
+        final int userHandle = mInjector.userHandleGetCallingUserId();
         enforceCrossUserPermission(userHandle);
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -3701,8 +3752,7 @@
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-                    if (userHandle != UserHandle.USER_SYSTEM
-                            || !isDeviceOwner(admin.info.getComponent())) {
+                    if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
                         throw new SecurityException(
                                "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
                     }
@@ -3806,7 +3856,7 @@
         }
         enforceCrossUserPermission(userHandle);
         // Managed Profile password can only be changed when per user encryption is present.
-        if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (!StorageManager.isFileBasedEncryptionEnabled()) {
             enforceNotManagedProfile(userHandle, "set the active password");
         }
 
@@ -4261,15 +4311,18 @@
         }
     }
 
-    private void updateScreenCaptureDisabledInWindowManager(int userHandle, boolean disabled) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+    private void updateScreenCaptureDisabledInWindowManager(final int userHandle,
+            final boolean disabled) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
+                }
+            }
+        });
     }
 
     /**
@@ -4325,7 +4378,7 @@
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        final int userHandle = UserHandle.getCallingUserId();
+        final int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
@@ -4335,15 +4388,7 @@
             }
         }
         // Tell the user manager that the restrictions have changed.
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                if (isDeviceOwner(who)) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                } else {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
-        }
+        pushUserRestrictions(userHandle);
     }
 
     /**
@@ -4352,6 +4397,11 @@
      */
     @Override
     public boolean getCameraDisabled(ComponentName who, int userHandle) {
+        return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true);
+    }
+
+    private boolean getCameraDisabled(ComponentName who, int userHandle,
+            boolean mergeDeviceOwnerRestriction) {
         if (!mHasFeature) {
             return false;
         }
@@ -4361,9 +4411,11 @@
                 return (admin != null) ? admin.disableCamera : false;
             }
             // First, see if DO has set it.  If so, it's device-wide.
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            if (deviceOwner != null && deviceOwner.disableCamera) {
-                return true;
+            if (mergeDeviceOwnerRestriction) {
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner != null && deviceOwner.disableCamera) {
+                    return true;
+                }
             }
 
             // Then check each device admin on the user.
@@ -4461,6 +4513,42 @@
     }
 
     @Override
+    public void setKeepUninstalledPackages(ComponentName who, List<String> packageList) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(packageList, "packageList is null");
+        final int userHandle = UserHandle.getCallingUserId();
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            admin.keepUninstalledPackages = packageList;
+            saveSettingsLocked(userHandle);
+            mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
+        }
+    }
+
+    @Override
+    public List<String> getKeepUninstalledPackages(ComponentName who) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        if (!mHasFeature) {
+            return null;
+        }
+        // TODO In split system user mode, allow apps on user 0 to query the list
+        synchronized (this) {
+            // Check if this is the device owner who is calling
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            return getKeepUninstalledPackagesLocked();
+        }
+    }
+
+    private List<String> getKeepUninstalledPackagesLocked() {
+        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+        return (deviceOwner != null) ? deviceOwner.keepUninstalledPackages : null;
+    }
+
+    @Override
     public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
         if (!mHasFeature) {
             return false;
@@ -4499,24 +4587,17 @@
         }
     }
 
-    public boolean isDeviceOwner(ComponentName who) {
-        if (!mHasFeature) {
-            return false;
-        }
+    public boolean isDeviceOwner(ComponentName who, int userId) {
         synchronized (this) {
-            return mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerComponent().equals(who);
+            return mOwners.hasDeviceOwner()
+                    && mOwners.getDeviceOwnerUserId() == userId
+                    && mOwners.getDeviceOwnerComponent().equals(who);
         }
     }
 
-    @Override
-    public boolean isDeviceOwnerPackage(String packageName) {
-        if (!mHasFeature) {
-            return false;
-        }
-        synchronized (this) {
-            return mOwners.hasDeviceOwner()
-                    && mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName);
-        }
+    public boolean isProfileOwner(ComponentName who, int userId) {
+        final ComponentName profileOwner = getProfileOwner(userId);
+        return who != null && who.equals(profileOwner);
     }
 
     @Override
@@ -4563,6 +4644,7 @@
                 return admin;
             }
         }
+        Slog.wtf(LOG_TAG, "Active admin for device owner not found. component=" + component);
         return null;
     }
 
@@ -4583,6 +4665,11 @@
             throw new SecurityException("clearDeviceOwner can only be called by the device owner");
         }
         synchronized (this) {
+            final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+            if (admin != null) {
+                admin.disableCamera = false;
+                admin.userRestrictions = null;
+            }
             clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
 
             mOwners.clearDeviceOwner();
@@ -4628,6 +4715,7 @@
         final ActiveAdmin admin =
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
+            admin.disableCamera = false;
             admin.userRestrictions = null;
             clearUserPoliciesLocked(callingUser);
             final int userId = callingUser.getIdentifier();
@@ -4636,6 +4724,30 @@
         }
     }
 
+    @Override
+    public boolean setDeviceOwnerLockScreenInfo(ComponentName who, String info) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        if (!mHasFeature) {
+            return false;
+        }
+
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            long token = mInjector.binderClearCallingIdentity();
+            try {
+                new LockPatternUtils(mContext).setDeviceOwnerInfo(info);
+            } finally {
+                mInjector.binderRestoreCallingIdentity(token);
+            }
+            return true;
+        }
+    }
+
+    @Override
+    public String getDeviceOwnerLockScreenInfo() {
+        return new LockPatternUtils(mContext).getDeviceOwnerInfo();
+    }
+
     private void clearUserPoliciesLocked(UserHandle userHandle) {
         int userId = userHandle.getIdentifier();
         // Reset some of the user-specific policies
@@ -4650,9 +4762,7 @@
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userHandle.getIdentifier());
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
-            }
+            pushUserRestrictions(userHandle.getIdentifier());
         } catch (RemoteException re) {
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -4941,31 +5051,30 @@
             return;
         }
 
-        final Printer p = new PrintWriterPrinter(pw);
-
         synchronized (this) {
-            p.println("Current Device Policy Manager state:");
+            pw.println("Current Device Policy Manager state:");
             mOwners.dump("  ", pw);
             int userCount = mUserData.size();
             for (int u = 0; u < userCount; u++) {
                 DevicePolicyData policy = getUserData(mUserData.keyAt(u));
-                p.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
+                pw.println();
+                pw.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
                 final int N = policy.mAdminList.size();
                 for (int i=0; i<N; i++) {
                     ActiveAdmin ap = policy.mAdminList.get(i);
                     if (ap != null) {
-                        pw.print("  "); pw.print(ap.info.getComponent().flattenToShortString());
+                        pw.print("    "); pw.print(ap.info.getComponent().flattenToShortString());
                                 pw.println(":");
-                        ap.dump("    ", pw);
+                        ap.dump("      ", pw);
                     }
                 }
                 if (!policy.mRemovingAdmins.isEmpty()) {
-                    p.println("  Removing Device Admins (User " + policy.mUserHandle + "): "
+                    pw.println("    Removing Device Admins (User " + policy.mUserHandle + "): "
                             + policy.mRemovingAdmins);
                 }
 
                 pw.println(" ");
-                pw.print("  mPasswordOwner="); pw.println(policy.mPasswordOwner);
+                pw.print("    mPasswordOwner="); pw.println(policy.mPasswordOwner);
             }
         }
     }
@@ -5603,57 +5712,83 @@
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     @Override
     public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                ActiveAdmin activeAdmin =
-                        getActiveAdminForCallerLocked(who,
-                                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-                final 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);
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(who,
+                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+            if (isDeviceOwner) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
-                if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
-                    throw new SecurityException("User restriction " + key + " cannot be changed");
+            } else { // profile owner
+                if (!UserRestrictionsUtils.canProfileOwnerChange(key)) {
+                    throw new SecurityException("Profile owner cannot set user restriction " + key);
                 }
-
-                final long id = mInjector.binderClearCallingIdentity();
-                try {
-                    // Save the restriction to ActiveAdmin.
-                    // TODO When DO sets a restriction, it'll always be treated as device-wide.
-                    // If there'll be a policy that can be set by both, we'll need scoping support,
-                    // and need to have another Bundle in DO active admin to hold restrictions as
-                    // PO.
-                    activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
-                    saveSettingsLocked(userHandle);
-
-                    // Tell UserManager the new value.
-                    if (isDeviceOwner) {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                    } else {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                    }
-                } finally {
-                    mInjector.binderRestoreCallingIdentity(id);
-                }
-
-                sendChangedNotification(userHandle);
             }
+
+            // Save the restriction to ActiveAdmin.
+            activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+            saveSettingsLocked(userHandle);
+
+            pushUserRestrictions(userHandle);
+
+            sendChangedNotification(userHandle);
+        }
+    }
+
+    private void pushUserRestrictions(int userId) {
+        synchronized (this) {
+            final Bundle global;
+            final Bundle local = new Bundle();
+            if (mOwners.isDeviceOwnerUserId(userId)) {
+                global = new Bundle();
+
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner == null) {
+                    return; // Shouldn't happen.
+                }
+
+                UserRestrictionsUtils.sortToGlobalAndLocal(deviceOwner.userRestrictions,
+                        global, local);
+                // DO can disable camera globally.
+                if (deviceOwner.disableCamera) {
+                    global.putBoolean(UserManager.DISALLOW_CAMERA, true);
+                }
+            } else {
+                global = null;
+
+                ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+                if (profileOwner != null) {
+                    UserRestrictionsUtils.merge(local, profileOwner.userRestrictions);
+                }
+            }
+            // Also merge in *local* camera restriction.
+            if (getCameraDisabled(/* who= */ null,
+                    userId, /* mergeDeviceOwnerRestriction= */ false)) {
+                local.putBoolean(UserManager.DISALLOW_CAMERA, true);
+            }
+            mUserManagerInternal.setDevicePolicyUserRestrictions(userId, local, global);
         }
     }
 
     @Override
-    public Bundle getUserRestrictions(ComponentName who) {
+    public Bundle getUserRestrictions(ComponentName who, int userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+        enforceCrossUserPermission(userHandle);
         synchronized (this) {
-            final ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(who, userHandle);
+            if (activeAdmin == null) {
+                throw new SecurityException("No active admin: " + activeAdmin);
+            }
+            if (activeAdmin.getUid() != mInjector.binderGetCallingUid()) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+            }
             return activeAdmin.userRestrictions;
         }
     }
@@ -6108,9 +6243,8 @@
             Bundle adminExtras = new Bundle();
             adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
             for (ActiveAdmin admin : policy.mAdminList) {
-                boolean ownsDevice = isDeviceOwner(admin.info.getComponent());
-                boolean ownsProfile = (getProfileOwner(userHandle) != null
-                        && getProfileOwner(userHandle).equals(admin.info.getPackageName()));
+                final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userHandle);
+                final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userHandle);
                 if (ownsDevice || ownsProfile) {
                     if (isEnabled) {
                         sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING,
@@ -6162,13 +6296,12 @@
     @Override
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Preconditions.checkNotNull(who, "ComponentName is null");
-        int callingUserId = UserHandle.getCallingUserId();
-        final ContentResolver contentResolver = mContext.getContentResolver();
+        int callingUserId = mInjector.userHandleGetCallingUserId();
 
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
 
-            if (isDeviceOwner(who)) {
+            if (isDeviceOwner(who, mInjector.userHandleGetCallingUserId())) {
                 if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)) {
                     throw new SecurityException(String.format(
                             "Permission denial: Device owners cannot update %1$s", setting));
@@ -6388,37 +6521,6 @@
             }
         }
 
-        @Override
-        public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
-            synchronized (DevicePolicyManagerService.this) {
-                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-                final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
-
-                final Bundle deviceOwnerRestrictions =
-                        deviceOwner == null ? null : deviceOwner.userRestrictions;
-                final Bundle profileOwnerRestrictions =
-                        profileOwner == null ? null : profileOwner.userRestrictions;
-                final boolean cameraDisabled = getCameraDisabled(null, userId);
-
-                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
-                        && !cameraDisabled) {
-                    // No restrictions to merge.
-                    return inBundle;
-                }
-
-                final Bundle composed = new Bundle(inBundle);
-                UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
-                UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
-
-                // Also merge in the camera restriction.
-                if (cameraDisabled) {
-                    composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
-                }
-
-                return composed;
-            }
-        }
-
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
@@ -6480,13 +6582,26 @@
      * @param callerUid UID of the caller.
      * @return true if the caller is the device owner app
      */
-    private boolean isCallerDeviceOwner(int callerUid) {
-        String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
-        for (String pkg : pkgs) {
-            if (isDeviceOwnerPackage(pkg)) {
-                return true;
+    @VisibleForTesting
+    boolean isCallerDeviceOwner(int callerUid) {
+        synchronized (this) {
+            if (!mOwners.hasDeviceOwner()) {
+                return false;
+            }
+            if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
+                return false;
+            }
+            final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
+                    .getPackageName();
+            final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+
+            for (String pkg : pkgs) {
+                if (deviceOwnerPackageName.equals(pkg)) {
+                    return true;
+                }
             }
         }
+
         return false;
     }
 
@@ -6566,10 +6681,8 @@
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             long ident = mInjector.binderClearCallingIdentity();
             try {
-                final ApplicationInfo ai = mIPackageManager
-                        .getApplicationInfo(packageName, 0, user.getIdentifier());
-                final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
-                if (targetSdkVersion < android.os.Build.VERSION_CODES.M) {
+                if (getTargetSdk(packageName, user.getIdentifier())
+                        < android.os.Build.VERSION_CODES.M) {
                     return false;
                 }
                 final PackageManager packageManager = mContext.getPackageManager();
@@ -6643,11 +6756,28 @@
 
     @Override
     public boolean isProvisioningAllowed(String action) {
-        if (mOwners.hasDeviceOwner()) {
-            return false;
-        }
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
+            if (mOwners.hasDeviceOwner()) {
+                if (!mInjector.userManagerIsSplitSystemUser()) {
+                    // Only split-system-user systems support managed-profiles in combination with
+                    // device-owner.
+                    return false;
+                }
+                if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
+                    // Only system device-owner supports managed-profiles. Non-system device-owner
+                    // doesn't.
+                    return false;
+                }
+                if (callingUserId == UserHandle.USER_SYSTEM) {
+                    // Managed-profiles cannot be setup on the system user, only regular users.
+                    return false;
+                }
+            }
+            if (getProfileOwner(callingUserId) != null) {
+                // Managed user cannot have a managed profile.
+                return false;
+            }
             try {
                 if (!mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
                     return false;
@@ -6665,18 +6795,51 @@
             }
             return true;
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
-            if (getProfileOwner(callingUserId) != null) {
+            return isDeviceOwnerProvisioningAllowed(callingUserId);
+        } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
+            if (!mInjector.userManagerIsSplitSystemUser()) {
+                // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
                 return false;
             }
-            if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
-                return false;
-            }
-            if (callingUserId != UserHandle.USER_SYSTEM) {
-                // Device owner provisioning can only be initiated from system user.
+            if (hasUserSetupCompleted(callingUserId)) {
                 return false;
             }
             return true;
+        } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action)) {
+            if (!mInjector.userManagerIsSplitSystemUser()) {
+                // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
+                return false;
+            }
+            return isDeviceOwnerProvisioningAllowed(callingUserId);
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
     }
+
+    private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
+        if (mOwners.hasDeviceOwner()) {
+            return false;
+        }
+        if (getProfileOwner(callingUserId) != null) {
+            return false;
+        }
+        if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+            return false;
+        }
+        if (callingUserId != UserHandle.USER_SYSTEM) {
+            // Device owner provisioning can only be initiated from system user.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the target sdk version number that the given packageName was built for
+     * in the given user.
+     */
+    private int getTargetSdk(String packageName, int userId) throws RemoteException {
+        final ApplicationInfo ai = mIPackageManager
+                .getApplicationInfo(packageName, 0, userId);
+        final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
+        return targetSdkVersion;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index ded4422..435de7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -221,6 +221,10 @@
         return mDeviceOwner != null;
     }
 
+    boolean isDeviceOwnerUserId(int userId) {
+        return mDeviceOwner != null && mDeviceOwnerUserId == userId;
+    }
+
     boolean hasProfileOwner(int userId) {
         return getProfileOwnerComponent(userId) != null;
     }
@@ -625,20 +629,30 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
+        boolean needBlank = false;
         if (mDeviceOwner != null) {
             pw.println(prefix + "Device Owner: ");
             mDeviceOwner.dump(prefix + "  ", pw);
             pw.println(prefix + "  User ID: " + mDeviceOwnerUserId);
-            pw.println();
+            needBlank = true;
         }
         if (mSystemUpdatePolicy != null) {
+            if (needBlank) {
+                needBlank = false;
+                pw.println();
+            }
             pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy);
-            pw.println();
+            needBlank = true;
         }
         if (mProfileOwners != null) {
             for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
+                if (needBlank) {
+                    needBlank = false;
+                    pw.println();
+                }
                 pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): ");
                 entry.getValue().dump(prefix + "  ", pw);
+                needBlank = true;
             }
         }
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e32af5c..2f33d7c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -407,8 +407,6 @@
         mSystemServiceManager.startService(UsageStatsService.class);
         mActivityManagerService.setUsageStatsManager(
                 LocalServices.getService(UsageStatsManagerInternal.class));
-        // Update after UsageStatsService is available, needed before performBootDexOpt.
-        mPackageManagerService.getUsageStatsIfNoPackageUsageInfo();
 
         // Tracks whether the updatable WebView is in a ready state and watches for update installs.
         mSystemServiceManager.startService(WebViewUpdateService.class);
@@ -612,11 +610,11 @@
         // as appropriate.
         mSystemServiceManager.startService(UiModeManagerService.class);
 
-        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "PerformBootDexOpt");
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "PerformFstrimIfNeeded");
         try {
-            mPackageManagerService.performBootDexOpt();
+            mPackageManagerService.performFstrimIfNeeded();
         } catch (Throwable e) {
-            reportWtf("performing boot dexopt", e);
+            reportWtf("performing fstrim", e);
         }
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
@@ -1319,4 +1317,4 @@
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
         Slog.i(TAG, name);
     }
-}
\ No newline at end of file
+}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 8927bfa..6a255e5 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -291,6 +291,11 @@
      */
     abstract void finishPacket(ByteBuffer buffer);
 
+    // Set in unit tests, to ensure that the test does not break when run on different devices and
+    // on different releases.
+    static String testOverrideVendorId = null;
+    static String testOverrideHostname = null;
+
     protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
                          Inet4Address nextIp, Inet4Address relayIp,
                          byte[] clientMac, boolean broadcast) {
@@ -593,6 +598,16 @@
         buf.put((byte) 0xFF);
     }
 
+    private String getVendorId() {
+        if (testOverrideVendorId != null) return testOverrideVendorId;
+        return "android-dhcp-" + Build.VERSION.RELEASE;
+    }
+
+    private String getHostname() {
+        if (testOverrideHostname != null) return testOverrideHostname;
+        return SystemProperties.get("net.hostname");
+    }
+
     /**
      * Adds common client TLVs.
      *
@@ -601,8 +616,8 @@
      */
     protected void addCommonClientTlvs(ByteBuffer buf) {
         addTlv(buf, DHCP_MAX_MESSAGE_SIZE, (short) MAX_LENGTH);
-        addTlv(buf, DHCP_VENDOR_CLASS_ID, "android-dhcp-" + Build.VERSION.RELEASE);
-        addTlv(buf, DHCP_HOST_NAME, SystemProperties.get("net.hostname"));
+        addTlv(buf, DHCP_VENDOR_CLASS_ID, getVendorId());
+        addTlv(buf, DHCP_HOST_NAME, getHostname());
     }
 
     /**
diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
index 7e60bf1..2a967e6 100644
--- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java
@@ -29,6 +29,7 @@
 
 import junit.framework.TestCase;
 import libcore.util.HexEncoding;
+import java.util.Arrays;
 
 import static android.net.dhcp.DhcpPacket.*;
 
@@ -47,6 +48,11 @@
         return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
     }
 
+    public void setUp() {
+        DhcpPacket.testOverrideVendorId = "android-dhcp-???";
+        DhcpPacket.testOverrideHostname = "android-01234567890abcde";
+    }
+
     class TestDhcpPacket extends DhcpPacket {
         private byte mType;
         // TODO: Make this a map of option numbers to bytes instead.
@@ -584,4 +590,93 @@
         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);
     }
+
+    @SmallTest
+    public void testDiscoverPacket() throws Exception {
+        short secs = 7;
+        int transactionId = 0xdeadbeef;
+        byte[] hwaddr = {
+                (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
+        };
+        byte[] params = new byte[] {
+            DHCP_SUBNET_MASK,
+            DHCP_ROUTER,
+            DHCP_DNS_SERVER,
+            DHCP_DOMAIN_NAME,
+            DHCP_MTU,
+            DHCP_LEASE_TIME,
+        };
+
+        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+                DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
+                false /* do unicast */, params);
+
+        byte[] headers = new byte[] {
+            // Ethernet header.
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
+            (byte) 0x08, (byte) 0x00,
+            // IP header.
+            (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x52,
+            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
+            (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x8c,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            // UDP header.
+            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
+            (byte) 0x01, (byte) 0x3e, (byte) 0xd8, (byte) 0xa4,
+            // BOOTP.
+            (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
+            (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
+            (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b,
+            (byte) 0xb1, (byte) 0x7a
+        };
+        byte[] options = new byte[] {
+            // Magic cookie 0x63825363.
+            (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+            // Message type DISCOVER.
+            (byte) 0x35, (byte) 0x01, (byte) 0x01,
+            // Client identifier Ethernet, da:01:19:5b:b1:7a.
+            (byte) 0x3d, (byte) 0x07,
+                    (byte) 0x01,
+                    (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
+            // Max message size 1500.
+            (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc,
+            // Version "android-dhcp-???".
+            (byte) 0x3c, (byte) 0x10,
+                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?',
+            // Hostname "android-01234567890abcde"
+            (byte) 0x0c, (byte) 0x18,
+                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
+                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
+            // Requested parameter list.
+            (byte) 0x37, (byte) 0x06,
+                DHCP_SUBNET_MASK,
+                DHCP_ROUTER,
+                DHCP_DNS_SERVER,
+                DHCP_DOMAIN_NAME,
+                DHCP_MTU,
+                DHCP_LEASE_TIME,
+            // End options.
+            (byte) 0xff,
+            // Our packets are always of even length. TODO: find out why and possibly fix it.
+            (byte) 0x00
+        };
+        byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length];
+        assertTrue((expected.length & 1) == 0);
+        System.arraycopy(headers, 0, expected, 0, headers.length);
+        System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length);
+
+        byte[] actual = new byte[packet.limit()];
+        packet.get(actual);
+        String msg =
+                "Expected:\n  " + Arrays.toString(expected) +
+                "\nActual:\n  " + Arrays.toString(actual);
+        assertTrue(msg, Arrays.equals(expected, actual));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index b0296a0..c174a92 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -61,7 +61,7 @@
         b2.putBoolean("b2", true);
 
         SyncOperation op1 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -73,7 +73,7 @@
 
         // Same as op1 but different time infos
         SyncOperation op2 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -85,7 +85,7 @@
 
         // Same as op1 but different authority
         SyncOperation op3 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority2",
                 b1,
@@ -97,7 +97,7 @@
 
         // Same as op1 but different account
         SyncOperation op4 = new SyncOperation(account2, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -109,7 +109,7 @@
 
         // Same as op1 but different bundle
         SyncOperation op5 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b2,
@@ -131,21 +131,21 @@
         long soonFlex = 50;
         long after = 1500;
         long afterFlex = 100;
-        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval disjoint from and after op1.
-        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, after, afterFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval equivalent to op1, but expedited.
         Bundle b2 = new Bundle();
         b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, 0,
+        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, "foo", 0, 0,
                 "authority1", b2, -1, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval overlaps but not equivalent to op1.
-        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon + 100, soonFlex + 100, mUnimportantLong, mUnimportantLong, true);
 
         assertTrue(op1.compareTo(op2) == -1);
@@ -165,7 +165,8 @@
 
         Bundle withExpedited = new Bundle();
         withExpedited.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_USER_START,
+        SyncOperation op = new SyncOperation(mDummy, 0, 0, "foo", 0,
+                SyncOperation.REASON_USER_START,
                 mAuthority, withExpedited, fiveSecondsFromNow, twoSecondsFlex,
                 eightSeconds /* backoff */, fourSeconds /* delayUntil */, true);
         // Create another sync op to be rerun in 5 minutes.
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index ae1967e..b22eb53 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -41,8 +41,6 @@
 import java.io.FileOutputStream;
 import java.util.List;
 
-import com.android.server.content.SyncStorageEngine.EndPoint;
-
 public class SyncStorageEngineTest extends AndroidTestCase {
 
     protected Account account1;
@@ -96,7 +94,7 @@
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
         long time0 = 1000;
-        SyncOperation op = new SyncOperation(account, 0,
+        SyncOperation op = new SyncOperation(account, 0, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL,
                 authority,
@@ -112,7 +110,7 @@
     @MediumTest
     public void testAppendPending() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
@@ -140,19 +138,19 @@
      */
     public void testWritePendingOperationsLocked() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_IS_SYNCABLE,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 1000L /* runtime */, 57L /* flex */, 0 /* backoff */, 0 /* delayuntil */,
                 true /* expedited */);
         SyncOperation sop1 = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, defaultBundle,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
                 false /* expedited */);
         SyncOperation deleted = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_SYNC_AUTO,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
@@ -456,14 +454,14 @@
 
         // Test service component read
         List<PeriodicSync> syncs = engine.getPeriodicSyncs(
-                new SyncStorageEngine.EndPoint(syncService1, 0));
+                new SyncStorageEngine.EndPoint(syncService1, 0, 0));
         assertEquals(1, syncs.size());
         assertEquals(true, engine.getIsTargetServiceActive(syncService1, 0));
     }
 
     @SmallTest
     public void testComponentSettings() throws Exception {
-        EndPoint target1 = new EndPoint(syncService1, 0);
+        EndPoint target1 = new EndPoint(syncService1, 0, 0);
         engine.updateOrAddPeriodicSync(target1, dayPoll, dayFuzz, Bundle.EMPTY);
         
         engine.setIsTargetServiceActive(target1.service, 0, true);
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 2c01b8a..56d6fc0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -20,8 +20,8 @@
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
-import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.media.IAudioService;
 import android.os.Looper;
 import android.os.PowerManagerInternal;
@@ -113,6 +113,11 @@
         }
 
         @Override
+        PackageManagerInternal getPackageManagerInternal() {
+            return context.packageManagerInternal;
+        }
+
+        @Override
         PowerManagerInternal getPowerManagerInternal() {
             return context.powerManagerInternal;
         }
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 36980e3..0bd4896 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,12 +25,8 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.content.pm.PackageInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Pair;
@@ -50,6 +46,7 @@
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -768,21 +765,42 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+                );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
         );
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
@@ -794,6 +812,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -804,7 +828,68 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE are PO restrictions, but when
+        // DO sets them, the scope is global.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+
+
+        // More tests.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_CAMERA, UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        // Set up another DA and let it disable camera.  Now DISALLOW_CAMERA will only be applied
+        // locally.
+        dpm.setCameraDisabled(admin1, false);
+        reset(mContext.userManagerInternal);
+
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        dpm.setCameraDisabled(admin2, true);
+
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
         // TODO Make sure restrictions are written to the file.
     }
 
@@ -818,7 +903,21 @@
         );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                        UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -837,7 +936,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -854,6 +958,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -865,69 +975,29 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even
+        // though when DO sets them they'll be applied globally.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA,
+                        UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         // TODO Make sure restrictions are written to the file.
     }
-
-    public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
-        final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        assertTrue(in == actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        assertTrue(in == actual);
-    }
-
-    public void testGetComposedUserRestrictions() 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));
-
-        dpm.addUserRestriction(admin1, "rest1");
-        dpm.addUserRestriction(admin1, "rest2");
-
-        // Set PO on CALLER_USER_HANDLE.
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
-        setAsProfileOwner(admin2);
-
-        dpm.addUserRestriction(admin2, "restA");
-        dpm.addUserRestriction(admin2, "restB");
-
-        final Bundle in = DpmTestUtils.newRestrictions("abc");
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE + 1, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-    }
 }
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 cc337b0..bb1e06d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.media.IAudioService;
 import android.os.Bundle;
@@ -205,6 +206,7 @@
     public final SystemPropertiesForMock systemProperties;
     public final UserManager userManager;
     public final UserManagerInternal userManagerInternal;
+    public final PackageManagerInternal packageManagerInternal;
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
     public final PowerManagerInternal powerManagerInternal;
@@ -237,6 +239,7 @@
         userManager = mock(UserManager.class);
         userManagerInternal = mock(UserManagerInternal.class);
         userManagerForMock = mock(UserManagerForMock.class);
+        packageManagerInternal = mock(PackageManagerInternal.class);
         powerManager = mock(PowerManagerForMock.class);
         powerManagerInternal = mock(PowerManagerInternal.class);
         notificationManager = mock(NotificationManager.class);
@@ -260,9 +263,6 @@
 
         // System user is always running.
         setUserRunning(UserHandle.USER_SYSTEM, true);
-
-        // This method must return an object.
-        when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
     }
 
     public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 5008fbf..58db192 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -17,8 +17,12 @@
 
 import com.google.common.base.Objects;
 
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserRestrictionsUtils;
+
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import org.hamcrest.BaseMatcher;
@@ -77,4 +81,37 @@
         };
         return Mockito.argThat(m);
     }
+
+    public static Bundle checkUserRestrictions(String... keys) {
+        final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
+        final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) return false;
+                return UserRestrictionsUtils.areEqual((Bundle) item, expected);
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("User restrictions=" + getRestrictionsAsString(expected));
+            }
+        };
+        return Mockito.argThat(m);
+    }
+
+    private static String getRestrictionsAsString(Bundle b) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+
+        if (b != null) {
+            String sep = "";
+            for (String key : b.keySet()) {
+                sb.append(sep);
+                sep = ",";
+                sb.append(key);
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }
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 4e11762..423c4d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -19,21 +19,7 @@
 import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
 
 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;
-import java.io.File;
-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;
 
 /**
  * Tests for the DeviceOwner object that saves & loads device and policy owner information.
diff --git a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
index b6742a1..d798518 100644
--- a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.updates;
 
+import com.android.internal.util.HexDump;
+
 import android.content.Context;
 import android.content.Intent;
 import android.test.AndroidTestCase;
@@ -128,7 +130,7 @@
         MessageDigest dgst = MessageDigest.getInstance("SHA512");
         byte[] encoded = content.getBytes();
         byte[] fingerprint = dgst.digest(encoded);
-        return IntegralToString.bytesToHexString(fingerprint, false);
+        return HexDump.toHexString(fingerprint, false);
     }
 
     private static String getHashOfCurrentContent() throws Exception {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 013154b..2b0919b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -215,11 +215,11 @@
             mPowerManager = getContext().getSystemService(PowerManager.class);
 
             mScreenOnSystemTimeSnapshot = System.currentTimeMillis();
-            synchronized (this) {
+            synchronized (mLock) {
                 mScreenOnTime = readScreenOnTimeLocked();
             }
-            mDisplayManager.registerDisplayListener(mDisplayListener, null);
-            synchronized (this) {
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            synchronized (mLock) {
                 updateDisplayLocked();
             }
         } else if (phase == PHASE_BOOT_COMPLETED) {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e0f95cf..c734fab 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -772,7 +772,8 @@
         }
 
         private void updateUsbNotification() {
-            if (mNotificationManager == null || !mUseUsbNotification) return;
+            if (mNotificationManager == null || !mUseUsbNotification
+                    || ("0".equals(SystemProperties.get("persist.charging.notify")))) return;
             int id = 0;
             Resources r = mContext.getResources();
             if (mConnected || mHostConnected) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a96c164..8fee91f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -291,21 +291,26 @@
                 String curService = Settings.Secure.getStringForUser(
                         mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser);
                 ComponentName serviceComponent = null;
+                ServiceInfo serviceInfo = null;
                 if (curService != null && !curService.isEmpty()) {
                     try {
                         serviceComponent = ComponentName.unflattenFromString(curService);
-                    } catch (RuntimeException e) {
+                        serviceInfo = AppGlobals.getPackageManager()
+                                .getServiceInfo(serviceComponent, 0, mCurUser);
+                    } catch (RuntimeException | RemoteException e) {
                         Slog.wtf(TAG, "Bad voice interaction service name " + curService, e);
                         serviceComponent = null;
+                        serviceInfo = null;
                     }
                 }
+
                 if (force || mImpl == null || mImpl.mUser != mCurUser
                         || !mImpl.mComponent.equals(serviceComponent)) {
                     mSoundTriggerHelper.stopAllRecognitions();
                     if (mImpl != null) {
                         mImpl.shutdownLocked();
                     }
-                    if (serviceComponent != null) {
+                    if (serviceComponent != null && serviceInfo != null) {
                         mImpl = new VoiceInteractionManagerServiceImpl(mContext,
                                 UiThread.getHandler(), this, mCurUser, serviceComponent);
                         mImpl.startLocked();
@@ -357,7 +362,6 @@
                             }
                         } catch (PackageManager.NameNotFoundException e) {
                             Slog.w(TAG, "Failure looking up interaction service " + comp);
-                        } catch (RemoteException e) {
                         }
                     }
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 30296e1..109d214 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -116,7 +116,7 @@
         VoiceInteractionServiceInfo info;
         try {
             info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
-        } catch (RemoteException|PackageManager.NameNotFoundException e) {
+        } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Voice interaction service not found: " + service, e);
             mInfo = null;
             mSessionComponentName = null;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6b1b6296..3b281e2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4848,4 +4848,21 @@
         }
         return null;
     }
-}
+
+    /**
+     * Returns the service state information on specified subscription. Callers require
+     * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+     * @hide
+     */
+    public ServiceState getServiceStateForSubscriber(int subId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getServiceStateForSubscriber(subId, getOpPackageName());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index dcece26..8172e94 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -21,9 +21,10 @@
 import android.telecom.PhoneAccount;
 import android.telephony.CellInfo;
 import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.RadioAccessFamily;
-import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
 import java.util.List;
@@ -1005,4 +1006,12 @@
      * Return the modem activity info.
      */
     ModemActivityInfo getModemActivityInfo();
+
+    /**
+     * Get the service state on specified subscription
+     * @param subId Subscription id
+     * @param callingPackage The package making the call
+     * @return Service state on specified subscription.
+     */
+    ServiceState getServiceStateForSubscriber(int subId, String callingPackage);
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 572cc6f..a183de5 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -59,6 +59,9 @@
     public static final int PHONE_TYPE_SIP = RILConstants.SIP_PHONE;
     public static final int PHONE_TYPE_THIRD_PARTY = RILConstants.THIRD_PARTY_PHONE;
     public static final int PHONE_TYPE_IMS = RILConstants.IMS_PHONE;
+    // Currently this is used only to differentiate CDMA and CDMALTE Phone in GsmCdma* files. For
+    // anything outside of that, a cdma + lte phone is still CDMA_PHONE
+    public static final int PHONE_TYPE_CDMA_LTE = RILConstants.CDMA_LTE_PHONE;
 
     // Modes for LTE_ON_CDMA
     public static final int LTE_ON_CDMA_UNKNOWN = RILConstants.LTE_ON_CDMA_UNKNOWN;
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 7088be8..3c4c04b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -118,6 +118,7 @@
     int SIP_PHONE  = 3;
     int THIRD_PARTY_PHONE = 4;
     int IMS_PHONE = 5;
+    int CDMA_LTE_PHONE = 6;
 
     int LTE_ON_CDMA_UNKNOWN = -1;
     int LTE_ON_CDMA_FALSE = 0;
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index b12795c..68bde35 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := core-libart core-junit framework
+LOCAL_JAVA_LIBRARIES := core-oj core-libart core-junit framework
 LOCAL_STATIC_JAVA_LIBRARIES := junit-runner
 
 LOCAL_MODULE:= android.test.runner
diff --git a/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
new file mode 100644
index 0000000..81648d9
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/raw/test_debug_ca.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIJAP/YiWztz/J7MA0GCSqGSIb3DQEBCwUAMCcxFjAUBgNV
+BAMMDVRlc3QgZGVidWcgQ0ExDTALBgNVBAoMBEFPU1AwHhcNMTUxMTA5MjEyNjQ2
+WhcNMTgwODI5MjEyNjQ2WjAnMRYwFAYDVQQDDA1UZXN0IGRlYnVnIENBMQ0wCwYD
+VQQKDARBT1NQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPFmkOJj
+ehjfvdDr2qTcBWNqNATrW1SuM88Vj00ubUFQ4tZElozj8YnQOw1FeC79c1k88b8R
+6jcqYYp/mw2JYoD6yWcFPHo5BplIpk0EhIUARH/aeoclHvsUN2GGDyTO0vf0CfJn
+9Wp6lSLjyq7V/6tYdk+0cL632t56MHp8TCO+AaveYP1T8JZqx0/50xNcsK7lIqNa
+ctWyRGFxR4ifdVsgkw9WhAB/Ow2uOwN9uLGqzsCd+yXW2weX52EIivoTGZfJo+U8
+Fi0ygnCHBv2jsJA7yWLhHmZ4ijsVtfutIKmN0w+DHkl6S25girXhy0zJp/1QvHGm
+jaF60V1gw471jQIDAQABo1AwTjAdBgNVHQ4EFgQUoq66jncy83L5eeyW1g78s/uq
+iyQwHwYDVR0jBBgwFoAUoq66jncy83L5eeyW1g78s/uqiyQwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAohytuH4CdX0gO8EGVDRVurRH7LO69lwd/6Iw
+hJ1lIK/mzj5RM2itVGTkintyHCLu5giVkHn4FHg4X9qzZaTPOcXv9ntQNS2nacZe
+bY8nfhsAhstJT4nIOWHE3FrZkMDOK6nZHIzfscX3V/VVq5MeA+WzXwmKp6MBNr+E
+oUegXCGjd26Bl6SFz3rD7Qh+dzSTtyf/ECzXaMjpZu3k6fb4EgRz6vdBCHKKtpv6
+Mxcr0nLwdI6LnAGXvJLV4sj+l6Ngg00EeyorG8ATgtmsUrXXOR1e+yDCQv6fjQfs
+CWYztECAUE9hfCXJwb0TBrq9YeJAvcO7iE6S0Pq+X3xNtetE1A==
+-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
new file mode 100644
index 0000000..8da9317
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_basic.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <base-config>
+    <trust-anchors>
+    </trust-anchors>
+  </base-config>
+  <debug-overrides>
+    <trust-anchors>
+      <certificates src="system" />
+    </trust-anchors>
+  </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
new file mode 100644
index 0000000..24eed7a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_domain.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <domain-config>
+    <domain>android.com</domain>
+    <trust-anchors>
+      <certificates src="@raw/ca_certs_pem" />
+    </trust-anchors>
+  </domain-config>
+  <debug-overrides>
+    <trust-anchors>
+      <certificates src="@raw/test_debug_ca" />
+    </trust-anchors>
+  </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml
new file mode 100644
index 0000000..ce0cbc8
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/debug_inherit.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <debug-overrides>
+    <trust-anchors>
+      <certificates src="@raw/test_debug_ca" />
+    </trust-anchors>
+  </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index 43c0e57..f7590fd 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -22,6 +22,7 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 
 import junit.framework.Assert;
 
@@ -69,8 +70,11 @@
 
     public static SSLContext getSSLContext(ConfigSource source) throws Exception {
         ApplicationConfig config = new ApplicationConfig(source);
+        TrustManagerFactory tmf =
+                TrustManagerFactory.getInstance("PKIX", new NetworkSecurityConfigProvider());
+        tmf.init(new RootTrustManagerFactorySpi.ApplicationConfigParameters(config));
         SSLContext context = SSLContext.getInstance("TLS");
-        context.init(null, new TrustManager[] {config.getTrustManager()}, null);
+        context.init(null, tmf.getTrustManagers(), null);
         return context;
     }
 }
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index f52a279..c6f3680 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -24,15 +24,23 @@
 import java.io.IOException;
 import java.net.Socket;
 import java.net.URL;
+import java.security.KeyStore;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Set;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 
 public class XmlConfigTests extends AndroidTestCase {
 
+    private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
+
     public void testEmptyConfigFile() throws Exception {
         XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
         ApplicationConfig appConfig = new ApplicationConfig(source);
@@ -274,6 +282,68 @@
         assertFalse(child.isCleartextTrafficPermitted());
     }
 
+    public void testDebugOverridesDisabled() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+        Set<TrustAnchor> anchors = config.getTrustAnchors();
+        MoreAsserts.assertEmpty(anchors);
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionFails(context, "android.com", 443);
+        TestUtils.assertConnectionFails(context, "developer.android.com", 443);
+    }
+
+    public void testBasicDebugOverrides() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+        Set<TrustAnchor> anchors = config.getTrustAnchors();
+        MoreAsserts.assertNotEmpty(anchors);
+        for (TrustAnchor anchor : anchors) {
+            assertTrue(anchor.overridesPins);
+        }
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+    }
+
+    public void testDebugOverridesWithDomain() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+        Set<TrustAnchor> anchors = config.getTrustAnchors();
+        boolean foundDebugCA = false;
+        for (TrustAnchor anchor : anchors) {
+            if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
+                foundDebugCA = true;
+                assertTrue(anchor.overridesPins);
+            }
+        }
+        assertTrue(foundDebugCA);
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+    }
+
+    public void testDebugInherit() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+        Set<TrustAnchor> anchors = config.getTrustAnchors();
+        boolean foundDebugCA = false;
+        for (TrustAnchor anchor : anchors) {
+            if (anchor.certificate.getSubjectDN().toString().equals(DEBUG_CA_SUBJ)) {
+                foundDebugCA = true;
+                assertTrue(anchor.overridesPins);
+            }
+        }
+        assertTrue(foundDebugCA);
+        assertTrue(anchors.size() > 1);
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+        TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
+    }
+
     private void testBadConfig(int configId) throws Exception {
         try {
             XmlConfigSource source = new XmlConfigSource(getContext(), configId);
@@ -310,4 +380,26 @@
     public void testBadConfig5() throws Exception {
         testBadConfig(R.xml.bad_config4);
     }
+
+    public void testTrustManagerKeystore() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        Provider provider = new NetworkSecurityConfigProvider();
+        TrustManagerFactory tmf =
+                TrustManagerFactory.getInstance("PKIX", provider);
+        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+        keystore.load(null);
+        int i = 0;
+        for (X509Certificate cert : SystemCertificateSource.getInstance().getCertificates()) {
+            keystore.setEntry(String.valueOf(i),
+                    new KeyStore.TrustedCertificateEntry(cert),
+                    null);
+            i++;
+        }
+        tmf.init(keystore);
+        TrustManager[] tms = tmf.getTrustManagers();
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(null, tms, null);
+        TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
+    }
 }
diff --git a/tests/RenderScriptTests/Fountain/Android.mk b/tests/RenderScriptTests/Fountain/Android.mk
deleted file mode 100644
index 0517aef..0000000
--- a/tests/RenderScriptTests/Fountain/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-LOCAL_SDK_VERSION := 17
-
-LOCAL_PACKAGE_NAME := RsFountain
-
-include $(BUILD_PACKAGE)
diff --git a/tests/RenderScriptTests/Fountain/AndroidManifest.xml b/tests/RenderScriptTests/Fountain/AndroidManifest.xml
deleted file mode 100644
index d19b8c3..0000000
--- a/tests/RenderScriptTests/Fountain/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.rs.fountain">
-    <uses-sdk android:minSdkVersion="14" />
-    <application
-        android:label="RsFountain"
-        android:hardwareAccelerated="true"
-        android:icon="@drawable/test_pattern">
-        <activity android:name="Fountain">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/RenderScriptTests/Fountain/_index.html b/tests/RenderScriptTests/Fountain/_index.html
deleted file mode 100644
index 223242f..0000000
--- a/tests/RenderScriptTests/Fountain/_index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<p>An example that renders many dots on the screen that follow a user's touch. The dots fall 
-to the bottom of the screen when the user releases the finger.</p>
-
-
-
diff --git a/tests/RenderScriptTests/Fountain/res/drawable/test_pattern.png b/tests/RenderScriptTests/Fountain/res/drawable/test_pattern.png
deleted file mode 100644
index e7d1455..0000000
--- a/tests/RenderScriptTests/Fountain/res/drawable/test_pattern.png
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/Fountain.java b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/Fountain.java
deleted file mode 100644
index 311455a..0000000
--- a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/Fountain.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountain;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings.System;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.ListView;
-
-import java.lang.Runtime;
-
-public class Fountain extends Activity {
-    //EventListener mListener = new EventListener();
-
-    private static final String LOG_TAG = "libRS_jni";
-    private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = false;
-
-    private FountainView mView;
-
-    // get the current looper (from your Activity UI thread for instance
-
-
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Create our Preview view and set it as the content of our
-        // Activity
-        mView = new FountainView(this);
-        setContentView(mView);
-    }
-
-    @Override
-    protected void onResume() {
-        Log.e("rs", "onResume");
-
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onResume();
-        mView.resume();
-    }
-
-    @Override
-    protected void onPause() {
-        Log.e("rs", "onPause");
-
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onPause();
-        mView.pause();
-
-
-
-        //Runtime.getRuntime().exit(0);
-    }
-
-
-    static void log(String message) {
-        if (LOG_ENABLED) {
-            Log.v(LOG_TAG, message);
-        }
-    }
-
-
-}
-
diff --git a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainRS.java b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainRS.java
deleted file mode 100644
index 646c807..0000000
--- a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainRS.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountain;
-
-import android.content.res.Resources;
-import android.renderscript.*;
-import android.util.Log;
-
-
-public class FountainRS {
-    public static final int PART_COUNT = 50000;
-
-    public FountainRS() {
-    }
-
-    private Resources mRes;
-    private RenderScriptGL mRS;
-    private ScriptC_fountain mScript;
-    public void init(RenderScriptGL rs, Resources res) {
-        mRS = rs;
-        mRes = res;
-
-        ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
-        pfb.setVaryingColor(true);
-        rs.bindProgramFragment(pfb.create());
-
-        ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);//
- //                                                        Allocation.USAGE_GRAPHICS_VERTEX);
-
-        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
-        smb.addVertexAllocation(points.getAllocation());
-        smb.addIndexSetType(Mesh.Primitive.POINT);
-        Mesh sm = smb.create();
-
-        mScript = new ScriptC_fountain(mRS, mRes, R.raw.fountain);
-        mScript.set_partMesh(sm);
-        mScript.bind_point(points);
-        mRS.bindRootScript(mScript);
-    }
-
-    boolean holdingColor[] = new boolean[10];
-    public void newTouchPosition(float x, float y, float pressure, int id) {
-        if (id >= holdingColor.length) {
-            return;
-        }
-        int rate = (int)(pressure * pressure * 500.f);
-        if (rate > 500) {
-            rate = 500;
-        }
-        if (rate > 0) {
-            mScript.invoke_addParticles(rate, x, y, id, !holdingColor[id]);
-            holdingColor[id] = true;
-        } else {
-            holdingColor[id] = false;
-        }
-
-    }
-}
diff --git a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java
deleted file mode 100644
index 98cec55..0000000
--- a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/FountainView.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountain;
-
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-import android.renderscript.RenderScriptGL;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-public class FountainView extends RSSurfaceView {
-
-    public FountainView(Context context) {
-        super(context);
-        //setFocusable(true);
-    }
-
-    private RenderScriptGL mRS;
-    private FountainRS mRender;
-
-    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
-        super.surfaceChanged(holder, format, w, h);
-        if (mRS == null) {
-            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
-            mRS = createRenderScriptGL(sc);
-            mRS.setSurface(holder, w, h);
-            mRender = new FountainRS();
-            mRender.init(mRS, getResources());
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mRS != null) {
-            mRS = null;
-            destroyRenderScriptGL();
-        }
-    }
-
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev)
-    {
-        int act = ev.getActionMasked();
-        if (act == ev.ACTION_UP) {
-            mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
-            return false;
-        } else if (act == MotionEvent.ACTION_POINTER_UP) {
-            // only one pointer going up, we can get the index like this
-            int pointerIndex = ev.getActionIndex();
-            int pointerId = ev.getPointerId(pointerIndex);
-            mRender.newTouchPosition(0, 0, 0, pointerId);
-        }
-        int count = ev.getHistorySize();
-        int pcount = ev.getPointerCount();
-
-        for (int p=0; p < pcount; p++) {
-            int id = ev.getPointerId(p);
-            mRender.newTouchPosition(ev.getX(p),
-                                     ev.getY(p),
-                                     ev.getPressure(p),
-                                     id);
-
-            for (int i=0; i < count; i++) {
-                mRender.newTouchPosition(ev.getHistoricalX(p, i),
-                                         ev.getHistoricalY(p, i),
-                                         ev.getHistoricalPressure(p, i),
-                                         id);
-            }
-        }
-        return true;
-    }
-}
-
-
diff --git a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/fountain.rs b/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/fountain.rs
deleted file mode 100644
index 151b689..0000000
--- a/tests/RenderScriptTests/Fountain/src/com/example/android/rs/fountain/fountain.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Fountain test script
-#pragma version(1)
-#pragma rs_fp_relaxed
-
-#pragma rs java_package_name(com.example.android.rs.fountain)
-
-#pragma stateFragment(parent)
-
-#include "rs_graphics.rsh"
-
-static int newPart = 0;
-rs_mesh partMesh;
-
-typedef struct __attribute__((packed, aligned(4))) Point {
-    float2 delta;
-    float2 position;
-    uchar4 color;
-} Point_t;
-Point_t *point;
-
-int root() {
-    float dt = min(rsGetDt(), 0.1f);
-    rsgClearColor(0.f, 0.f, 0.f, 1.f);
-    const float height = rsgGetHeight();
-    const int size = rsAllocationGetDimX(rsGetAllocation(point));
-    float dy2 = dt * (10.f);
-    Point_t * p = point;
-    for (int ct=0; ct < size; ct++) {
-        p->delta.y += dy2;
-        p->position += p->delta;
-        if ((p->position.y > height) && (p->delta.y > 0)) {
-            p->delta.y *= -0.3f;
-        }
-        p++;
-    }
-
-    rsgDrawMesh(partMesh);
-    return 1;
-}
-
-static float4 partColor[10];
-void addParticles(int rate, float x, float y, int index, bool newColor)
-{
-    if (newColor) {
-        partColor[index].x = rsRand(0.5f, 1.0f);
-        partColor[index].y = rsRand(1.0f);
-        partColor[index].z = rsRand(1.0f);
-    }
-    float rMax = ((float)rate) * 0.02f;
-    int size = rsAllocationGetDimX(rsGetAllocation(point));
-    uchar4 c = rsPackColorTo8888(partColor[index]);
-
-    Point_t * np = &point[newPart];
-    float2 p = {x, y};
-    while (rate--) {
-        float angle = rsRand(3.14f * 2.f);
-        float len = rsRand(rMax);
-        np->delta.x = len * sin(angle);
-        np->delta.y = len * cos(angle);
-        np->position = p;
-        np->color = c;
-        newPart++;
-        np++;
-        if (newPart >= size) {
-            newPart = 0;
-            np = &point[newPart];
-        }
-    }
-}
-
diff --git a/tests/RenderScriptTests/FountainFbo/Android.mk b/tests/RenderScriptTests/FountainFbo/Android.mk
deleted file mode 100644
index c0f3323..0000000
--- a/tests/RenderScriptTests/FountainFbo/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-# TODO: build fails with this set
-# LOCAL_SDK_VERSION := current
-
-LOCAL_PACKAGE_NAME := RsFountainFbo
-LOCAL_SDK_VERSION := 14
-
-include $(BUILD_PACKAGE)
diff --git a/tests/RenderScriptTests/FountainFbo/AndroidManifest.xml b/tests/RenderScriptTests/FountainFbo/AndroidManifest.xml
deleted file mode 100644
index 082744b..0000000
--- a/tests/RenderScriptTests/FountainFbo/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.rs.fountainfbo">
-    <uses-sdk android:minSdkVersion="14" />
-    <application
-        android:label="RsFountainFbo"
-        android:hardwareAccelerated="true"
-        android:icon="@drawable/test_pattern">
-        <activity android:name="FountainFbo">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/RenderScriptTests/FountainFbo/_index.html b/tests/RenderScriptTests/FountainFbo/_index.html
deleted file mode 100644
index 5508657..0000000
--- a/tests/RenderScriptTests/FountainFbo/_index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<p>An example that renders many dots on the screen that follow a user's touch. The dots fall 
-to the bottom of the screen when no touch is detected. This example modifies
-the <a href="../Fountain/index.html">Fountain</a> sample to include rendering to a
-a framebuffer object as well as the default framebuffer.</p>
-
-
-
diff --git a/tests/RenderScriptTests/FountainFbo/res/drawable/test_pattern.png b/tests/RenderScriptTests/FountainFbo/res/drawable/test_pattern.png
deleted file mode 100644
index e7d1455..0000000
--- a/tests/RenderScriptTests/FountainFbo/res/drawable/test_pattern.png
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFbo.java b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFbo.java
deleted file mode 100644
index d8ba30f..0000000
--- a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFbo.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountainfbo;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-public class FountainFbo extends Activity {
-    private static final String LOG_TAG = "libRS_jni";
-    private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = false;
-
-    private FountainFboView mView;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        /* Create our Preview view and set it as the content of our Activity */
-        mView = new FountainFboView(this);
-        setContentView(mView);
-    }
-
-    @Override
-    protected void onResume() {
-        Log.e("rs", "onResume");
-
-        /* Ideally a game should implement onResume() and onPause()
-         to take appropriate action when the activity loses focus */
-        super.onResume();
-        mView.resume();
-    }
-
-    @Override
-    protected void onPause() {
-        Log.e("rs", "onPause");
-
-        /* Ideally a game should implement onResume() and onPause()
-        to take appropriate action when the activity loses focus */
-        super.onPause();
-        mView.pause();
-    }
-
-    static void log(String message) {
-        if (LOG_ENABLED) {
-            Log.v(LOG_TAG, message);
-        }
-    }
-}
-
diff --git a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboRS.java b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboRS.java
deleted file mode 100644
index 3bf3ff1..0000000
--- a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboRS.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountainfbo;
-
-import android.content.res.Resources;
-import android.renderscript.Allocation;
-import android.renderscript.Element;
-import android.renderscript.Mesh;
-import android.renderscript.ProgramFragment;
-import android.renderscript.ProgramFragmentFixedFunction;
-import android.renderscript.RenderScriptGL;
-import android.renderscript.Type;
-
-public class FountainFboRS {
-    public static final int PART_COUNT = 50000;
-
-    public FountainFboRS() {
-    }
-
-    private Resources mRes;
-    private RenderScriptGL mRS;
-    private ScriptC_fountainfbo mScript;
-    private Allocation mColorBuffer;
-    private ProgramFragment mProgramFragment;
-    private ProgramFragment mTextureProgramFragment;
-    public void init(RenderScriptGL rs, Resources res) {
-      mRS = rs;
-      mRes = res;
-
-      ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);
-
-      Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
-      smb.addVertexAllocation(points.getAllocation());
-      smb.addIndexSetType(Mesh.Primitive.POINT);
-      Mesh sm = smb.create();
-
-      mScript = new ScriptC_fountainfbo(mRS, mRes, R.raw.fountainfbo);
-      mScript.set_partMesh(sm);
-      mScript.bind_point(points);
-
-      ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
-      pfb.setVaryingColor(true);
-      mProgramFragment = pfb.create();
-      mScript.set_gProgramFragment(mProgramFragment);
-
-      /* Second fragment shader to use a texture (framebuffer object) to draw with */
-      pfb.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE,
-          ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
-
-      /* Set the fragment shader in the Renderscript runtime */
-      mTextureProgramFragment = pfb.create();
-      mScript.set_gTextureProgramFragment(mTextureProgramFragment);
-
-      /* Create the allocation for the color buffer */
-      Type.Builder colorBuilder = new Type.Builder(mRS, Element.RGBA_8888(mRS));
-      colorBuilder.setX(256).setY(256);
-      mColorBuffer = Allocation.createTyped(mRS, colorBuilder.create(),
-      Allocation.USAGE_GRAPHICS_TEXTURE |
-      Allocation.USAGE_GRAPHICS_RENDER_TARGET);
-
-      /* Set the allocation in the Renderscript runtime */
-      mScript.set_gColorBuffer(mColorBuffer);
-
-      mRS.bindRootScript(mScript);
-  }
-
-    boolean holdingColor[] = new boolean[10];
-    public void newTouchPosition(float x, float y, float pressure, int id) {
-        if (id >= holdingColor.length) {
-            return;
-        }
-        int rate = (int)(pressure * pressure * 500.f);
-        if (rate > 500) {
-            rate = 500;
-        }
-        if (rate > 0) {
-            mScript.invoke_addParticles(rate, x, y, id, !holdingColor[id]);
-            holdingColor[id] = true;
-        } else {
-            holdingColor[id] = false;
-        }
-
-    }
-}
-
diff --git a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java
deleted file mode 100644
index 8636717..0000000
--- a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/FountainFboView.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.example.android.rs.fountainfbo;
-
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScriptGL;
-import android.content.Context;
-import android.view.SurfaceHolder;
-import android.view.MotionEvent;
-
-public class FountainFboView extends RSSurfaceView {
-
-    public FountainFboView(Context context) {
-        super(context);
-    }
-
-    private RenderScriptGL mRS;
-    private FountainFboRS mRender;
-
-    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
-        super.surfaceChanged(holder, format, w, h);
-        if (mRS == null) {
-            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
-            mRS = createRenderScriptGL(sc);
-            mRS.setSurface(holder, w, h);
-            mRender = new FountainFboRS();
-            mRender.init(mRS, getResources());
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        android.util.Log.e("rs", "onDetachedFromWindow");
-        if (mRS != null) {
-            mRS = null;
-            destroyRenderScriptGL();
-        }
-    }
-
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev)
-    {
-        int act = ev.getActionMasked();
-        if (act == ev.ACTION_UP) {
-            mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
-            return false;
-        } else if (act == MotionEvent.ACTION_POINTER_UP) {
-            // only one pointer going up, we can get the index like this
-            int pointerIndex = ev.getActionIndex();
-            int pointerId = ev.getPointerId(pointerIndex);
-            mRender.newTouchPosition(0, 0, 0, pointerId);
-        }
-        int count = ev.getHistorySize();
-        int pcount = ev.getPointerCount();
-
-        for (int p=0; p < pcount; p++) {
-            int id = ev.getPointerId(p);
-            mRender.newTouchPosition(ev.getX(p),
-                                     ev.getY(p),
-                                     ev.getPressure(p),
-                                     id);
-
-            for (int i=0; i < count; i++) {
-                mRender.newTouchPosition(ev.getHistoricalX(p, i),
-                                         ev.getHistoricalY(p, i),
-                                         ev.getHistoricalPressure(p, i),
-                                         id);
-            }
-        }
-        return true;
-    }
-}
-
-
diff --git a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/fountainfbo.rs b/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/fountainfbo.rs
deleted file mode 100644
index 763f6ba..0000000
--- a/tests/RenderScriptTests/FountainFbo/src/com/example/android/rs/fountainfbo/fountainfbo.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-// Fountain test script
-#pragma version(1)
-
-#pragma rs java_package_name(com.example.android.rs.fountainfbo)
-
-#pragma stateFragment(parent)
-
-#include "rs_graphics.rsh"
-
-static int newPart = 0;
-rs_mesh partMesh;
-rs_program_vertex gProgramVertex;
-
-//allocation for color buffer
-rs_allocation gColorBuffer;
-//fragment shader for rendering without a texture (used for rendering to framebuffer object)
-rs_program_fragment gProgramFragment;
-//fragment shader for rendering with a texture (used for rendering to default framebuffer)
-rs_program_fragment gTextureProgramFragment;
-
-typedef struct __attribute__((packed, aligned(4))) Point {
-    float2 delta;
-    float2 position;
-    uchar4 color;
-} Point_t;
-Point_t *point;
-
-int root() {
-    float dt = min(rsGetDt(), 0.1f);
-    rsgClearColor(0.f, 0.f, 0.f, 1.f);
-    const float height = rsgGetHeight();
-    const int size = rsAllocationGetDimX(rsGetAllocation(point));
-    float dy2 = dt * (10.f);
-    Point_t * p = point;
-    for (int ct=0; ct < size; ct++) {
-        p->delta.y += dy2;
-        p->position += p->delta;
-        if ((p->position.y > height) && (p->delta.y > 0)) {
-            p->delta.y *= -0.3f;
-        }
-        p++;
-    }
-    //Tell Renderscript runtime to render to the frame buffer object
-    rsgBindColorTarget(gColorBuffer, 0);
-
-    //Begin rendering on a white background
-    rsgClearColor(1.f, 1.f, 1.f, 1.f);
-    rsgDrawMesh(partMesh);
-
-    //When done, tell Renderscript runtime to stop rendering to framebuffer object
-    rsgClearAllRenderTargets();
-
-    //Bind a new fragment shader that declares the framebuffer object to be used as a texture
-    rsgBindProgramFragment(gTextureProgramFragment);
-
-    //Bind the framebuffer object to the fragment shader at slot 0 as a texture
-    rsgBindTexture(gTextureProgramFragment, 0, gColorBuffer);
-
-    //Draw a quad using the framebuffer object as the texture
-    float startX = 10, startY = 10;
-    float s = 256;
-    rsgDrawQuadTexCoords(startX, startY, 0, 0, 1,
-                         startX, startY + s, 0, 0, 0,
-                         startX + s, startY + s, 0, 1, 0,
-                         startX + s, startY, 0, 1, 1);
-
-    //Rebind the original fragment shader to render as normal
-    rsgBindProgramFragment(gProgramFragment);
-
-    //Render the main scene
-    rsgDrawMesh(partMesh);
-
-    return 1;
-}
-
-static float4 partColor[10];
-void addParticles(int rate, float x, float y, int index, bool newColor)
-{
-    if (newColor) {
-        partColor[index].x = rsRand(0.5f, 1.0f);
-        partColor[index].y = rsRand(1.0f);
-        partColor[index].z = rsRand(1.0f);
-    }
-    float rMax = ((float)rate) * 0.02f;
-    int size = rsAllocationGetDimX(rsGetAllocation(point));
-    uchar4 c = rsPackColorTo8888(partColor[index]);
-
-    Point_t * np = &point[newPart];
-    float2 p = {x, y};
-    while (rate--) {
-        float angle = rsRand(3.14f * 2.f);
-        float len = rsRand(rMax);
-        np->delta.x = len * sin(angle);
-        np->delta.y = len * cos(angle);
-        np->position = p;
-        np->color = c;
-        newPart++;
-        np++;
-        if (newPart >= size) {
-            newPart = 0;
-            np = &point[newPart];
-        }
-    }
-}
-
-
diff --git a/tests/RenderScriptTests/Fountain_v11/Android.mk b/tests/RenderScriptTests/Fountain_v11/Android.mk
deleted file mode 100644
index ac2690c..0000000
--- a/tests/RenderScriptTests/Fountain_v11/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-#LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
-
-LOCAL_PACKAGE_NAME := Fountain_v11
-LOCAL_SDK_VERSION := 11
-
-include $(BUILD_PACKAGE)
diff --git a/tests/RenderScriptTests/Fountain_v11/AndroidManifest.xml b/tests/RenderScriptTests/Fountain_v11/AndroidManifest.xml
deleted file mode 100644
index fcb4faf..0000000
--- a/tests/RenderScriptTests/Fountain_v11/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.fountain_v11">
-    <uses-sdk android:minSdkVersion="11" />
-    <application 
-        android:label="Fountain_v11"
-        android:icon="@drawable/test_pattern">
-        <activity android:name="Fountain_v11">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/RenderScriptTests/Fountain_v11/_index.html b/tests/RenderScriptTests/Fountain_v11/_index.html
deleted file mode 100644
index 223242f..0000000
--- a/tests/RenderScriptTests/Fountain_v11/_index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<p>An example that renders many dots on the screen that follow a user's touch. The dots fall 
-to the bottom of the screen when the user releases the finger.</p>
-
-
-
diff --git a/tests/RenderScriptTests/Fountain_v11/res/drawable/test_pattern.png b/tests/RenderScriptTests/Fountain_v11/res/drawable/test_pattern.png
deleted file mode 100644
index e7d1455..0000000
--- a/tests/RenderScriptTests/Fountain_v11/res/drawable/test_pattern.png
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainRS.java b/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainRS.java
deleted file mode 100644
index e858100..0000000
--- a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainRS.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.fountain_v11;
-
-import android.content.res.Resources;
-import android.renderscript.*;
-import android.util.Log;
-
-
-public class FountainRS {
-    public static final int PART_COUNT = 50000;
-
-    public FountainRS() {
-    }
-
-    private Resources mRes;
-    private RenderScriptGL mRS;
-    private ScriptC_fountain mScript;
-    public void init(RenderScriptGL rs, Resources res, int width, int height) {
-        mRS = rs;
-        mRes = res;
-
-        ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
-        pfb.setVaryingColor(true);
-        rs.bindProgramFragment(pfb.create());
-
-        ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);//
- //                                                        Allocation.USAGE_GRAPHICS_VERTEX);
-
-        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
-        smb.addVertexAllocation(points.getAllocation());
-        smb.addIndexSetType(Mesh.Primitive.POINT);
-        Mesh sm = smb.create();
-
-        mScript = new ScriptC_fountain(mRS, mRes, R.raw.fountain);
-        mScript.set_partMesh(sm);
-        mScript.bind_point(points);
-        mRS.bindRootScript(mScript);
-    }
-
-    boolean holdingColor[] = new boolean[10];
-    public void newTouchPosition(float x, float y, float pressure, int id) {
-        if (id >= holdingColor.length) {
-            return;
-        }
-        int rate = (int)(pressure * pressure * 500.f);
-        if (rate > 500) {
-            rate = 500;
-        }
-        if (rate > 0) {
-            mScript.invoke_addParticles(rate, x, y, id, !holdingColor[id]);
-            holdingColor[id] = true;
-        } else {
-            holdingColor[id] = false;
-        }
-
-    }
-}
diff --git a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainView.java b/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainView.java
deleted file mode 100644
index e82376c..0000000
--- a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/FountainView.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.fountain_v11;
-
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-import android.renderscript.RenderScriptGL;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-public class FountainView extends RSSurfaceView {
-
-    public FountainView(Context context) {
-        super(context);
-        //setFocusable(true);
-    }
-
-    private RenderScriptGL mRS;
-    private FountainRS mRender;
-
-    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
-        super.surfaceChanged(holder, format, w, h);
-        if (mRS == null) {
-            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
-            mRS = createRenderScriptGL(sc);
-            mRS.setSurface(holder, w, h);
-            mRender = new FountainRS();
-            mRender.init(mRS, getResources(), w, h);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mRS != null) {
-            mRS = null;
-            destroyRenderScriptGL();
-        }
-    }
-
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev)
-    {
-        int act = ev.getActionMasked();
-        if (act == ev.ACTION_UP) {
-            mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
-            return false;
-        } else if (act == MotionEvent.ACTION_POINTER_UP) {
-            // only one pointer going up, we can get the index like this
-            int pointerIndex = ev.getActionIndex();
-            int pointerId = ev.getPointerId(pointerIndex);
-            mRender.newTouchPosition(0, 0, 0, pointerId);
-        }
-        int count = ev.getHistorySize();
-        int pcount = ev.getPointerCount();
-
-        for (int p=0; p < pcount; p++) {
-            int id = ev.getPointerId(p);
-            mRender.newTouchPosition(ev.getX(p),
-                                     ev.getY(p),
-                                     ev.getPressure(p),
-                                     id);
-
-            for (int i=0; i < count; i++) {
-                mRender.newTouchPosition(ev.getHistoricalX(p, i),
-                                         ev.getHistoricalY(p, i),
-                                         ev.getHistoricalPressure(p, i),
-                                         id);
-            }
-        }
-        return true;
-    }
-}
-
-
diff --git a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/Fountain_v11.java b/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/Fountain_v11.java
deleted file mode 100644
index 2c07b27..0000000
--- a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/Fountain_v11.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.fountain_v11;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings.System;
-import android.util.Config;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.ListView;
-
-import java.lang.Runtime;
-
-public class Fountain_v11 extends Activity {
-    //EventListener mListener = new EventListener();
-
-    private static final String LOG_TAG = "libRS_jni";
-    private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
-
-    private FountainView mView;
-
-    // get the current looper (from your Activity UI thread for instance
-
-
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Create our Preview view and set it as the content of our
-        // Activity
-        mView = new FountainView(this);
-        setContentView(mView);
-    }
-
-    @Override
-    protected void onResume() {
-        Log.e("rs", "onResume");
-
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onResume();
-        mView.resume();
-    }
-
-    @Override
-    protected void onPause() {
-        Log.e("rs", "onPause");
-
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onPause();
-        mView.pause();
-
-
-
-        //Runtime.getRuntime().exit(0);
-    }
-
-
-    static void log(String message) {
-        if (LOG_ENABLED) {
-            Log.v(LOG_TAG, message);
-        }
-    }
-
-
-}
-
diff --git a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/fountain.rs b/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/fountain.rs
deleted file mode 100644
index 3b6c89a..0000000
--- a/tests/RenderScriptTests/Fountain_v11/src/com/android/fountain/fountain.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Fountain test script
-#pragma version(1)
-
-#pragma rs java_package_name(com.android.fountain_v11)
-
-#pragma stateFragment(parent)
-
-#include "rs_graphics.rsh"
-
-static int newPart = 0;
-rs_mesh partMesh;
-
-typedef struct __attribute__((packed, aligned(4))) Point {
-    float2 delta;
-    float2 position;
-    uchar4 color;
-} Point_t;
-Point_t *point;
-
-int root() {
-    float dt = min(rsGetDt(), 0.1f);
-    rsgClearColor(0.f, 0.f, 0.f, 1.f);
-    const float height = rsgGetHeight();
-    const int size = rsAllocationGetDimX(rsGetAllocation(point));
-    float dy2 = dt * (10.f);
-    Point_t * p = point;
-    for (int ct=0; ct < size; ct++) {
-        p->delta.y += dy2;
-        p->position += p->delta;
-        if ((p->position.y > height) && (p->delta.y > 0)) {
-            p->delta.y *= -0.3f;
-        }
-        p++;
-    }
-
-    rsgDrawMesh(partMesh);
-    return 1;
-}
-
-static float4 partColor[10];
-void addParticles(int rate, float x, float y, int index, bool newColor)
-{
-    if (newColor) {
-        partColor[index].x = rsRand(0.5f, 1.0f);
-        partColor[index].y = rsRand(1.0f);
-        partColor[index].z = rsRand(1.0f);
-    }
-    float rMax = ((float)rate) * 0.02f;
-    int size = rsAllocationGetDimX(rsGetAllocation(point));
-    uchar4 c = rsPackColorTo8888(partColor[index]);
-
-    Point_t * np = &point[newPart];
-    float2 p = {x, y};
-    while (rate--) {
-        float angle = rsRand(3.14f * 2.f);
-        float len = rsRand(rMax);
-        np->delta.x = len * sin(angle);
-        np->delta.y = len * cos(angle);
-        np->position = p;
-        np->color = c;
-        newPart++;
-        np++;
-        if (newPart >= size) {
-            newPart = 0;
-            np = &point[newPart];
-        }
-    }
-}
-
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 67b9d77..8eb30d2 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -160,6 +160,36 @@
             }
         },
 
+        new Test("with topic Hello") {
+            public void run() {
+                Notification n = new Notification.Builder(NotificationTestList.this)
+                        .setSmallIcon(R.drawable.icon1)
+                        .setWhen(mActivityCreateTime)
+                        .setContentTitle("hihi")
+                        .setContentText("This is a notification!!!")
+                        .setContentIntent(makeIntent2())
+                        .setTopic(new Notification.Topic("hello", "Hello"))
+                        .build();
+
+                mNM.notify(999, n);
+            }
+        },
+
+        new Test("with topic GoodBye") {
+            public void run() {
+                Notification n = new Notification.Builder(NotificationTestList.this)
+                        .setSmallIcon(R.drawable.icon1)
+                        .setWhen(mActivityCreateTime)
+                        .setContentTitle("byebye")
+                        .setContentText("This is a notification!!!")
+                        .setContentIntent(makeIntent2())
+                        .setTopic(new Notification.Topic("bye", "Goodbye"))
+                        .build();
+
+                mNM.notify(9999, n);
+            }
+        },
+
         new Test("Whens") {
             public void run()
             {
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index ec29c38..d8e0aac 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -58,8 +58,9 @@
 	ResourceValues.cpp \
 	SdkConstants.cpp \
 	StringPool.cpp \
-	XmlDom.cpp \
-	XmlPullParser.cpp
+	xml/XmlDom.cpp \
+	xml/XmlPullParser.cpp \
+	xml/XmlUtil.cpp
 
 testSources := \
 	compile/IdAssigner_test.cpp \
@@ -90,8 +91,9 @@
 	ResourceUtils_test.cpp \
 	StringPool_test.cpp \
 	ValueVisitor_test.cpp \
-	XmlDom_test.cpp \
-	XmlPullParser_test.cpp
+	xml/XmlDom_test.cpp \
+	xml/XmlPullParser_test.cpp \
+	xml/XmlUtil_test.cpp
 
 toolSources := \
 	compile/Compile.cpp \
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index f2a1878..02fe59c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -19,9 +19,8 @@
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-#include "XmlPullParser.h"
-
 #include "util/Util.h"
+#include "xml/XmlPullParser.h"
 
 #include <sstream>
 
@@ -29,26 +28,6 @@
 
 constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
 
-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 {};
-}
-
-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 {};
-}
-
 /**
  * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
  */
@@ -65,7 +44,7 @@
 /**
  * Build a string from XML that converts nested elements into Span objects.
  */
-bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
                                        StyleString* outStyleString) {
     std::vector<Span> spanStack;
 
@@ -74,9 +53,9 @@
     outStyleString->spans.clear();
     util::StringBuilder builder;
     size_t depth = 1;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        const XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kEndElement) {
+    while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+        const xml::XmlPullParser::Event event = parser->getEvent();
+        if (event == xml::XmlPullParser::Event::kEndElement) {
             if (!parser->getElementNamespace().empty()) {
                 // We already warned and skipped the start element, so just skip here too
                 continue;
@@ -91,11 +70,11 @@
             outStyleString->spans.push_back(spanStack.back());
             spanStack.pop_back();
 
-        } else if (event == XmlPullParser::Event::kText) {
+        } else if (event == xml::XmlPullParser::Event::kText) {
             outRawString->append(parser->getText());
             builder.append(parser->getText());
 
-        } else if (event == XmlPullParser::Event::kStartElement) {
+        } else if (event == xml::XmlPullParser::Event::kStartElement) {
             if (!parser->getElementNamespace().empty()) {
                 if (parser->getElementNamespace() != sXliffNamespaceUri) {
                     // Only warn if this isn't an xliff namespace.
@@ -128,7 +107,7 @@
                 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
             }
 
-        } else if (event == XmlPullParser::Event::kComment) {
+        } else if (event == xml::XmlPullParser::Event::kComment) {
             // Skip
         } else {
             assert(false);
@@ -140,11 +119,11 @@
     return !error;
 }
 
-bool ResourceParser::parse(XmlPullParser* parser) {
+bool ResourceParser::parse(xml::XmlPullParser* parser) {
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip comments and text.
             continue;
         }
@@ -159,7 +138,7 @@
         break;
     };
 
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+    if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
         mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                      << "xml parser error: " << parser->getLastError());
         return false;
@@ -167,10 +146,11 @@
     return !error;
 }
 
-static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
-    assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+static bool shouldStripResource(const xml::XmlPullParser* parser,
+                                const Maybe<std::u16string> productToMatch) {
+    assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement);
 
-    if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+    if (Maybe<StringPiece16> maybeProduct = xml::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.
@@ -229,20 +209,20 @@
     return !error;
 }
 
-bool ResourceParser::parseResources(XmlPullParser* parser) {
+bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
     std::set<ResourceName> strippedResources;
 
     bool error = false;
     std::u16string comment;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        const XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        const xml::XmlPullParser::Event event = parser->getEvent();
+        if (event == xml::XmlPullParser::Event::kComment) {
             comment = parser->getComment();
             continue;
         }
 
-        if (event == XmlPullParser::Event::kText) {
+        if (event == xml::XmlPullParser::Event::kText) {
             if (!util::trimWhitespace(parser->getText()).empty()) {
                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                              << "plain text not allowed here");
@@ -251,7 +231,7 @@
             continue;
         }
 
-        assert(event == XmlPullParser::Event::kStartElement);
+        assert(event == xml::XmlPullParser::Event::kStartElement);
 
         if (!parser->getElementNamespace().empty()) {
             // Skip unknown namespace.
@@ -266,7 +246,7 @@
 
         if (elementName == u"item") {
             // Items simply have their type encoded in the type attribute.
-            if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+            if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
                 elementName = maybeType.value().toString();
             } else {
                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
@@ -280,7 +260,7 @@
         parsedResource.source = mSource.withLine(parser->getLineNumber());
         parsedResource.comment = std::move(comment);
 
-        if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) {
+        if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) {
             parsedResource.name.entry = maybeName.value().toString();
 
         } else if (elementName != u"public-group") {
@@ -403,7 +383,7 @@
  * an Item. If allowRawValue is false, nullptr is returned in this
  * case.
  */
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
                                                const bool allowRawValue) {
     const size_t beginXmlLine = parser->getLineNumber();
 
@@ -432,10 +412,7 @@
     if (processedItem) {
         // Fix up the reference.
         if (Reference* ref = valueCast<Reference>(processedItem.get())) {
-            if (Maybe<ResourceName> transformedName =
-                    parser->transformPackage(ref->name.value(), u"")) {
-                ref->name = std::move(transformedName);
-            }
+            transformReferenceFromNamespace(parser, u"", ref);
         }
         return processedItem;
     }
@@ -456,11 +433,11 @@
     return {};
 }
 
-bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     bool formatted = true;
-    if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+    if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
         if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
             mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
             return false;
@@ -468,7 +445,7 @@
     }
 
     bool translateable = mOptions.translatable;
-    if (Maybe<StringPiece16> translateableAttr = findAttribute(parser, u"translatable")) {
+    if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
         if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
             mDiag->error(DiagMessage(source)
                          << "invalid value for 'translatable'. Must be a boolean");
@@ -495,7 +472,7 @@
     return true;
 }
 
-bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
@@ -506,7 +483,7 @@
     return true;
 }
 
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     uint32_t typeMask = 0;
@@ -540,10 +517,10 @@
     return true;
 }
 
-bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
         return false;
@@ -558,7 +535,7 @@
 
     outResource->name.type = *parsedType;
 
-    if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
+    if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
         android::Res_value val;
         bool result = android::ResTable::stringToInt(maybeId.value().data(),
                                                      maybeId.value().size(), &val);
@@ -580,10 +557,10 @@
     return true;
 }
 
-bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute");
         return false;
@@ -596,7 +573,7 @@
         return false;
     }
 
-    Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id");
+    Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
     if (!maybeId) {
         mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute");
         return false;
@@ -615,11 +592,11 @@
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text.
             continue;
         }
@@ -628,20 +605,20 @@
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"public") {
-            Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+            Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
             if (!maybeName) {
                 mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
                 error = true;
                 continue;
             }
 
-            if (findNonEmptyAttribute(parser, u"id")) {
+            if (xml::findNonEmptyAttribute(parser, u"id")) {
                 mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
                 error = true;
                 continue;
             }
 
-            if (findNonEmptyAttribute(parser, u"type")) {
+            if (xml::findNonEmptyAttribute(parser, u"type")) {
                 mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
                 error = true;
                 continue;
@@ -666,10 +643,10 @@
     return !error;
 }
 
-bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
                      "'type' attribute");
@@ -715,17 +692,16 @@
     return mask;
 }
 
-
-
-bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
     outResource->source = mSource.withLine(parser->getLineNumber());
     return parseAttrImpl(parser, outResource, false);
 }
 
-bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+                                   bool weak) {
     uint32_t typeMask = 0;
 
-    Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+    Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
     if (maybeFormat) {
         typeMask = parseFormatAttribute(maybeFormat.value());
         if (typeMask == 0) {
@@ -735,28 +711,22 @@
         }
     }
 
-    // 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 && !maybeFormat) {
-        StringPiece16 package, type, name;
-        ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
-        if (type.empty() && !package.empty()) {
-            outResource->name.package = package.toString();
-            outResource->name.entry = name.toString();
+    struct SymbolComparator {
+        bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+            return a.symbol.name.value() < b.symbol.name.value();
         }
-    }
+    };
 
-    std::vector<Attribute::Symbol> items;
+    std::set<Attribute::Symbol, SymbolComparator> items;
 
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text.
             continue;
         }
@@ -785,15 +755,27 @@
             }
 
             if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+                Attribute::Symbol& symbol = s.value();
                 ParsedResource childResource;
-                childResource.name = s.value().symbol.name.value();
+                childResource.name = symbol.symbol.name.value();
                 childResource.source = itemSource;
                 childResource.value = util::make_unique<Id>();
                 outResource->childResources.push_back(std::move(childResource));
 
-                s.value().symbol.setComment(std::move(comment));
-                s.value().symbol.setSource(itemSource);
-                items.push_back(std::move(s.value()));
+                symbol.symbol.setComment(std::move(comment));
+                symbol.symbol.setSource(itemSource);
+
+                auto insertResult = items.insert(std::move(symbol));
+                if (!insertResult.second) {
+                    const Attribute::Symbol& existingSymbol = *insertResult.first;
+                    mDiag->error(DiagMessage(itemSource)
+                                 << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
+                                 << "'");
+
+                    mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
+                                << "first defined here");
+                    error = true;
+                }
             } else {
                 error = true;
             }
@@ -810,23 +792,23 @@
     }
 
     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
-    attr->symbols.swap(items);
+    attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
     outResource->value = std::move(attr);
     return true;
 }
 
-Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
                                                              const StringPiece16& tag) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    Maybe<StringPiece16> maybeName = xml::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");
+    Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
     if (!maybeValue) {
         mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
         return {};
@@ -845,12 +827,19 @@
             val.data };
 }
 
-static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
+static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
     str = util::trimWhitespace(str);
-    const char16_t* const start = str.data();
+    const char16_t* start = str.data();
     const char16_t* const end = start + str.size();
     const char16_t* p = start;
 
+    Reference ref;
+    if (p != end && *p == u'*') {
+        ref.privateReference = true;
+        start++;
+        p++;
+    }
+
     StringPiece16 package;
     StringPiece16 name;
     while (p != end) {
@@ -862,28 +851,27 @@
         p++;
     }
 
-    return ResourceName(package.toString(), ResourceType::kAttr,
+    ref.name = ResourceName(package.toString(), ResourceType::kAttr,
                         name.empty() ? str.toString() : name.toString());
+    return Maybe<Reference>(std::move(ref));
 }
 
-bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
     if (!maybeName) {
         mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
         return false;
     }
 
-    Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+    Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
     if (!maybeKey) {
         mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
         return false;
     }
 
-    if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
-        maybeKey = std::move(transformedName);
-    }
+    transformReferenceFromNamespace(parser, u"", &maybeKey.value());
 
     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
     if (!value) {
@@ -891,15 +879,15 @@
         return false;
     }
 
-    style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
+    style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
     return true;
 }
 
-bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Style> style = util::make_unique<Style>();
 
-    Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+    Maybe<StringPiece16> maybeParent = xml::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()) {
@@ -910,10 +898,9 @@
                 return false;
             }
 
-            if (Maybe<ResourceName> transformedName =
-                    parser->transformPackage(style->parent.value().name.value(), u"")) {
-                style->parent.value().name = std::move(transformedName);
-            }
+            // Transform the namespace prefix to the actual package name, and mark the reference as
+            // private if appropriate.
+            transformReferenceFromNamespace(parser, u"", &style->parent.value());
         }
 
     } else {
@@ -922,15 +909,15 @@
         size_t pos = styleName.find_last_of(u'.');
         if (pos != std::string::npos) {
             style->parentInferred = true;
-            style->parent = Reference(
-                    ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
+            style->parent = Reference(ResourceName({}, ResourceType::kStyle,
+                                                   styleName.substr(0, pos)));
         }
     }
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -955,15 +942,15 @@
     return true;
 }
 
-bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource,
                                 uint32_t typeMask) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Array> array = util::make_unique<Array>();
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -996,14 +983,14 @@
     return true;
 }
 
-bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -1012,16 +999,15 @@
         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()) {
+            Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
+            if (!maybeQuantity) {
                 mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
                              << "'quantity'");
                 error = true;
                 continue;
             }
 
-            StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+            StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
             size_t index = 0;
             if (trimmedQuantity == u"zero") {
                 index = Plural::Zero;
@@ -1071,7 +1057,7 @@
     return true;
 }
 
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
 
@@ -1081,11 +1067,11 @@
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Ignore text.
             continue;
         }
@@ -1094,17 +1080,29 @@
         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()) {
+            Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+            if (!maybeName) {
                 mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
                 error = true;
                 continue;
             }
 
+            // If this is a declaration, the package name may be in the name. Separate these out.
+            // Eg. <attr name="android:text" />
+            Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+            if (!maybeRef) {
+                mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+                             << maybeName.value() << "'");
+                error = true;
+                continue;
+            }
+
+            Reference& childRef = maybeRef.value();
+            xml::transformReferenceFromNamespace(parser, u"", &childRef);
+
             // Create the ParsedResource that will add the attribute to the table.
             ParsedResource childResource;
-            childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+            childResource.name = childRef.name.value();
             childResource.source = itemSource;
             childResource.comment = std::move(comment);
 
@@ -1114,7 +1112,6 @@
             }
 
             // Create the reference to this attribute.
-            Reference childRef(childResource.name);
             childRef.setComment(childResource.comment);
             childRef.setSource(itemSource);
             styleable->entries.push_back(std::move(childRef));
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 18101ee..1150758 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -22,10 +22,9 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "StringPool.h"
-#include "XmlPullParser.h"
-
 #include "util/Maybe.h"
 #include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
 
 #include <memory>
 
@@ -57,7 +56,7 @@
 
     ResourceParser(const ResourceParser&) = delete; // No copy.
 
-    bool parse(XmlPullParser* parser);
+    bool parse(xml::XmlPullParser* parser);
 
 private:
     /*
@@ -66,7 +65,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(xml::XmlPullParser* parser, std::u16string* outRawString,
                            StyleString* outStyleString);
 
     /*
@@ -75,24 +74,25 @@
      * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
      * RawString is returned. Otherwise this returns false;
      */
-    std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+    std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
                                    const bool allowRawValue);
 
-    bool parseResources(XmlPullParser* parser);
-    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 parsePublicGroup(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);
+    bool parseResources(xml::XmlPullParser* parser);
+    bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
+    Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+                                                 const StringPiece16& tag);
+    bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+    bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+    bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
 
     IDiagnostics* mDiag;
     ResourceTable* mTable;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59eb95..ab16424 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -18,9 +18,8 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "XmlPullParser.h"
-
 #include "test/Context.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -36,7 +35,7 @@
     input << "<attr name=\"foo\"/>" << std::endl;
     ResourceTable table;
     ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
-    XmlPullParser xmlParser(input);
+    xml::XmlPullParser xmlParser(input);
     ASSERT_FALSE(parser.parse(&xmlParser));
 }
 
@@ -56,7 +55,7 @@
         parserOptions.product = product;
         ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
                               parserOptions);
-        XmlPullParser xmlParser(input);
+        xml::XmlPullParser xmlParser(input);
         if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
@@ -360,6 +359,25 @@
     EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
 }
 
+TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
+    std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+                        "  <attr name=\"*android:bar\" />\n"
+                        "  <attr name=\"privAndroid:bat\" />\n"
+                        "</declare-styleable>";
+    ASSERT_TRUE(testParse(input));
+    Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+    ASSERT_NE(nullptr, styleable);
+    ASSERT_EQ(2u, styleable->entries.size());
+
+    EXPECT_TRUE(styleable->entries[0].privateReference);
+    AAPT_ASSERT_TRUE(styleable->entries[0].name);
+    EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package);
+
+    EXPECT_TRUE(styleable->entries[1].privateReference);
+    AAPT_ASSERT_TRUE(styleable->entries[1].name);
+    EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package);
+}
+
 TEST_F(ResourceParserTest, ParseArray) {
     std::string input = "<array name=\"foo\">\n"
                         "  <item>@string/ref</item>\n"
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index deafe20..73d8585 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -190,22 +190,32 @@
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
                                 std::unique_ptr<Value> value, IDiagnostics* diag) {
-    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
+    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars,
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
                                 const ConfigDescription& config, std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
+    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars,
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                                      const Source& source, const StringPiece16& path,
                                      IDiagnostics* diag) {
+    return addFileReference(name, config, source, path, resolveValueCollision, diag);
+}
+
+bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                                     const Source& source, const StringPiece16& path,
+                                     std::function<int(Value*,Value*)> conflictResolver,
+                                     IDiagnostics* diag) {
     std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
             stringPool.makeRef(path));
     fileRef->setSource(source);
-    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, diag);
+    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars,
+                           conflictResolver, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -213,7 +223,7 @@
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
     return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
-                           diag);
+                           resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
@@ -221,12 +231,17 @@
                                             const ConfigDescription& config,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
+    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars,
+                           resolveValueCollision, diag);
 }
 
-bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                                    const ConfigDescription& config, std::unique_ptr<Value> value,
-                                    const char16_t* validChars, IDiagnostics* diag) {
+bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
+                                    const ResourceId resId,
+                                    const ConfigDescription& config,
+                                    std::unique_ptr<Value> value,
+                                    const char16_t* validChars,
+                                    std::function<int(Value*,Value*)> conflictResolver,
+                                    IDiagnostics* diag) {
     assert(value && "value can't be nullptr");
     assert(diag && "diagnostics can't be nullptr");
 
@@ -289,7 +304,7 @@
         // This resource did not exist before, add it.
         entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
     } else {
-        int collisionResult = resolveValueCollision(iter->value.get(), value.get());
+        int collisionResult = conflictResolver(iter->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
             iter->value = std::move(value);
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 980504b..6b7b07e 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -163,7 +163,12 @@
                      IDiagnostics* diag);
 
     bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                          const Source& source, const StringPiece16& path, IDiagnostics* diag);
+                          const Source& source, const StringPiece16& path,
+                          IDiagnostics* diag);
+
+    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+                          const Source& source, const StringPiece16& path,
+                          std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
@@ -221,9 +226,14 @@
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
 
-    bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
-                         const ConfigDescription& config, std::unique_ptr<Value> value,
-                         const char16_t* validChars, IDiagnostics* diag);
+    bool addResourceImpl(const ResourceNameRef& name,
+                         ResourceId resId,
+                         const ConfigDescription& config,
+                         std::unique_ptr<Value> value,
+                         const char16_t* validChars,
+                         std::function<int(Value*,Value*)> conflictResolver,
+                         IDiagnostics* diag);
+
     bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
                             const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
 };
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index d3c3c10..ffe6595 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ResourceUtils.h"
+#include "flatten/ResourceTypeExtensions.h"
 #include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
@@ -23,22 +24,64 @@
 namespace aapt {
 namespace ResourceUtils {
 
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
                          StringPiece16* outType, StringPiece16* outEntry) {
+    bool hasPackageSeparator = false;
+    bool hasTypeSeparator = false;
     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'/') {
+            hasTypeSeparator = true;
             outType->assign(start, current - start);
             start = current + 1;
         } else if (outPackage->size() == 0 && *current == u':') {
+            hasPackageSeparator = true;
             outPackage->assign(start, current - start);
             start = current + 1;
         }
         current++;
     }
     outEntry->assign(start, end - start);
+
+    return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+}
+
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+    size_t offset = 0;
+    bool priv = false;
+    if (str.data()[0] == u'*') {
+        priv = true;
+        offset = 1;
+    }
+
+    StringPiece16 package;
+    StringPiece16 type;
+    StringPiece16 entry;
+    if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(type);
+    if (!parsedType) {
+        return false;
+    }
+
+    if (entry.empty()) {
+        return false;
+    }
+
+    if (outRef) {
+        outRef->package = package;
+        outRef->type = *parsedType;
+        outRef->entry = entry;
+    }
+
+    if (outPrivate) {
+        *outPrivate = priv;
+    }
+    return true;
 }
 
 bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
@@ -55,33 +98,30 @@
         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) {
+        ResourceNameRef name;
+        if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+                               &name, &priv)) {
             return false;
         }
 
-        if (create && *parsedType != ResourceType::kId) {
+        if (create && priv) {
             return false;
         }
 
-        if (outRef != nullptr) {
-            outRef->package = package;
-            outRef->type = *parsedType;
-            outRef->entry = entry;
+        if (create && name.type != ResourceType::kId) {
+            return false;
         }
+
+        if (outRef) {
+            *outRef = name;
+        }
+
         if (outCreate) {
             *outCreate = create;
         }
+
         if (outPrivate) {
             *outPrivate = priv;
         }
@@ -104,20 +144,33 @@
         StringPiece16 package;
         StringPiece16 type;
         StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+        if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+                                 &package, &type, &entry)) {
+            return false;
+        }
 
         if (!type.empty() && type != u"attr") {
             return false;
         }
 
-        outRef->package = package;
-        outRef->type = ResourceType::kAttr;
-        outRef->entry = entry;
+        if (entry.empty()) {
+            return false;
+        }
+
+        if (outRef) {
+            outRef->package = package;
+            outRef->type = ResourceType::kAttr;
+            outRef->entry = entry;
+        }
         return true;
     }
     return false;
 }
 
+bool isAttributeReference(const StringPiece16& str) {
+    return tryParseAttributeReference(str, nullptr);
+}
+
 /*
  * Style parent's are a bit different. We accept the following formats:
  *
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 851edc8..f93a4c7 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -34,10 +34,18 @@
  *
  * where the package can be empty. Validation must be performed on each
  * individual extracted piece to verify that the pieces are valid.
+ * Returns false if there was no package but a ':' was present.
  */
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
                          StringPiece16* outType, StringPiece16* outEntry);
 
+/**
+ * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * was present.
+ */
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+
 /*
  * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
  * `outReference` set to the parsed reference.
@@ -54,12 +62,17 @@
 bool isReference(const StringPiece16& str);
 
 /*
- * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * 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 true if the string is in the form of an attribute reference(?[package:][type/]name).
+ */
+bool isAttributeReference(const StringPiece16& str);
+
+/**
  * Returns true if the value is a boolean, putting the result in `outValue`.
  */
 bool tryParseBool(const StringPiece16& str, bool* outValue);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 7de8f41..4bbfc32 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -16,15 +16,30 @@
 
 #include "Resource.h"
 #include "ResourceUtils.h"
-
 #include "test/Common.h"
 
 #include <gtest/gtest.h>
 
 namespace aapt {
 
+TEST(ResourceUtilsTest, ParseResourceName) {
+    ResourceNameRef actual;
+    bool actualPriv = false;
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+    EXPECT_FALSE(actualPriv);
+
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual);
+    EXPECT_FALSE(actualPriv);
+
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+    EXPECT_TRUE(actualPriv);
+}
+
 TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
-    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+    ResourceNameRef expected({}, ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -35,7 +50,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -47,7 +62,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -59,7 +74,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -71,7 +86,7 @@
 }
 
 TEST(ResourceUtilsTest, ParsePrivateReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -90,9 +105,29 @@
                                                   &privateRef));
 }
 
+TEST(ResourceUtilsTest, ParseAttributeReferences) {
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo"));
+}
+
+TEST(ResourceUtilsTest, FailParseIncompleteReference) {
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo"));
+}
+
 TEST(ResourceUtilsTest, ParseStyleParentReference) {
-    const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
-    const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+    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);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 8acff0d..5550f19 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -71,27 +71,23 @@
 }
 
 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->dataType = (referenceType == Reference::Type::kResource) ?
+            android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
     outValue->data = util::hostToDevice32(id ? id.value().id : 0);
     return true;
 }
 
 Reference* Reference::clone(StringPool* /*newPool*/) const {
-    Reference* ref = new Reference();
-    ref->mComment = mComment;
-    ref->mSource = mSource;
-    ref->referenceType = referenceType;
-    ref->name = name;
-    ref->id = id;
-    return ref;
+    return new Reference(*this);
 }
 
 void Reference::print(std::ostream* out) const {
     *out << "(reference) ";
     if (referenceType == Reference::Type::kResource) {
         *out << "@";
+        if (privateReference) {
+            *out << "*";
+        }
     } else {
         *out << "?";
     }
@@ -116,10 +112,7 @@
 }
 
 Id* Id::clone(StringPool* /*newPool*/) const {
-    Id* id = new Id();
-    id->mComment = mComment;
-    id->mSource = mSource;
-    return id;
+    return new Id(*this);
 }
 
 void Id::print(std::ostream* out) const {
@@ -214,10 +207,7 @@
 }
 
 BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
-    BinaryPrimitive* bp = new BinaryPrimitive(value);
-    bp->mComment = mComment;
-    bp->mSource = mSource;
-    return bp;
+    return new BinaryPrimitive(*this);
 }
 
 void BinaryPrimitive::print(std::ostream* out) const {
@@ -255,12 +245,7 @@
 }
 
 Attribute* Attribute::clone(StringPool* /*newPool*/) const {
-    Attribute* attr = new Attribute(weak);
-    attr->mComment = mComment;
-    attr->mSource = mSource;
-    attr->typeMask = typeMask;
-    std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
-    return attr;
+    return new Attribute(*this);
 }
 
 void Attribute::printMask(std::ostream* out) const {
@@ -450,11 +435,7 @@
 }
 
 Styleable* Styleable::clone(StringPool* /*newPool*/) const {
-    Styleable* styleable = new Styleable();
-    styleable->mComment = mComment;
-    styleable->mSource = mSource;
-    std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
-    return styleable;
+    return new Styleable(*this);
 }
 
 void Styleable::print(std::ostream* out) const {
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 39088bc..17a658e 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -19,9 +19,6 @@
 #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"
@@ -31,6 +28,8 @@
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlPullParser.h"
 
 #include <fstream>
 #include <string>
@@ -131,7 +130,7 @@
 
 
         // Parse the values file from XML.
-        XmlPullParser xmlParser(fin);
+        xml::XmlPullParser xmlParser(fin);
 
         ResourceParserOptions parserOptions;
         parserOptions.product = options.product;
@@ -191,7 +190,7 @@
 static bool compileXml(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& pathData, const std::string& outputPath) {
 
-    std::unique_ptr<XmlResource> xmlRes;
+    std::unique_ptr<xml::XmlResource> xmlRes;
 
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index dfdf710..f40689e 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -16,9 +16,8 @@
 
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "XmlDom.h"
-
 #include "compile/XmlIdCollector.h"
+#include "xml/XmlDom.h"
 
 #include <algorithm>
 #include <vector>
@@ -61,7 +60,7 @@
 
 } // namespace
 
-bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
     xmlRes->file.exportedSymbols.clear();
     IdCollector collector(&xmlRes->file.exportedSymbols);
     xmlRes->root->accept(&collector);
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 96a58f2..1b14944 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -18,11 +18,12 @@
 #define AAPT_XMLIDCOLLECTOR_H
 
 #include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
 struct XmlIdCollector : public IXmlResourceConsumer {
-    bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+    bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index c703f451..45b7af2 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -26,7 +26,7 @@
 TEST(XmlIdCollectorTest, CollectsIds) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                   android:id="@+id/foo"
                   text="@+id/bar">
@@ -50,7 +50,7 @@
 TEST(XmlIdCollectorTest, DontCollectNonIds) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
 
     XmlIdCollector collector;
     ASSERT_TRUE(collector.consume(context.get(), doc.get()));
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index c1ff556..acf5bb5 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -62,7 +62,7 @@
          * A raw string value that hasn't had its escape sequences
          * processed nor whitespace removed.
          */
-        TYPE_RAW_STRING = 0xfe
+        TYPE_RAW_STRING = 0xfe,
     };
 };
 
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 6b90fb2..636e977 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -78,10 +78,18 @@
     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 addSymbol(const Reference& ref, size_t offset) {
+        const ResourceName& name = ref.name.value();
+        std::u16string fullName;
+        if (ref.privateReference) {
+            fullName += u"*";
+        }
+
+        if (!name.package.empty()) {
+            fullName += name.package + u":";
+        }
+        fullName += toString(name.type).toString() + u"/" + name.entry;
+        symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
     }
 
     void shiftAllOffsets(size_t offset) {
@@ -100,20 +108,23 @@
     SymbolWriter* mSymbols;
     FlatEntry* mEntry;
     BigBuffer* mBuffer;
+    bool mUseExtendedChunks;
     size_t mEntryCount = 0;
     Maybe<uint32_t> mParentIdent;
     Maybe<ResourceNameRef> mParentName;
 
-    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
-            mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+                      bool useExtendedChunks) :
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer),
+            mUseExtendedChunks(useExtendedChunks) {
     }
 
     void flattenKey(Reference* key, ResTable_map* outEntry) {
-        if (!key->id) {
+        if (!key->id || (key->privateReference && mUseExtendedChunks)) {
             assert(key->name && "reference must have a name");
 
             outEntry->name.ident = util::hostToDevice32(0);
-            mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+            mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
                                     offsetof(ResTable_map, name));
         } else {
             outEntry->name.ident = util::hostToDevice32(key->id.value().id);
@@ -121,16 +132,21 @@
     }
 
     void flattenValue(Item* value, ResTable_map* outEntry) {
+        bool privateRef = false;
         if (Reference* ref = valueCast<Reference>(value)) {
-            if (!ref->id) {
+            privateRef = ref->privateReference && mUseExtendedChunks;
+            if (!ref->id || privateRef) {
                 assert(ref->name && "reference must have a name");
 
-                mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
                                         offsetof(ResTable_map, value) + offsetof(Res_value, data));
             }
         }
 
         bool result = value->flatten(&outEntry->value);
+        if (privateRef) {
+            outEntry->value.data = 0;
+        }
         assert(result && "flatten failed");
     }
 
@@ -169,7 +185,8 @@
 
     void visit(Style* style) override {
         if (style->parent) {
-            if (!style->parent.value().id) {
+            bool privateRef = style->parent.value().privateReference && mUseExtendedChunks;
+            if (!style->parent.value().id || privateRef) {
                 assert(style->parent.value().name && "reference must have a name");
                 mParentName = style->parent.value().name;
             } else {
@@ -339,21 +356,28 @@
     bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
         if (Item* item = valueCast<Item>(entry->value)) {
             writeEntry<ResTable_entry, true>(entry, buffer);
+            bool privateRef = false;
             if (Reference* ref = valueCast<Reference>(entry->value)) {
-                if (!ref->id) {
+                // If there is no ID or the reference is private and we allow extended chunks,
+                // write out a 0 and mark the symbol table with the name of the reference.
+                privateRef = (ref->privateReference && mOptions.useExtendedChunks);
+                if (!ref->id || privateRef) {
                     assert(ref->name && "reference must have at least a name");
-                    mSymbols->addSymbol(ref->name.value(),
-                                        buffer->size() + offsetof(Res_value, data));
+                    mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
                 }
             }
             Res_value* outValue = buffer->nextBlock<Res_value>();
             bool result = item->flatten(outValue);
             assert(result && "flatten failed");
+            if (privateRef) {
+                // Force the value of 0 so we look up the symbol at unflatten time.
+                outValue->data = 0;
+            }
             outValue->size = util::hostToDevice16(sizeof(*outValue));
         } else {
             const size_t beforeEntry = buffer->size();
             ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
-            MapFlattenVisitor visitor(mSymbols, entry, buffer);
+            MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
             entry->value->accept(&visitor);
             outEntry->count = util::hostToDevice32(visitor.mEntryCount);
             if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 68a1f47..4ffb980 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -15,11 +15,11 @@
  */
 
 #include "flatten/TableFlattener.h"
+#include "test/Builders.h"
+#include "test/Context.h"
 #include "unflatten/BinaryResourceParser.h"
 #include "util/Util.h"
 
-#include "test/Builders.h"
-#include "test/Context.h"
 
 #include <gtest/gtest.h>
 
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 4efb08b..8219462 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -15,15 +15,14 @@
  */
 
 #include "SdkConstants.h"
-#include "XmlDom.h"
-
 #include "flatten/ChunkWriter.h"
 #include "flatten/ResourceTypeExtensions.h"
 #include "flatten/XmlFlattener.h"
+#include "xml/XmlDom.h"
 
 #include <androidfw/ResourceTypes.h>
-#include <vector>
 #include <utils/misc.h>
+#include <vector>
 
 using namespace android;
 
@@ -306,7 +305,7 @@
     return true;
 }
 
-bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
     if (!resource->root) {
         return false;
     }
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index b1fb3a7..a688ac9 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,16 +17,12 @@
 #ifndef AAPT_FLATTEN_XMLFLATTENER_H
 #define AAPT_FLATTEN_XMLFLATTENER_H
 
-#include "util/BigBuffer.h"
-
 #include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
-namespace xml {
-struct Node;
-}
-
 struct XmlFlattenerOptions {
     /**
      * Keep attribute raw string values along with typed values.
@@ -45,7 +41,7 @@
             mBuffer(buffer), mOptions(options) {
     }
 
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 
 private:
     BigBuffer* mBuffer;
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 318bcdd..8648879 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -16,11 +16,10 @@
 
 #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 "util/BigBuffer.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <gtest/gtest.h>
@@ -45,7 +44,7 @@
                 .build();
     }
 
-    ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+    ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
                                        XmlFlattenerOptions options = {}) {
         BigBuffer buffer(1024);
         XmlFlattener flattener(&buffer, options);
@@ -65,7 +64,7 @@
 };
 
 TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:test="http://com.test"
                   attr="hey">
               <Layout test:hello="hi" />
@@ -144,7 +143,7 @@
 }
 
 TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                 android:paddingStart="1dp"
                 android:colorAccent="#ffffff"/>)EOF");
@@ -169,7 +168,7 @@
 }
 
 TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                   android:id="@id/id"
                   class="str"
@@ -192,7 +191,7 @@
  * namespace.
  */
 TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
 
     android::ResXMLTree tree;
     ASSERT_TRUE(flatten(doc.get(), &tree));
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index d5a2b38..da96b84 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -17,12 +17,10 @@
 #include "ResourceParser.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "XmlPullParser.h"
-
 #include "java/AnnotationProcessor.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 
@@ -42,7 +40,7 @@
                               options);
         std::stringstream in;
         in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
-        XmlPullParser xmlParser(in);
+        xml::XmlPullParser xmlParser(in);
         if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h
index b8886f9..04e1274 100644
--- a/tools/aapt2/java/ClassDefinitionWriter.h
+++ b/tools/aapt2/java/ClassDefinitionWriter.h
@@ -17,6 +17,7 @@
 #ifndef AAPT_JAVA_CLASSDEFINITION_H
 #define AAPT_JAVA_CLASSDEFINITION_H
 
+#include "Resource.h"
 #include "java/AnnotationProcessor.h"
 #include "util/StringPiece.h"
 #include "util/Util.h"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index d963d89..a9b4c14 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -15,12 +15,11 @@
  */
 
 #include "Source.h"
-#include "XmlDom.h"
-
 #include "java/AnnotationProcessor.h"
 #include "java/ClassDefinitionWriter.h"
 #include "java/ManifestClassGenerator.h"
 #include "util/Maybe.h"
+#include "xml/XmlDom.h"
 
 #include <algorithm>
 
@@ -80,7 +79,7 @@
 }
 
 bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
-                                      XmlResource* res, std::ostream* out) {
+                                      xml::XmlResource* res, std::ostream* out) {
     xml::Element* el = xml::findRootElement(res->root.get());
     if (!el) {
         return false;
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index 0f0998f..226ed23 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -18,15 +18,15 @@
 #define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
 
 #include "Diagnostics.h"
-#include "process/IResourceTableConsumer.h"
 #include "util/StringPiece.h"
+#include "xml/XmlDom.h"
 
 #include <iostream>
 
 namespace aapt {
 
 struct ManifestClassGenerator {
-    bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+    bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res,
                   std::ostream* out);
 };
 
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 4081287..fc57ae6f 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "java/ManifestClassGenerator.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -25,7 +24,7 @@
 
 TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
-    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android">
           <permission android:name="android.permission.ACCESS_INTERNET" />
           <permission android:name="android.DO_DANGEROUS_THINGS" />
@@ -75,7 +74,7 @@
 
 TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
-    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android">
           <!-- Required to access the internet.
                Added in API 1. -->
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 4431477..c096854 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-#include "XmlDom.h"
-
 #include "java/ProguardRules.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 #include <memory>
 #include <string>
@@ -40,11 +39,11 @@
 
     virtual void visit(xml::Element* node) override {
         if (!node->namespaceUri.empty()) {
-            Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+            Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
                     node->namespaceUri);
             if (maybePackage) {
                 // This is a custom view, let's figure out the class name from this.
-                std::u16string package = maybePackage.value() + u"." + node->name;
+                std::u16string package = maybePackage.value().package + u"." + node->name;
                 if (util::isJavaClassName(package)) {
                     addClass(node->lineNumber, package);
                 }
@@ -185,7 +184,8 @@
     std::u16string mPackage;
 };
 
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
+                                     KeepSet* keepSet) {
     ManifestVisitor visitor(source, keepSet);
     if (res->root) {
         res->root->accept(&visitor);
@@ -194,7 +194,7 @@
     return false;
 }
 
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
     if (!res->root) {
         return false;
     }
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index be61eb9..aafffd3 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -19,8 +19,7 @@
 
 #include "Resource.h"
 #include "Source.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 #include <map>
 #include <ostream>
@@ -47,8 +46,8 @@
     std::map<std::u16string, std::set<Source>> mKeepMethodSet;
 };
 
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 9ce3734..9850ae5 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -18,8 +18,6 @@
 #include "Debug.h"
 #include "Flags.h"
 #include "NameMangler.h"
-#include "XmlDom.h"
-
 #include "compile/IdAssigner.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
@@ -28,6 +26,7 @@
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
 #include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
 #include "link/ManifestFixer.h"
 #include "link/TableMerger.h"
 #include "process/IResourceTableConsumer.h"
@@ -36,6 +35,7 @@
 #include "unflatten/FileExportHeaderReader.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
+#include "xml/XmlDom.h"
 
 #include <fstream>
 #include <sys/stat.h>
@@ -48,8 +48,9 @@
     std::string outputPath;
     std::string manifestPath;
     std::vector<std::string> includePaths;
+    std::vector<std::string> overlayFiles;
     Maybe<std::string> generateJavaClassPath;
-    std::vector<std::string> extraJavaPackages;
+    std::set<std::string> extraJavaPackages;
     Maybe<std::string> generateProguardRulesPath;
     bool noAutoVersion = false;
     bool staticLib = false;
@@ -88,9 +89,11 @@
     }
 };
 
-struct LinkCommand {
-    LinkOptions mOptions;
-    LinkContext mContext;
+class LinkCommand {
+public:
+    LinkCommand(const LinkOptions& options) :
+            mOptions(options), mContext(), mFinalTable() {
+    }
 
     std::string buildResourceFileName(const ResourceFile& resFile) {
         std::stringstream out;
@@ -117,8 +120,7 @@
         AssetManagerSymbolTableBuilder builder;
         for (const std::string& path : mOptions.includePaths) {
             if (mOptions.verbose) {
-                mContext.getDiagnostics()->note(
-                        DiagMessage(Source{ path }) << "loading include path");
+                mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
             }
 
             std::unique_ptr<android::AssetManager> assetManager =
@@ -126,7 +128,7 @@
             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");
+                        DiagMessage(path) << "failed to load include path");
                 return {};
             }
             builder.add(std::move(assetManager));
@@ -141,12 +143,12 @@
         std::string errorStr;
         Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
         if (!map) {
-            mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
+            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
             return {};
         }
 
         std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(&mContext, table.get(), Source{ input },
+        BinaryResourceParser parser(&mContext, table.get(), Source(input),
                                     map.value().getDataPtr(), map.value().getDataLength());
         if (!parser.parse()) {
             return {};
@@ -157,20 +159,20 @@
     /**
      * Inflates an XML file from the source path.
      */
-    std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+    std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
         std::ifstream fin(path, std::ifstream::binary);
         if (!fin) {
-            mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
+            mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
             return {};
         }
 
-        return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
+        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) {
+    std::unique_ptr<xml::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);
@@ -186,7 +188,7 @@
             return {};
         }
 
-        std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
                 (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
                 maybeF.value().getDataLength() - offset,
                 mContext.getDiagnostics(), Source(path));
@@ -243,7 +245,7 @@
         return true;
     }
 
-    Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+    Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
         // Make sure the first element is <manifest> with package attribute.
         if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
             if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
@@ -255,9 +257,9 @@
         return {};
     }
 
-    bool verifyNoExternalPackages(ResourceTable* table) {
+    bool verifyNoExternalPackages() {
         bool error = false;
-        for (const auto& package : table->packages) {
+        for (const auto& package : mFinalTable.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!
@@ -307,7 +309,7 @@
         return true;
     }
 
-    bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+    bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
                     IArchiveWriter* writer) {
         BigBuffer buffer(1024);
         XmlFlattenerOptions options = {};
@@ -352,7 +354,7 @@
         return true;
     }
 
-    bool writeManifestJavaFile(XmlResource* manifestXml) {
+    bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
         if (!mOptions.generateJavaClassPath) {
             return true;
         }
@@ -401,9 +403,106 @@
         return true;
     }
 
+    bool mergeStaticLibrary(const std::string& input) {
+        // TODO(adamlesinski): Load resources from a static library APK and merge the table into
+        // TableMerger.
+        mContext.getDiagnostics()->warn(DiagMessage()
+                                        << "linking static libraries not supported yet: "
+                                        << input);
+        return true;
+    }
+
+    bool mergeResourceTable(const std::string& input, bool override) {
+        if (mOptions.verbose) {
+            mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+        }
+
+        std::unique_ptr<ResourceTable> table = loadTable(input);
+        if (!table) {
+            return false;
+        }
+
+        if (!mTableMerger->merge(Source(input), table.get(), override)) {
+            return false;
+        }
+        return true;
+    }
+
+    bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
+        if (file.name.package.empty()) {
+            file.name.package = mContext.getCompilationPackage().toString();
+        }
+
+        ResourceNameRef resName = file.name;
+
+        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+        if (mangledName) {
+            resName = mangledName.value();
+        }
+
+        std::function<int(Value*,Value*)> resolver;
+        if (override) {
+            resolver = [](Value* a, Value* b) -> int {
+                int result = ResourceTable::resolveValueCollision(a, b);
+                if (result == 0) {
+                    // Always accept the new value if it would normally conflict (override).
+                    result = 1;
+                }
+                return result;
+            };
+        } else {
+            // Otherwise use the default resolution.
+            resolver = ResourceTable::resolveValueCollision;
+        }
+
+        // Add this file to the table.
+        if (!mFinalTable.addFileReference(resName, file.config, file.source,
+                                          util::utf8ToUtf16(buildResourceFileName(file)),
+                                          resolver, mContext.getDiagnostics())) {
+            return false;
+        }
+
+        // Add the exports of this file to the table.
+        for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+            if (exportedSymbol.name.package.empty()) {
+                exportedSymbol.name.package = mContext.getCompilationPackage().toString();
+            }
+
+            ResourceNameRef resName = exportedSymbol.name;
+
+            Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+                    exportedSymbol.name);
+            if (mangledName) {
+                resName = mangledName.value();
+            }
+
+            std::unique_ptr<Id> id = util::make_unique<Id>();
+            id->setSource(file.source.withLine(exportedSymbol.line));
+            bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
+                    mContext.getDiagnostics());
+            if (!result) {
+                return false;
+            }
+        }
+
+        mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
+        return true;
+    }
+
+    bool processFile(const std::string& input, bool override) {
+        if (util::stringEndsWith<char>(input, ".apk")) {
+            return mergeStaticLibrary(input);
+        } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
+            return mergeResourceTable(input, override);
+        } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
+            return mergeCompiledFile(input, std::move(maybeF.value()), override);
+        }
+        return false;
+    }
+
     int run(const std::vector<std::string>& inputFiles) {
         // Load the AndroidManifest.xml
-        std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
         if (!manifestXml) {
             return 1;
         }
@@ -438,97 +537,36 @@
             return 1;
         }
 
+        mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable);
+
         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);
-                        std::unique_ptr<Id> id = util::make_unique<Id>();
-                        id->setSource(f.source.withLine(exportedSymbol.line));
-                        if (!mergedTable.addResourceAllowMangled(
-                                mangledName ? mangledName.value() : exportedSymbol.name,
-                                {}, std::move(id), mContext.getDiagnostics())) {
-                            error = true;
-                        }
-                    }
-
-                    filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
-                } else {
-                    return 1;
-                }
+            if (!processFile(input, false)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+                return 1;
             }
         }
 
-        if (error) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
-            return 1;
+        for (const std::string& input : mOptions.overlayFiles) {
+            if (!processFile(input, true)) {
+                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+                return 1;
+            }
         }
 
-        if (!verifyNoExternalPackages(&mergedTable)) {
+        if (!verifyNoExternalPackages()) {
             return 1;
         }
 
         if (!mOptions.staticLib) {
             PrivateAttributeMover mover;
-            if (!mover.consume(&mContext, &mergedTable)) {
+            if (!mover.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(
                         DiagMessage() << "failed moving private attributes");
                 return 1;
@@ -537,22 +575,22 @@
 
         {
             IdAssigner idAssigner;
-            if (!idAssigner.consume(&mContext, &mergedTable)) {
+            if (!idAssigner.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
                 return 1;
             }
         }
 
-        mContext.mNameMangler = util::make_unique<NameMangler>(
-                NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
+        mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
+                mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
         mContext.mSymbols = JoinedSymbolTableBuilder()
-                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
+                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
                 .addSymbolTable(std::move(mContext.mSymbols))
                 .build();
 
         {
             ReferenceLinker linker;
-            if (!linker.consume(&mContext, &mergedTable)) {
+            if (!linker.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
                 return 1;
             }
@@ -566,6 +604,7 @@
             return 1;
         }
 
+        bool error = false;
         {
             ManifestFixerOptions manifestFixerOptions;
             manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
@@ -575,6 +614,11 @@
                 error = true;
             }
 
+            // AndroidManifest.xml has no resource name, but the CallSite is built from the name
+            // (aka, which package the AndroidManifest.xml is coming from).
+            // So we give it a package name so it can see local resources.
+            manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+
             XmlReferenceLinker manifestLinker;
             if (manifestLinker.consume(&mContext, manifestXml.get())) {
                 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
@@ -598,20 +642,26 @@
             }
         }
 
-        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 (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+            return 1;
+        }
+
+        for (const FileToProcess& file : mFilesToProcess) {
+            if (file.file.name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage()
+                                                    << "linking " << file.source.path);
                 }
 
-                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
+                std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
+                        file.source.path);
                 if (!xmlRes) {
                     return 1;
                 }
 
-                xmlRes->file = std::move(f.file);
+                xmlRes->file = std::move(file.file);
 
                 XmlReferenceLinker xmlLinker;
                 if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -631,7 +681,7 @@
                     }
 
                     if (!mOptions.noAutoVersion) {
-                        Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
+                        Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
                                 xmlRes->file.name);
                         for (int sdkLevel : xmlLinker.getSdkLevels()) {
                             if (sdkLevel > xmlRes->file.config.sdkVersion &&
@@ -639,7 +689,7 @@
                                                                     xmlRes->file.config,
                                                                     sdkLevel)) {
                                 xmlRes->file.config.sdkVersion = sdkLevel;
-                                if (!mergedTable.addFileReference(xmlRes->file.name,
+                                if (!mFinalTable.addFileReference(xmlRes->file.name,
                                                                   xmlRes->file.config,
                                                                   xmlRes->file.source,
                                                                   util::utf8ToUtf16(
@@ -662,10 +712,11 @@
                 }
             } else {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying "
+                                                    << file.source.path);
                 }
 
-                if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
+                if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
                                        archiveWriter.get())) {
                     error = true;
                 }
@@ -679,13 +730,13 @@
 
         if (!mOptions.noAutoVersion) {
             AutoVersioner versioner;
-            if (!versioner.consume(&mContext, &mergedTable)) {
+            if (!versioner.consume(&mContext, &mFinalTable)) {
                 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
                 return 1;
             }
         }
 
-        if (!flattenTable(&mergedTable, archiveWriter.get())) {
+        if (!flattenTable(&mFinalTable, archiveWriter.get())) {
             mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
             return 1;
         }
@@ -704,7 +755,7 @@
                 // to the original package, and private and public symbols to the private package.
 
                 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
-                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
                                    mContext.getCompilationPackage(), options)) {
                     return 1;
                 }
@@ -713,12 +764,12 @@
                 outputPackage = mOptions.privateSymbols.value();
             }
 
-            if (!writeJavaFile(&mergedTable, actualPackage, outputPackage, options)) {
+            if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
                 return 1;
             }
 
-            for (std::string& extraPackage : mOptions.extraJavaPackages) {
-                if (!writeJavaFile(&mergedTable, actualPackage, util::utf8ToUtf16(extraPackage),
+            for (const std::string& extraPackage : mOptions.extraJavaPackages) {
+                if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
                                    options)) {
                     return 1;
                 }
@@ -732,10 +783,10 @@
         }
 
         if (mOptions.verbose) {
-            Debug::printTable(&mergedTable);
-            for (; !tableMerger.getFileMergeQueue()->empty();
-                    tableMerger.getFileMergeQueue()->pop()) {
-                const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
+            Debug::printTable(&mFinalTable);
+            for (; !mTableMerger->getFileMergeQueue()->empty();
+                    mTableMerger->getFileMergeQueue()->pop()) {
+                const FileToMerge& f = mTableMerger->getFileMergeQueue()->front();
                 mContext.getDiagnostics()->note(
                         DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
                                       << std::hex << (uintptr_t) f.srcTable << std::dec);
@@ -744,17 +795,40 @@
 
         return 0;
     }
+
+private:
+    LinkOptions mOptions;
+    LinkContext mContext;
+    ResourceTable mFinalTable;
+    std::unique_ptr<TableMerger> mTableMerger;
+
+    struct FileToProcess {
+        ResourceFile file;
+        Source source;
+    };
+
+    struct FileToProcessComparator {
+        bool operator()(const FileToProcess& a, const FileToProcess& b) {
+            return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+        }
+    };
+
+    std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
 };
 
 int link(const std::vector<StringPiece>& args) {
     LinkOptions options;
     Maybe<std::string> privateSymbolsPackage;
     Maybe<std::string> minSdkVersion, targetSdkVersion;
+    std::vector<std::string> extraJavaPackages;
     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)
+            .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
+                              "The last conflicting resource given takes precedence.",
+                              &options.overlayFiles)
             .optionalFlag("--java", "Directory in which to generate R.java",
                           &options.generateJavaClassPath)
             .optionalFlag("--proguard", "Output file for generated Proguard rules",
@@ -775,7 +849,7 @@
                           "If not specified, public and private symbols will use the application's "
                           "package name", &privateSymbolsPackage)
             .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
-                              "package names", &options.extraJavaPackages)
+                              "package names", &extraJavaPackages)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -794,7 +868,15 @@
         options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
     }
 
-    LinkCommand cmd = { options };
+    // Populate the set of extra packages for which to generate R.java.
+    for (std::string& extraPackage : extraJavaPackages) {
+        // A given package can actually be a colon separated list of packages.
+        for (StringPiece package : util::split(extraPackage, ':')) {
+            options.extraJavaPackages.insert(package.toString());
+        }
+    }
+
+    LinkCommand cmd(options);
     return cmd.run(flags.getArgs());
 }
 
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 7b3fc35..4d3a483 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,7 +17,9 @@
 #ifndef AAPT_LINKER_LINKERS_H
 #define AAPT_LINKER_LINKERS_H
 
+#include "Resource.h"
 #include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 #include <set>
 
@@ -28,6 +30,14 @@
 struct ConfigDescription;
 
 /**
+ * Defines the location in which a value exists. This determines visibility of other
+ * package's private symbols.
+ */
+struct CallSite {
+    ResourceNameRef resource;
+};
+
+/**
  * Determines whether a versioned resource should be created. If a versioned resource already
  * exists, it takes precedence.
  */
@@ -39,7 +49,7 @@
 };
 
 struct XmlAutoVersioner : public IXmlResourceConsumer {
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 };
 
 /**
@@ -69,15 +79,6 @@
 };
 
 /**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
- * The ResourceTable must already have IDs assigned to each resource.
- * Once the ResourceTable is processed by this linker, it is ready to be flattened.
- */
-struct ReferenceLinker : public IResourceTableConsumer {
-    bool consume(IAaptContext* context, ResourceTable* table) override;
-};
-
-/**
  * Resolves attributes in the XmlResource and compiles string values to resource values.
  * Once an XmlResource is processed by this linker, it is ready to be flattened.
  */
@@ -86,7 +87,7 @@
     std::set<int> mSdkLevelsFound;
 
 public:
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 
     /**
      * Once the XmlResource has been consumed, this returns the various SDK levels in which
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 52d9426..2034c57 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -15,10 +15,9 @@
  */
 
 #include "ResourceUtils.h"
-#include "XmlDom.h"
-
 #include "link/ManifestFixer.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
@@ -63,7 +62,7 @@
     return true;
 }
 
-bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
     xml::Element* root = xml::findRootElement(doc->root.get());
     if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
         context->getDiagnostics()->error(DiagMessage(doc->file.source)
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 16e161d..a77e6d5 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,6 +18,10 @@
 #define AAPT_LINK_MANIFESTFIXER_H
 
 #include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
+#include <string>
 
 namespace aapt {
 
@@ -36,7 +40,7 @@
     ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
     }
 
-    bool consume(IAaptContext* context, XmlResource* doc) override;
+    bool consume(IAaptContext* context, xml::XmlResource* doc) override;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 5c5d8af..f6bf895 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "link/ManifestFixer.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -51,13 +50,13 @@
                 .build();
     }
 
-    std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+    std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
         return verifyWithOptions(str, {});
     }
 
-    std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
-                                                   const ManifestFixerOptions& options) {
-        std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+    std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
+                                                        const ManifestFixerOptions& options) {
+        std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
         ManifestFixer fixer(options);
         if (fixer.consume(mContext.get(), doc.get())) {
             return doc;
@@ -88,7 +87,7 @@
 TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
     ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
 
-    std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                 package="android">
         <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 5a2f5f0..3c8af4f 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "ResourceTable.h"
-
 #include "link/Linkers.h"
 
 #include <algorithm>
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 8c924b5..4b82537 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
+#include "ReferenceLinker.h"
+
 #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 "util/Util.h"
+#include "xml/XmlUtil.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <cassert>
@@ -41,52 +42,15 @@
  *
  * NOTE: All of the entries in the ResourceTable must be assigned IDs.
  */
-class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+class ReferenceLinkerVisitor : public ValueVisitor {
 private:
-    ReferenceLinkerVisitor mReferenceVisitor;
     IAaptContext* mContext;
     ISymbolTable* mSymbols;
-    IPackageDeclStack* mPackageDecls;
+    xml::IPackageDeclStack* mPackageDecls;
     StringPool* mStringPool;
+    CallSite* mCallSite;
     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.
@@ -94,8 +58,8 @@
     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);
+            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)) {
@@ -163,14 +127,16 @@
 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) {
+    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, StringPool* stringPool,
+                           xml::IPackageDeclStack* decl,CallSite* callSite) :
+            mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
+            mCallSite(callSite) {
     }
 
-    void visit(Reference* reference) override {
-        mReferenceVisitor.visit(reference);
+    void visit(Reference* ref) override {
+        if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
+            mError = true;
+        }
     }
 
     /**
@@ -184,13 +150,26 @@
         }
 
         for (Style::Entry& entry : style->entries) {
-            if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+            std::string errStr;
+
+            // Transform the attribute reference so that it is using the fully qualified package
+            // name. This will also mark the reference as being able to see private resources if
+            // there was a '*' in the reference or if the package came from the private namespace.
+            Reference transformedReference = entry.key;
+            transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
+                                            &transformedReference);
+
+            // Find the attribute in the symbol table and check if it is visible from this callsite.
+            const ISymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
+                    transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+            if (symbol) {
                 // Assign our style key the correct ID.
-                entry.key.id = s->id;
+                entry.key.id = symbol->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());
+                entry.value = parseValueWithAttribute(std::move(entry.value),
+                                                      symbol->attribute.get());
 
                 // Link/resolve the final value (mostly if it's a reference).
                 entry.value->accept(this);
@@ -201,13 +180,13 @@
                 entry.value->flatten(&val);
 
                 // Always allow references.
-                const uint32_t typeMask = s->attribute->typeMask |
+                const uint32_t typeMask = symbol->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(style->getSource());
-                    buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+                    buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
                     mContext->getDiagnostics()->error(msg);
                     mError = true;
                 }
@@ -219,23 +198,151 @@
                 } else {
                     msg << entry.key.id.value();
                 }
-                msg << "' not found";
+                msg << "' " << errStr;
                 mContext->getDiagnostics()->error(msg);
+                mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
                 mError = true;
             }
         }
     }
 
-    inline bool hasError() {
-        return mError || mReferenceVisitor.hasError();
+    bool hasError() {
+        return mError;
     }
 };
 
-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 };
+} // namespace
+
+/**
+ * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * or if the callsite comes from the same package.
+ */
+bool ReferenceLinker::isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+                                      const CallSite& callSite) {
+    if (!symbol.isPublic && !ref.privateReference) {
+        if (ref.name) {
+            return callSite.resource.package == ref.name.value().package;
+        } else if (ref.id) {
+            return ref.id.value().packageId() == symbol.id.packageId();
+        } else {
+            return false;
+        }
+    }
+    return true;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
+                                                           NameMangler* mangler,
+                                                           ISymbolTable* symbols) {
+    if (reference.name) {
+        Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+        return symbols->findByName(mangled ? mangled.value() : reference.name.value());
+    } else if (reference.id) {
+        return symbols->findById(reference.id.value());
+    } else {
+        return nullptr;
+    }
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
+        const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+        CallSite* callSite, std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+    if (!symbol) {
+        std::stringstream errStr;
+        errStr << "not found";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+
+    if (!isSymbolVisible(*symbol, reference, *callSite)) {
+        std::stringstream errStr;
+        errStr << "is private";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+    return symbol;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
+        const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+        CallSite* callSite, std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
+                                                                      symbols, callSite,
+                                                                      outError);
+    if (!symbol) {
+        return nullptr;
+    }
+
+    if (!symbol->attribute) {
+        std::stringstream errStr;
+        errStr << "is not an attribute";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+    return symbol;
+}
+
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
+                                                               NameMangler* nameMangler,
+                                                               ISymbolTable* symbols,
+                                                               CallSite* callSite,
+                                                               std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+    if (!symbol) {
+        return {};
+    }
+
+    if (!symbol->attribute) {
+        std::stringstream errStr;
+        errStr << "is not an attribute";
+        if (outError) *outError = errStr.str();
+        return {};
+    }
+    return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+}
+
+bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
+                                    ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+                                    CallSite* callSite) {
+    assert(reference);
+    assert(reference->name || reference->id);
+
+    Reference transformedReference = *reference;
+    transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+                                    &transformedReference);
+
+    std::string errStr;
+    const ISymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+            transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
+    if (s) {
+        reference->id = s->id;
+        return true;
+    }
+
+    DiagMessage errorMsg(reference->getSource());
+    errorMsg << "resource ";
+    if (reference->name) {
+        errorMsg << reference->name.value();
+        if (transformedReference.name.value() != reference->name.value()) {
+            errorMsg << " (aka " << transformedReference.name.value() << ")";
+        }
+    } else {
+        errorMsg << reference->id.value();
+    }
+
+    errorMsg << " " << errStr;
+    context->getDiagnostics()->error(errorMsg);
+    return false;
+}
+
+namespace {
+
+struct EmptyDeclStack : public xml::IPackageDeclStack {
+    Maybe<xml::ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override {
+        if (alias.empty()) {
+            return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
         }
         return {};
     }
@@ -259,14 +366,16 @@
                     error = true;
                 }
 
+                CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
+                ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+                                               &table->stringPool, &declStack, &callSite);
+
                 for (auto& configValue : entry->values) {
-                    StyleAndReferenceLinkerVisitor visitor(context,
-                                                           context->getExternalSymbols(),
-                                                           &table->stringPool, &declStack);
                     configValue.value->accept(&visitor);
-                    if (visitor.hasError()) {
-                        error = true;
-                    }
+                }
+
+                if (visitor.hasError()) {
+                    error = true;
                 }
             }
         }
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
new file mode 100644
index 0000000..6f11d58
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_REFERENCELINKER_H
+#define AAPT_LINKER_REFERENCELINKER_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "xml/XmlDom.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * The ResourceTable must already have IDs assigned to each resource.
+ * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ */
+struct ReferenceLinker : public IResourceTableConsumer {
+    /**
+     * Returns true if the symbol is visible by the reference and from the callsite.
+     */
+    static bool isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+                                const CallSite& callSite);
+
+    /**
+     * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
+     * if the symbol was not found.
+     */
+    static const ISymbolTable::Symbol* resolveSymbol(const Reference& reference,
+                                                     NameMangler* mangler, ISymbolTable* symbols);
+
+    /**
+     * Performs name mangling and looks up the resource in the symbol table. If the symbol is
+     * not visible by the reference at the callsite, nullptr is returned. outError holds
+     * the error message.
+     */
+    static const ISymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
+                                                                    NameMangler* nameMangler,
+                                                                    ISymbolTable* symbols,
+                                                                    CallSite* callSite,
+                                                                    std::string* outError);
+
+    /**
+     * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
+     * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
+     */
+    static const ISymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
+                                                                       NameMangler* nameMangler,
+                                                                       ISymbolTable* symbols,
+                                                                       CallSite* callSite,
+                                                                       std::string* outError);
+
+    /**
+     * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
+     * If resolution fails, outError holds the error message.
+     */
+    static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
+                                                         NameMangler* nameMangler,
+                                                         ISymbolTable* symbols,
+                                                         CallSite* callSite,
+                                                         std::string* outError);
+
+    /**
+     * Transforms the package name of the reference to the fully qualified package name using
+     * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
+     * to the reference at the callsite, the reference is updated with an ID.
+     * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
+     */
+    static bool linkReference(Reference* reference, IAaptContext* context, ISymbolTable* symbols,
+                              xml::IPackageDeclStack* decls, CallSite* callSite);
+
+    /**
+     * Links all references in the ResourceTable.
+     */
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
deleted file mode 100644
index c70531b..0000000
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ /dev/null
@@ -1,109 +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_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
index 5e7641a..8d324fe 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
 #include "process/SymbolTable.h"
 
 #include "test/Builders.h"
@@ -44,7 +44,7 @@
             .setSymbolTable(JoinedSymbolTableBuilder()
                             .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
                             .addSymbolTable(test::StaticSymbolTableBuilder()
-                                    .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+                                    .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034))
                                     .build())
                             .build())
             .build();
@@ -92,12 +92,12 @@
             .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),
+                    .addPublicSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+                    .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001),
                                test::AttributeBuilder()
                                     .setTypeMask(android::ResTable_map::TYPE_COLOR)
                                     .build())
-                    .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+                    .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002),
                                test::AttributeBuilder()
                                     .setTypeMask(android::ResTable_map::TYPE_FLAGS)
                                     .addItem(u"one", 0x01)
@@ -132,7 +132,7 @@
             .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",
+                    .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo",
                                ResourceId(0x7f010000), test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
                     .build())
@@ -156,4 +156,78 @@
     EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
 }
 
+TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@android:string/hidden")
+            .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/hidden", ResourceId(0x01040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
+    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.lib:string/hidden")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@com.app.test:string/com.app.lib$hidden",
+                                               ResourceId(0x7f040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+                    .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff"))
+                    .build())
+            .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:attr/hidden", ResourceId(0x01010001),
+                                               test::AttributeBuilder()
+                                                    .setTypeMask(android::ResTable_map::TYPE_COLOR)
+                                                    .build())
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1eea410..a06a1bf 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,7 +37,7 @@
 /**
  * This will merge packages with the same package name (or no package name).
  */
-bool TableMerger::merge(const Source& src, ResourceTable* table) {
+bool TableMerger::merge(const Source& src, ResourceTable* table, bool overrideExisting) {
     const uint8_t desiredPackageId = mContext->getPackageId();
 
     bool error = false;
@@ -55,7 +55,7 @@
             // 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(), false)) {
+            if (!doMerge(src, table, package.get(), false, overrideExisting)) {
                 error = true;
             }
         }
@@ -79,7 +79,7 @@
 
         bool mangle = packageName != mContext->getCompilationPackage();
         mMergedPackages.insert(package->name);
-        if (!doMerge(src, table, package.get(), mangle)) {
+        if (!doMerge(src, table, package.get(), mangle, false)) {
             error = true;
         }
     }
@@ -87,7 +87,8 @@
 }
 
 bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
-                          ResourceTablePackage* srcPackage, const bool manglePackage) {
+                          ResourceTablePackage* srcPackage, const bool manglePackage,
+                          const bool overrideExisting) {
     bool error = false;
 
     for (auto& srcType : srcPackage->types) {
@@ -149,7 +150,7 @@
                 if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
                             iter->value.get(), srcValue.value.get());
-                    if (collisionResult == 0) {
+                    if (collisionResult == 0 && !overrideExisting) {
                         // Error!
                         ResourceNameRef resourceName(srcPackage->name,
                                                      srcType->type,
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index c903f1b..a2c9dbf 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -63,7 +63,7 @@
     /**
      * Merges resources from the same or empty package. This is for local sources.
      */
-    bool merge(const Source& src, ResourceTable* table);
+    bool merge(const Source& src, ResourceTable* table, bool overrideExisting);
 
     /**
      * Merges resources from the given package, mangling the name. This is for static libraries.
@@ -79,7 +79,7 @@
     std::queue<FileToMerge> mFilesToMerge;
 
     bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage,
-                 const bool manglePackage);
+                 const bool manglePackage, const bool overrideExisting);
 
     std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package,
                                           Value* value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 0af4314..b7ffba7 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -59,7 +59,7 @@
     ResourceTable finalTable;
     TableMerger merger(mContext.get(), &finalTable);
 
-    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
     ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
 
     EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0);
@@ -89,7 +89,7 @@
     ResourceTable finalTable;
     TableMerger merger(mContext.get(), &finalTable);
 
-    ASSERT_TRUE(merger.merge({}, tableA.get()));
+    ASSERT_TRUE(merger.merge({}, tableA.get(), false));
     ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get()));
 
     FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file");
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 147b9bf..a26d763 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -17,49 +17,91 @@
 #include "Diagnostics.h"
 #include "ResourceUtils.h"
 #include "SdkConstants.h"
-#include "XmlDom.h"
-
 #include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
+#include "link/ReferenceLinker.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
 namespace {
 
-class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+/**
+ * Visits all references (including parents of styles, references in styles, arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * as needed.
+ */
+class ReferenceVisitor : public ValueVisitor {
 private:
     IAaptContext* mContext;
     ISymbolTable* mSymbols;
+    xml::IPackageDeclStack* mDecls;
+    CallSite* mCallSite;
+    bool mError;
+
+public:
+    using ValueVisitor::visit;
+
+    ReferenceVisitor(IAaptContext* context, ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+                     CallSite* callSite) :
+             mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
+             mError(false) {
+    }
+
+    void visit(Reference* ref) override {
+        if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
+            mError = true;
+        }
+    }
+
+    bool hasError() const {
+        return mError;
+    }
+};
+
+/**
+ * Visits each xml Element and compiles the attributes within.
+ */
+class XmlVisitor : public xml::PackageAwareVisitor {
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    Source mSource;
     std::set<int>* mSdkLevelsFound;
-    ReferenceLinkerVisitor mReferenceLinkerVisitor;
+    CallSite* mCallSite;
+    ReferenceVisitor mReferenceVisitor;
     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) {
+    XmlVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
+               std::set<int>* sdkLevelsFound, CallSite* callSite) :
+            mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
+            mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
     }
 
     void visit(xml::Element* el) override {
+        const Source source = mSource.withLine(el->lineNumber);
         for (xml::Attribute& attr : el->attributes) {
-            Maybe<std::u16string> maybePackage =
-                    util::extractPackageFromNamespace(attr.namespaceUri);
+            Maybe<xml::ExtractedPackage> maybePackage =
+                    xml::extractPackageFromNamespace(attr.namespaceUri);
             if (maybePackage) {
                 // There is a valid package name for this attribute. We will look this up.
-                StringPiece16 package = maybePackage.value();
+                StringPiece16 package = maybePackage.value().package;
                 if (package.empty()) {
                     // Empty package means the 'current' or 'local' package.
                     package = mContext->getCompilationPackage();
                 }
 
-                attr.compiledAttribute = compileAttribute(
-                        ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+                Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
+                attrRef.privateReference = maybePackage.value().privateNamespace;
+
+                std::string errStr;
+                attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+                        attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
 
                 // Convert the string value into a compiled Value if this is a valid attribute.
                 if (attr.compiledAttribute) {
@@ -76,15 +118,16 @@
                             !(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);
+                                DiagMessage(source) << "'" << attr.value << "' "
+                                                    << "is incompatible with attribute "
+                                                    << package << ":" << attr.name << " "
+                                                    << *attribute);
                         mError = true;
                     }
                 } else {
-                    mContext->getDiagnostics()->error(
-                            DiagMessage() << "attribute '" << package << ":" << attr.name
-                                          << "' was not found");
+                    mContext->getDiagnostics()->error(DiagMessage(source)
+                                                      << "attribute '" << package << ":"
+                                                      << attr.name << "' " << errStr);
                     mError = true;
 
                 }
@@ -95,7 +138,8 @@
 
             if (attr.compiledValue) {
                 // With a compiledValue, we must resolve the reference and assign it an ID.
-                attr.compiledValue->accept(&mReferenceLinkerVisitor);
+                attr.compiledValue->setSource(source);
+                attr.compiledValue->accept(&mReferenceVisitor);
             }
         }
 
@@ -103,27 +147,18 @@
         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();
+    bool hasError() {
+        return mError || mReferenceVisitor.hasError();
     }
 };
 
 } // namespace
 
-bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
     mSdkLevelsFound.clear();
-    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+    CallSite callSite = { resource->file.name };
+    XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+                       &mSdkLevelsFound, &callSite);
     if (resource->root) {
         resource->root->accept(&visitor);
         return !visitor.hasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 7f91ec3..3bfaf91 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -31,37 +31,40 @@
                 .setNameManglerPolicy(
                         NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
                 .setSymbolTable(test::StaticSymbolTableBuilder()
-                        .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+                        .addPublicSymbol(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),
+                        .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+                        .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002),
                                    test::AttributeBuilder().build())
-                        .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+                        .addPublicSymbol(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),
+                        .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
                                    test::AttributeBuilder().build())
 
-                        .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+                        // Private symbol.
+                        .addSymbol(u"@android:color/hidden", ResourceId(0x01020001))
+
+                        .addPublicSymbol(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",
+                        .addPublicSymbol(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),
+                        .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
                                    test::AttributeBuilder().build())
                         .build())
                 .build();
@@ -71,23 +74,8 @@
     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(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
         <View xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:background="@color/green"
@@ -97,7 +85,7 @@
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
@@ -132,8 +120,26 @@
     ASSERT_EQ(xmlAttr->compiledValue, nullptr);
 }
 
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:colorAccent="@android:color/hidden" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+    <View xmlns:android="http://schemas.android.com/apk/res/android"
+          android:colorAccent="@*android:color/hidden" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+}
+
 TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
         <View xmlns:android="http://schemas.android.com/apk/res/android"
               android:colorAccent="#ffffff" />)EOF");
 
@@ -143,14 +149,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), 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());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(
@@ -162,14 +168,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), 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());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
@@ -185,7 +191,7 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), 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"
@@ -195,7 +201,7 @@
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     // All attributes and references in this element should be referring to "android" (0x01).
@@ -225,14 +231,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), 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());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     // All attributes and references in this element should be referring to "com.app.test" (0x7f).
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 24ad05d..a2528d2 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -49,25 +49,13 @@
 };
 
 namespace xml {
-struct Node;
+struct XmlResource;
 }
 
-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;
+    virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 9a8b263..d04181d 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -51,6 +51,7 @@
 
     std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
     symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
+    symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
 
     if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
         const ConfigDescription kDefaultConfig;
@@ -77,16 +78,8 @@
 }
 
 
-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;
-    }
-
+static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
+                                                                    ResourceId id) {
     // Try as a bag.
     const android::ResTable::bag_entry* entry;
     ssize_t count = table.lockBag(id.id, &entry);
@@ -148,15 +141,25 @@
     for (const auto& asset : mAssets) {
         const android::ResTable& table = asset->getResources(false);
         StringPiece16 typeStr = toString(name.type);
+        uint32_t typeSpecFlags = 0;
         ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
                                                    typeStr.data(), typeStr.size(),
-                                                   name.package.data(), name.package.size());
+                                                   name.package.data(), name.package.size(),
+                                                   &typeSpecFlags);
         if (!resId.isValid()) {
             continue;
         }
 
-        std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+        std::shared_ptr<Symbol> s;
+        if (name.type == ResourceType::kAttr) {
+            s = lookupAttributeInTable(table, resId);
+        } else {
+            s = std::make_shared<Symbol>();
+            s->id = resId;
+        }
+
         if (s) {
+            s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
             mCache.put(name, s);
             return s.get();
         }
@@ -164,6 +167,44 @@
     return nullptr;
 }
 
+static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
+    android::ResTable::resource_name resName;
+    if (!table.getResourceName(id.id, true, &resName)) {
+        return {};
+    }
+
+    ResourceName name;
+    if (resName.package) {
+        name.package = StringPiece16(resName.package, resName.packageLen).toString();
+    }
+
+    const ResourceType* type;
+    if (resName.type) {
+        type = parseResourceType(StringPiece16(resName.type, resName.typeLen));
+
+    } else if (resName.type8) {
+        type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen)));
+    } else {
+        return {};
+    }
+
+    if (!type) {
+        return {};
+    }
+
+    name.type = *type;
+
+    if (resName.name) {
+        name.entry = StringPiece16(resName.name, resName.nameLen).toString();
+    } else if (resName.name8) {
+        name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen));
+    } else {
+        return {};
+    }
+
+    return name;
+}
+
 const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
         ResourceId id) {
     if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
@@ -173,8 +214,24 @@
     for (const auto& asset : mAssets) {
         const android::ResTable& table = asset->getResources(false);
 
-        std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+        Maybe<ResourceName> maybeName = getResourceName(table, id);
+        if (!maybeName) {
+            continue;
+        }
+
+        uint32_t typeSpecFlags = 0;
+        table.getResourceFlags(id.id, &typeSpecFlags);
+
+        std::shared_ptr<Symbol> s;
+        if (maybeName.value().type == ResourceType::kAttr) {
+            s = lookupAttributeInTable(table, id);
+        } else {
+            s = std::make_shared<Symbol>();
+            s->id = id;
+        }
+
         if (s) {
+            s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
             mIdCache.put(id, s);
             return s.get();
         }
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 89cd972..9ca694a 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,10 +19,9 @@
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "XmlDom.h"
-#include "util/Util.h"
-
 #include "test/Common.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
 
 #include <memory>
 
@@ -212,15 +211,22 @@
     }
 };
 
-inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+inline std::unique_ptr<xml::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, {});
+    std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {});
     assert(doc);
     return doc;
 }
 
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
+                                                                   const StringPiece& str) {
+    std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+    doc->file.name.package = context->getCompilationPackage().toString();
+    return doc;
+}
+
 } // namespace test
 } // namespace aapt
 
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4fa4918..555a539 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -135,8 +135,19 @@
     std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
 
 public:
+    StaticSymbolTableBuilder& addPublicSymbol(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));
+        symbol->isPublic = true;
+        mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+        mSymbolTable->mIdMap[id] = symbol.get();
+        mSymbolTable->mSymbols.push_back(std::move(symbol));
+        return *this;
+    }
+
     StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
-                                  std::unique_ptr<Attribute> attr = {}) {
+                                        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();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 0d17e84..49625b5 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -97,19 +97,19 @@
     return !error;
 }
 
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
     if (!mSymbolEntries || mSymbolEntryCount == 0) {
-        return false;
+        return {};
     }
 
     if ((uintptr_t) data < (uintptr_t) mData) {
-        return false;
+        return {};
     }
 
     // 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;
+        return {};
     }
 
     for (size_t i = 0; i < mSymbolEntryCount; i++) {
@@ -118,24 +118,23 @@
             const StringPiece16 str = util::getString(
                     mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
 
-            StringPiece16 typeStr;
-            ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
-                                               &outSymbol->entry);
-            const ResourceType* type = parseResourceType(typeStr);
-            if (!type) {
-                return false;
+            ResourceNameRef nameRef;
+            bool privateRef = false;
+            if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
+                return {};
             }
 
-            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;
+
+            Reference ref(nameRef);
+            ref.privateReference = privateRef;
+            return Maybe<Reference>(std::move(ref));
         }
     }
-    return false;
+    return {};
 }
 
 /**
@@ -304,6 +303,11 @@
         return false;
     }
 
+    // There can be multiple packages in a table, so
+    // clear the type and key pool in case they were set from a previous package.
+    mTypePool.uninit();
+    mKeyPool.uninit();
+
     ResChunkPullParser parser(getChunkData(&packageHeader->header),
                               getChunkDataLen(&packageHeader->header));
     while (ResChunkPullParser::isGoodEvent(parser.next())) {
@@ -561,7 +565,13 @@
             resourceValue = parseValue(name, config, value, entry->flags);
         }
 
-        assert(resourceValue && "failed to interpret valid resource");
+        if (!resourceValue) {
+            mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                              << "failed to parse value for resource " << name
+                                              << " (" << resId << ") with configuration '"
+                                              << config << "'");
+            return false;
+        }
 
         Source source = mSource;
         if (sourceBlock) {
@@ -652,7 +662,7 @@
     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;
+                Reference::Type::kResource : Reference::Type::kAttribute;
 
         if (data != 0) {
             // This is a normal reference.
@@ -660,9 +670,9 @@
         }
 
         // 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);
+        if (Maybe<Reference> ref = getSymbol(&value->data)) {
+            ref.value().referenceType = type;
+            return util::make_unique<Reference>(std::move(ref.value()));
         }
 
         // This is not an unresolved symbol, so it must be the magic @null reference.
@@ -710,10 +720,8 @@
     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());
-        }
+        style->parent = getSymbol(&map->parent.ident);
+
     } else {
          // The parent is a regular reference to a resource.
         style->parent = Reference(util::deviceToHost32(map->parent.ident));
@@ -726,10 +734,14 @@
         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();
+            Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
+            if (!symbol) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "unresolved style attribute");
+                return {};
+            }
+            styleEntry.key = std::move(symbol.value());
+
         } else {
             // The map entry's key (attribute) is a regular reference.
             styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -737,7 +749,9 @@
 
         // Parse the attribute's value.
         styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
-        assert(styleEntry.value);
+        if (!styleEntry.value) {
+            return {};
+        }
     }
     return style;
 }
@@ -768,10 +782,14 @@
             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();
+                Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+                if (!ref) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "unresolved attribute symbol");
+                    return {};
+                }
+                symbol.symbol = std::move(ref.value());
+
             } else {
                 // The map entry's key (id) is a regular reference.
                 symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -803,10 +821,14 @@
         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);
+            Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+            if (!ref) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "unresolved styleable symbol");
+                return {};
+            }
+            styleable->entries.emplace_back(std::move(ref.value()));
+
         } else {
             // The map entry's key (attribute) is a regular reference.
             styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
@@ -821,6 +843,9 @@
     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);
+        if (!item) {
+            return {};
+        }
 
         switch (util::deviceToHost32(mapEntry.name.ident)) {
             case android::ResTable_map::ATTR_ZERO:
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 02c4081..73fcf52 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -57,7 +57,7 @@
 private:
     // Helper method to retrieve the symbol name for a given table offset specified
     // as a pointer.
-    bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+    Maybe<Reference> getSymbol(const void* data);
 
     bool parseTable(const android::ResChunk_header* chunk);
     bool parseSymbolTable(const android::ResChunk_header* chunk);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 59b8385..9ecc974 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -28,9 +28,6 @@
 namespace aapt {
 namespace util {
 
-constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
-constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
-
 static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
         const std::function<char(char)>& f) {
     std::vector<std::string> parts;
@@ -467,18 +464,6 @@
     return data;
 }
 
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
-    if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
-        StringPiece16 schemaPrefix = kSchemaPrefix;
-        StringPiece16 package = namespaceUri;
-        return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
-                .toString();
-    } else if (namespaceUri == kSchemaAuto) {
-        return std::u16string();
-    }
-    return {};
-}
-
 bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
                              StringPiece16* outEntry, StringPiece16* outSuffix) {
     if (!stringStartsWith<char16_t>(path, u"res/")) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 80552a5..a898619 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -229,11 +229,12 @@
     private:
         friend class Tokenizer<Char>;
 
-        iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+        iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
 
-        BasicStringPiece<Char> str;
-        Char separator;
-        BasicStringPiece<Char> token;
+        BasicStringPiece<Char> mStr;
+        Char mSeparator;
+        BasicStringPiece<Char> mToken;
+        bool mEnd;
     };
 
     Tokenizer(BasicStringPiece<Char> str, Char sep);
@@ -252,36 +253,38 @@
 
 template <typename Char>
 typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
-    const Char* start = token.end();
-    const Char* end = str.end();
+    const Char* start = mToken.end();
+    const Char* end = mStr.end();
     if (start == end) {
-        token.assign(token.end(), 0);
+        mEnd = true;
+        mToken.assign(mToken.end(), 0);
         return *this;
     }
 
     start += 1;
     const Char* current = start;
     while (current != end) {
-        if (*current == separator) {
-            token.assign(start, current - start);
+        if (*current == mSeparator) {
+            mToken.assign(start, current - start);
             return *this;
         }
         ++current;
     }
-    token.assign(start, end - start);
+    mToken.assign(start, end - start);
     return *this;
 }
 
 template <typename Char>
 inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
-    return token;
+    return mToken;
 }
 
 template <typename Char>
 inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
     // We check equality here a bit differently.
     // We need to know that the addresses are the same.
-    return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+    return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
+            mEnd == rhs.mEnd;
 }
 
 template <typename Char>
@@ -291,8 +294,8 @@
 
 template <typename Char>
 inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
-                                           BasicStringPiece<Char> tok) :
-        str(s), separator(sep), token(tok) {
+                                           BasicStringPiece<Char> tok, bool end) :
+        mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
 }
 
 template <typename Char>
@@ -307,8 +310,8 @@
 
 template <typename Char>
 inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
-        mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
-        mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+        mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
+        mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
 }
 
 inline uint16_t hostToDevice16(uint16_t value) {
@@ -328,15 +331,6 @@
 }
 
 /**
- * Returns a package name if the namespace URI is of the form:
- * http://schemas.android.com/apk/res/<package>
- *
- * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
- * returns an empty package name.
- */
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
-
-/**
  * Given a path like: res/xml-sw600dp/foo.xml
  *
  * Extracts "res/xml-sw600dp/" into outPrefix.
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 9db9fb7..9208e07 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -101,6 +101,15 @@
     ASSERT_EQ(tokenizer.end(), iter);
 }
 
+TEST(UtilTest, TokenizeEmptyString) {
+    auto tokenizer = util::tokenize(StringPiece16(u""), u'|');
+    auto iter = tokenizer.begin();
+    ASSERT_NE(tokenizer.end(), iter);
+    ASSERT_EQ(StringPiece16(), *iter);
+    ++iter;
+    ASSERT_EQ(tokenizer.end(), iter);
+}
+
 TEST(UtilTest, TokenizeAtEnd) {
     auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
     auto iter = tokenizer.begin();
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
similarity index 91%
rename from tools/aapt2/XmlDom.cpp
rename to tools/aapt2/xml/XmlDom.cpp
index b769c76..d27b62fd 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "util/Util.h"
 #include "XmlDom.h"
 #include "XmlPullParser.h"
+#include "util/Util.h"
 
 #include <cassert>
+#include <expat.h>
 #include <memory>
 #include <stack>
 #include <string>
@@ -317,6 +318,10 @@
     return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
+Element* findRootElement(XmlResource* doc) {
+    return findRootElement(doc->root.get());
+}
+
 Element* findRootElement(Node* node) {
     if (!node) {
         return nullptr;
@@ -397,5 +402,39 @@
     return elements;
 }
 
+void PackageAwareVisitor::visit(Namespace* ns) {
+   bool added = false;
+   if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
+       ExtractedPackage& package = maybePackage.value();
+       mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
+       added = true;
+   }
+
+   Visitor::visit(ns);
+
+   if (added) {
+       mPackageDecls.pop_back();
+   }
+}
+
+Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
+       const StringPiece16& alias, const StringPiece16& localPackage) const {
+   if (alias.empty()) {
+       return ExtractedPackage{ localPackage.toString(), false /* private */ };
+   }
+
+   const auto rend = mPackageDecls.rend();
+   for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+       if (alias == iter->prefix) {
+           if (iter->package.package.empty()) {
+               return ExtractedPackage{ localPackage.toString(),
+                                              iter->package.privateNamespace };
+           }
+           return iter->package;
+       }
+   }
+   return {};
+}
+
 } // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/xml/XmlDom.h
similarity index 76%
rename from tools/aapt2/XmlDom.h
rename to tools/aapt2/xml/XmlDom.h
index 9a46bcb..033b0a4 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -22,11 +22,9 @@
 #include "ResourceValues.h"
 #include "util/StringPiece.h"
 #include "util/Util.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlUtil.h"
 
 #include <istream>
-#include <expat.h>
 #include <memory>
 #include <string>
 #include <vector>
@@ -34,21 +32,9 @@
 namespace aapt {
 namespace xml {
 
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
 struct RawVisitor;
 
 /**
- * The type of node. Can be used to downcast to the concrete XML node
- * class.
- */
-enum class NodeType {
-    kNamespace,
-    kElement,
-    kText,
-};
-
-/**
  * Base class for all XML nodes.
  */
 struct Node {
@@ -58,9 +44,10 @@
     std::u16string comment;
     std::vector<std::unique_ptr<Node>> children;
 
+    virtual ~Node() = default;
+
     void addChild(std::unique_ptr<Node> child);
     virtual void accept(RawVisitor* visitor) = 0;
-    virtual ~Node() {}
 };
 
 /**
@@ -122,6 +109,14 @@
 };
 
 /**
+ * An XML resource with a source, name, and XML tree.
+ */
+struct XmlResource {
+    ResourceFile file;
+    std::unique_ptr<xml::Node> root;
+};
+
+/**
  * Inflates an XML DOM from a text stream, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
@@ -134,6 +129,7 @@
 std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
                                      const Source& source);
 
+Element* findRootElement(XmlResource* doc);
 Element* findRootElement(Node* node);
 
 /**
@@ -180,7 +176,7 @@
 private:
     struct PackageDecl {
         std::u16string prefix;
-        std::u16string package;
+        ExtractedPackage package;
     };
 
     std::vector<PackageDecl> mPackageDecls;
@@ -188,41 +184,9 @@
 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 {};
-    }
+    void visit(Namespace* ns) override;
+    Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override;
 };
 
 // Implementations
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
similarity index 93%
rename from tools/aapt2/XmlDom_test.cpp
rename to tools/aapt2/xml/XmlDom_test.cpp
index a1b9ed0..431ee2c 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -38,7 +38,7 @@
 
     const Source source = { "test.xml" };
     StdErrDiagnostics diag;
-    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+    std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
     ASSERT_NE(doc, nullptr);
 
     xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
similarity index 85%
rename from tools/aapt2/XmlPullParser.cpp
rename to tools/aapt2/xml/XmlPullParser.cpp
index cff935c..323ec05 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -16,12 +16,14 @@
 
 #include "util/Maybe.h"
 #include "util/Util.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
+#include "xml/XmlUtil.h"
 
 #include <iostream>
 #include <string>
 
 namespace aapt {
+namespace xml {
 
 constexpr char kXmlNamespaceSep = 1;
 
@@ -72,14 +74,14 @@
     // Record namespace prefixes and package names so that we can do our own
     // handling of references that use namespace aliases.
     if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
-        Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+        Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
         if (event == Event::kStartNamespace) {
             if (result) {
-                mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+                mPackageAliases.emplace_back(
+                        PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
             }
         } else {
             if (result) {
-                assert(mPackageAliases.back().second == result.value());
                 mPackageAliases.pop_back();
             }
         }
@@ -131,20 +133,20 @@
     return mEventQueue.front().data2;
 }
 
-Maybe<ResourceName> XmlPullParser::transformPackage(
-        const ResourceName& name, const StringPiece16& localPackage) const {
-    if (name.package.empty()) {
-        return ResourceName{ localPackage.toString(), name.type, name.entry };
+Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
+        const StringPiece16& alias, const StringPiece16& localPackage) const {
+    if (alias.empty()) {
+        return ExtractedPackage{ localPackage.toString(), false /* private */ };
     }
 
     const auto endIter = mPackageAliases.rend();
     for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-        if (name.package == iter->first) {
-            if (iter->second.empty()) {
-                return ResourceName{ localPackage.toString(), name.type, name.entry };
-            } else {
-                return ResourceName{ iter->second, name.type, name.entry };
+        if (alias == iter->prefix) {
+            if (iter->package.package.empty()) {
+                return ExtractedPackage{ localPackage.toString(),
+                                         iter->package.privateNamespace };
             }
+            return iter->package;
         }
     }
     return {};
@@ -283,4 +285,24 @@
     });
 }
 
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        return StringPiece16(util::trimWhitespace(iter->value));
+    }
+    return {};
+}
+
+Maybe<StringPiece16> findNonEmptyAttribute(const 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 {};
+}
+
+} // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
similarity index 91%
rename from tools/aapt2/XmlPullParser.h
rename to tools/aapt2/xml/XmlPullParser.h
index a0ce21d..7e7070e 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,11 +17,11 @@
 #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 "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
 
 #include <algorithm>
 #include <expat.h>
@@ -33,6 +33,7 @@
 #include <vector>
 
 namespace aapt {
+namespace xml {
 
 class XmlPullParser : public IPackageDeclStack {
 public:
@@ -60,7 +61,7 @@
     static bool isGoodEvent(Event event);
 
     XmlPullParser(std::istream& in);
-    virtual ~XmlPullParser();
+    ~XmlPullParser();
 
     /**
      * Returns the current event that is being processed.
@@ -95,6 +96,13 @@
     const std::u16string& getNamespacePrefix() const;
     const std::u16string& getNamespaceUri() const;
 
+    //
+    // These are available for StartElement and EndElement.
+    //
+
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
     /*
      * Uses the current stack of namespaces to resolve the package. Eg:
      * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
@@ -106,17 +114,8 @@
      * If xmlns:app="http://schemas.android.com/apk/res-auto", then
      * 'package' will be set to 'defaultPackage'.
      */
-    //
-
-    //
-    // These are available for StartElement and EndElement.
-    //
-
-    const std::u16string& getElementNamespace() const;
-    const std::u16string& getElementName() const;
-
-    Maybe<ResourceName> transformPackage(const ResourceName& name,
-                                         const StringPiece16& localPackage) const override;
+    Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override;
 
     //
     // Remaining methods are for retrieving information about attributes
@@ -169,9 +168,25 @@
     const std::u16string mEmpty;
     size_t mDepth;
     std::stack<std::u16string> mNamespaceUris;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+
+    struct PackageDecl {
+        std::u16string prefix;
+        ExtractedPackage package;
+    };
+    std::vector<PackageDecl> mPackageAliases;
 };
 
+/**
+ * Finds the attribute in the current element within the global namespace.
+ */
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
+/**
+ * Finds the attribute in the current element within the global namespace. The attribute's value
+ * must not be the empty string.
+ */
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
 //
 // Implementation
 //
@@ -277,6 +292,7 @@
     return endIter;
 }
 
+} // namespace xml
 } // namespace aapt
 
 #endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
similarity index 63%
rename from tools/aapt2/XmlPullParser_test.cpp
rename to tools/aapt2/xml/XmlPullParser_test.cpp
index 1c99a43..8fa2c6d 100644
--- a/tools/aapt2/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "util/StringPiece.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -26,30 +26,30 @@
     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);
+    xml::XmlPullParser parser(str);
 
     const size_t depthOuter = parser.getDepth();
-    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
 
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(xml::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());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
+    EXPECT_EQ(xml::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());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(xml::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());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(xml::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());
+    ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+    EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
new file mode 100644
index 0000000..ab9f544
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -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.
+ */
+
+#include "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+    if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) {
+        StringPiece16 schemaPrefix = kSchemaPublicPrefix;
+        StringPiece16 package = namespaceUri;
+        package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+        if (package.empty()) {
+            return {};
+        }
+        return ExtractedPackage{ package.toString(), false /* isPrivate */ };
+
+    } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) {
+        StringPiece16 schemaPrefix = kSchemaPrivatePrefix;
+        StringPiece16 package = namespaceUri;
+        package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+        if (package.empty()) {
+            return {};
+        }
+        return ExtractedPackage{ package.toString(), true /* isPrivate */ };
+
+    } else if (namespaceUri == kSchemaAuto) {
+        return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+    }
+    return {};
+}
+
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+                                     const StringPiece16& localPackage, Reference* inRef) {
+    if (inRef->name) {
+        if (Maybe<ExtractedPackage> transformedPackage =
+                   declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
+            ExtractedPackage& extractedPackage = transformedPackage.value();
+            inRef->name.value().package = std::move(extractedPackage.package);
+
+            // If the reference was already private (with a * prefix) and the namespace is public,
+            // we keep the reference private.
+            inRef->privateReference |= extractedPackage.privateNamespace;
+        }
+    }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
new file mode 100644
index 0000000..98e5520
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_XMLUTIL_H
+#define AAPT_XML_XMLUTIL_H
+
+#include "ResourceValues.h"
+#include "util/Maybe.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/";
+constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/";
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+/**
+ * Result of extracting a package name from a namespace URI declaration.
+ */
+struct ExtractedPackage {
+    /**
+     * The name of the package. This can be the empty string, which means that the package
+     * should be assumed to be the package being compiled.
+     */
+    std::u16string package;
+
+    /**
+     * True if the package's private namespace was declared. This means that private resources
+     * are made visible.
+     */
+    bool privateNamespace;
+};
+
+/**
+ * Returns an ExtractedPackage struct if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package> or
+ * http://schemas.android.com/apk/prv/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+/**
+ * Interface representing a stack of XML namespace declarations. When looking up the package
+ * for a namespace prefix, the stack is checked from top to bottom.
+ */
+struct IPackageDeclStack {
+    virtual ~IPackageDeclStack() = default;
+
+    /**
+     * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
+     */
+    virtual Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const = 0;
+};
+
+/**
+ * Helper function for transforming the original Reference inRef to a fully qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * the package declaration was private.
+ */
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+                                     const StringPiece16& localPackage, Reference* inRef);
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
new file mode 100644
index 0000000..7796b3e
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -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.
+ */
+
+#include "test/Common.h"
+#include "xml/XmlUtil.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlUtilTest, ExtractPackageFromNamespace) {
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(
+            u"http://schemas.android.com/apk/prv/res/"));
+
+    Maybe<xml::ExtractedPackage> p =
+            xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"a"), p.value().package);
+    EXPECT_EQ(false, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"android"), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+}
+
+} // namespace aapt
diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
index e206d70..f758959 100644
--- a/tools/layoutlib/.idea/encodings.xml
+++ b/tools/layoutlib/.idea/encodings.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 61ddb04..53bfc15 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -30,6 +30,9 @@
 built_framework_dep := $(call java-lib-deps,framework)
 built_framework_classes := $(call java-lib-files,framework)
 
+built_oj_dep := $(call java-lib-deps,core-oj)
+built_oj_classes := $(call java-lib-files,core-oj)
+
 built_core_dep := $(call java-lib-deps,core-libart)
 built_core_classes := $(call java-lib-files,core-libart)
 
@@ -56,7 +59,8 @@
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
+                       $(built_core_dep) \
                        $(built_framework_dep) \
                        $(built_ext_dep) \
                        $(built_ext_data) \
@@ -69,6 +73,7 @@
 	$(hide) ls -l $(built_framework_classes)
 	$(hide) java -ea -jar $(built_layoutlib_create_jar) \
 	             $@ \
+	             $(built_oj_classes) \
 	             $(built_core_classes) \
 	             $(built_framework_classes) \
 	             $(built_ext_classes) \
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 5db1bde..723e827 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -23,6 +23,7 @@
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
 import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
@@ -126,6 +127,9 @@
             if (view == null) {
                 view = loadCustomView(name, attrs);
             }
+        } catch (InflateException e) {
+            // Don't catch the InflateException below as that results in hiding the real cause.
+            throw e;
         } catch (Exception e) {
             // Wrap the real exception in a ClassNotFoundException, so that the calling method
             // can deal with it.
@@ -154,23 +158,30 @@
                 }
                 ta.recycle();
             }
-            final Object lastContext = mConstructorArgs[0];
-            mConstructorArgs[0] = context;
-            // try to load the class from using the custom view loader
-            try {
-                view = loadCustomView(name, attrs);
-            } catch (Exception e2) {
-                // Wrap the real exception in an InflateException so that the calling
-                // method can deal with it.
-                InflateException exception = new InflateException();
-                if (!e2.getClass().equals(ClassNotFoundException.class)) {
-                    exception.initCause(e2);
-                } else {
-                    exception.initCause(e);
+            if (!(e.getCause() instanceof ClassNotFoundException)) {
+                // There is some unknown inflation exception in inflating a View that was found.
+                view = new MockView(context, attrs);
+                ((MockView) view).setText(name);
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
+            } else {
+                final Object lastContext = mConstructorArgs[0];
+                mConstructorArgs[0] = context;
+                // try to load the class from using the custom view loader
+                try {
+                    view = loadCustomView(name, attrs);
+                } catch (Exception e2) {
+                    // Wrap the real exception in an InflateException so that the calling
+                    // method can deal with it.
+                    InflateException exception = new InflateException();
+                    if (!e2.getClass().equals(ClassNotFoundException.class)) {
+                        exception.initCause(e2);
+                    } else {
+                        exception.initCause(e);
+                    }
+                    throw exception;
+                } finally {
+                    mConstructorArgs[0] = lastContext;
                 }
-                throw exception;
-            } finally {
-                mConstructorArgs[0] = lastContext;
             }
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 44a9aad..d392f21 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -17,39 +17,90 @@
 package com.android.layoutlib.bridge;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 /**
  * Base class for mocked views.
- *
- * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
- * (or better the id of the view).
+ * <p/>
+ * FrameLayout with a single TextView. Doesn't allow adding any other views to itself.
  */
-public class MockView extends TextView {
+public class MockView extends FrameLayout {
+
+    private final TextView mView;
+
+    public MockView(Context context) {
+        this(context, null);
+    }
 
     public MockView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public MockView(Context context, AttributeSet attrs, int defStyle) {
-        this(context, attrs, defStyle, 0);
+    public MockView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-
-        setText(this.getClass().getSimpleName());
-        setTextColor(0xFF000000);
+        mView = new TextView(context, attrs);
+        mView.setTextColor(0xFF000000);
         setGravity(Gravity.CENTER);
+        setText(getClass().getSimpleName());
+        addView(mView);
+        setBackgroundColor(0xFF7F7F7F);
+    }
+
+    // Only allow adding one TextView.
+    @Override
+    public void addView(View child) {
+        if (child == mView) {
+            super.addView(child);
+        }
     }
 
     @Override
-    public void onDraw(Canvas canvas) {
-        canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+    public void addView(View child, int index) {
+        if (child == mView) {
+            super.addView(child, index);
+        }
+    }
 
-        super.onDraw(canvas);
+    @Override
+    public void addView(View child, int width, int height) {
+        if (child == mView) {
+            super.addView(child, width, height);
+        }
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        if (child == mView) {
+            super.addView(child, params);
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (child == mView) {
+            super.addView(child, index, params);
+        }
+    }
+
+    // The following methods are called by the IDE via reflection, and should be considered part
+    // of the API.
+    // Historically, MockView used to be a textView and had these methods. Now, we simply delegate
+    // them to the contained textView.
+
+    public void setText(CharSequence text) {
+        mView.setText(text);
+    }
+
+    public void setGravity(int gravity) {
+        mView.setGravity(gravity);
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 11bd15d..5c73fb6a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -95,8 +95,8 @@
     }
 
     @Override
-    public void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
-            Rect outFrame) {
+    public void repositionChild(IWindow childWindow, int x, int y, int width, int height,
+            long deferTransactionUntilFrame, Rect outFrame) {
         // pass for now.
         return;
     }
@@ -167,6 +167,11 @@
     }
 
     @Override
+    public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
+        // pass for now
+    }
+
+    @Override
     public void dragRecipientEntered(IWindow window) throws RemoteException {
         // pass for now
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 868c6d3..cdcf0ea 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,10 +16,13 @@
 
 package com.android.layoutlib.bridge.bars;
 
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
 import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.SessionParams;
 import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 import com.android.resources.ResourceType;
@@ -45,6 +48,8 @@
 
     private Object mWindowDecorActionBar;
     private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
+    // This is used on v23.1.1 and later.
+    private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
     private Class<?> mWindowActionBarClass;
 
     /**
@@ -70,14 +75,25 @@
         try {
             Class[] constructorParams = {View.class};
             Object[] constructorArgs = {getDecorContent()};
-            mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS,
-                    constructorParams, constructorArgs);
+            LayoutlibCallback callback = params.getLayoutlibCallback();
 
+            // Check if the old action bar class is present.
+            String actionBarClass = WINDOW_ACTION_BAR_CLASS;
+            try {
+                callback.findClass(actionBarClass);
+            } catch (ClassNotFoundException expected) {
+                // Failed to find the old class, use the newer one.
+                actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW;
+            }
+
+            mWindowDecorActionBar = callback.loadView(actionBarClass,
+                    constructorParams, constructorArgs);
             mWindowActionBarClass = mWindowDecorActionBar == null ? null :
                     mWindowDecorActionBar.getClass();
             setupActionBar();
         } catch (Exception e) {
-            e.printStackTrace();
+            Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+                    "Failed to load AppCompat ActionBar with unknown error.", e);
         }
     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 42e55e2..a6e5fb8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -33,7 +33,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap_Delegate;
@@ -228,18 +227,16 @@
      * Find the background color for this bar from the theme attributes. Only relevant to StatusBar
      * and NavigationBar.
      * <p/>
-     * Returns null if not found.
+     * Returns 0 if not found.
      *
      * @param colorAttrName the attribute name for the background color
      * @param translucentAttrName the attribute name for the translucency property of the bar.
      *
      * @throws NumberFormatException if color resolved to an invalid string.
      */
-    @Nullable
-    protected Integer getBarColor(@NonNull String colorAttrName,
-            @NonNull String translucentAttrName) {
+    protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) {
         if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) {
-            return null;
+            return 0;
         }
         RenderResources renderResources = getContext().getRenderResources();
         // First check if the bar is translucent.
@@ -254,11 +251,10 @@
         if (transparent) {
             return getColor(renderResources, colorAttrName);
         }
-        return null;
+        return 0;
     }
 
-    @Nullable
-    private static Integer getColor(RenderResources renderResources, String attr) {
+    private static int getColor(RenderResources renderResources, String attr) {
         // From ?attr/foo to @color/bar. This is most likely an ItemResourceValue.
         ResourceValue resource = renderResources.findItemInTheme(attr, true);
         // Form @color/bar to the #AARRGGBB
@@ -279,7 +275,7 @@
                 }
             }
         }
-        return null;
+        return 0;
     }
 
     private ResourceValue getResourceValue(String reference) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index d50ce23..9c89bfe 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -65,8 +65,8 @@
         super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML,
                 "navigation_bar.xml", simulatedPlatformVersion);
 
-        Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
-        setBackgroundColor(color == null ? 0xFF000000 : color);
+        int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+        setBackgroundColor(color == 0 ? 0xFF000000 : color);
 
         // Cannot access the inside items through id because no R.id values have been
         // created for them.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
index 95a5a58..2dc7c65 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -71,9 +71,8 @@
         // FIXME: use FILL_H?
         setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT);
 
-        Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
-        setBackgroundColor(
-                color == null ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
+        int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+        setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
 
         // Cannot access the inside items through id because no R.id values have been
         // created for them.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 2a4f583..0ffa357 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -421,7 +421,8 @@
                     gc.setComposite(AlphaComposite.Src);
 
                     gc.setColor(new Color(0x00000000, true));
-                    gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+                    gc.fillRect(0, 0,
+                            mMeasuredScreenWidth, mMeasuredScreenHeight);
 
                     // done
                     gc.dispose();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d..7ef7566 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -17,6 +17,7 @@
 package com.android.tools.layoutlib.create;
 
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -40,6 +41,7 @@
     private final String mClassName;
     private final Set<String> mDelegateMethods;
     private final Log mLog;
+    private boolean mIsStaticInnerClass;
 
     /**
      * Creates a new {@link DelegateClassAdapter} that can transform some methods
@@ -62,16 +64,30 @@
         mLog = log;
         mClassName = className;
         mDelegateMethods = delegateMethods;
+        // If this is an inner class, by default, we assume it's static. If it's not we will detect
+        // by looking at the fields (see visitField)
+        mIsStaticInnerClass = className.contains("$");
     }
 
     //----------------------------------
     // Methods from the ClassAdapter
 
     @Override
+    public FieldVisitor visitField(int access, String name, String desc, String signature,
+            Object value) {
+        if (mIsStaticInnerClass && "this$0".equals(name)) {
+            // Having a "this$0" field, proves that this class is not a static inner class.
+            mIsStaticInnerClass = false;
+        }
+
+        return super.visitField(access, name, desc, signature, value);
+    }
+
+    @Override
     public MethodVisitor visitMethod(int access, String name, String desc,
             String signature, String[] exceptions) {
 
-        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+        boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
         boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
 
         boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
@@ -96,7 +112,8 @@
             MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
 
             DelegateMethodAdapter a = new DelegateMethodAdapter(
-                    mLog, null, mwDelegate, mClassName, name, desc, isStatic);
+                    mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
+                    mIsStaticInnerClass);
 
             // A native has no code to visit, so we need to generate it directly.
             a.generateDelegateCode();
@@ -120,6 +137,7 @@
                                                      desc, signature, exceptions);
 
         return new DelegateMethodAdapter(
-                mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+                mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
+                mIsStaticInnerClass);
     }
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 12690db..cca9e57 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -85,6 +85,8 @@
     private String mDesc;
     /** True if the original method is static. */
     private final boolean mIsStatic;
+    /** True if the method is contained in a static inner class */
+    private final boolean mIsStaticInnerClass;
     /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
     private final String mClassName;
     /** The method name. */
@@ -120,7 +122,8 @@
             String className,
             String methodName,
             String desc,
-            boolean isStatic) {
+            boolean isStatic,
+            boolean isStaticClass) {
         super(Opcodes.ASM4);
         mLog = log;
         mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@
         mMethodName = methodName;
         mDesc = desc;
         mIsStatic = isStatic;
+        mIsStaticInnerClass = isStaticClass;
     }
 
     /**
@@ -206,7 +210,7 @@
         // by the 'this' of any outer class, if any.
         if (!mIsStatic) {
 
-            if (outerType != null) {
+            if (outerType != null && !mIsStaticInnerClass) {
                 // The first-level inner class has a package-protected member called 'this$0'
                 // that points to the outer class.
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 648cea43..e37a09b 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
 import com.android.tools.layoutlib.create.dataclass.OuterClass;
 import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,8 @@
     private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
     private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
                                                    InnerClass.class.getSimpleName();
+    private static final String STATIC_INNER_CLASS_NAME =
+            OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
 
     @Before
     public void setUp() throws Exception {
@@ -294,6 +297,61 @@
         }
     }
 
+    @Test
+    public void testDelegateStaticInner() throws Throwable {
+        // We'll delegate the "get" method of both the inner and outer class.
+        HashSet<String> delegateMethods = new HashSet<String>();
+        delegateMethods.add("get");
+
+        // Generate the delegate for the outer class.
+        ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+        String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+                mLog, cwOuter, outerClassName, delegateMethods);
+        ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+        cr.accept(cvOuter, 0 /* flags */);
+
+        // Generate the delegate for the static inner class.
+        ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+        String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cvInner = new DelegateClassAdapter(
+                mLog, cwInner, innerClassName, delegateMethods);
+        cr = new ClassReader(STATIC_INNER_CLASS_NAME);
+        cr.accept(cvInner, 0 /* flags */);
+
+        // Load the generated classes in a different class loader and try them
+        ClassLoader2 cl2 = null;
+        try {
+            cl2 = new ClassLoader2() {
+                @Override
+                public void testModifiedInstance() throws Exception {
+
+                    // Check the outer class
+                    Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+                    Object o2 = outerClazz2.newInstance();
+                    assertNotNull(o2);
+
+                    // Check the inner class. Since it's not a static inner class, we need
+                    // to use the hidden constructor that takes the outer class as first parameter.
+                    Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
+                    Constructor<?> innerCons = innerClazz2.getConstructor();
+                    Object i2 = innerCons.newInstance();
+                    assertNotNull(i2);
+
+                    // The original StaticInner.get returns 100+10+20,
+                    // but the delegate makes it return 6+10+20
+                    assertEquals(6+10+20, callGet(i2, 10, 20));
+                    assertEquals(100+10+20, callGet_Original(i2, 10, 20));
+                }
+            };
+            cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+            cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
+            cl2.testModifiedInstance();
+        } catch (Throwable t) {
+            throw dumpGeneratedClass(t, cl2);
+        }
+    }
+
     //-------
 
     /**
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index f083e76..6dfb816 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -45,6 +45,16 @@
         }
     }
 
+    public static class StaticInnerClass {
+        public StaticInnerClass() {
+        }
+
+        // StaticInnerClass.get returns 100 + a + b
+        public int get(int a, long b) {
+            return 100 + a + (int) b;
+        }
+    }
+
     @SuppressWarnings("unused")
     private String privateMethod() {
         return "outerPrivateMethod";
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
new file mode 100644
index 0000000..a29439e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_StaticInnerClass_Delegate {
+    // The delegate override of Inner.get return 6 + a + b
+    public static int get(StaticInnerClass inner, int a, long b) {
+        return 6 + a + (int) b;
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e611ea4..6e42391 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -737,7 +737,9 @@
     public String toString() {
         StringBuffer sb = new StringBuffer();
         for (String key : mFields.keySet()) {
-            sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+            // Don't display password in toString().
+            String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key);
+            sb.append(key).append(" ").append(value).append("\n");
         }
         return sb.toString();
     }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index c26ca6e..5534cad 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -168,6 +168,22 @@
          * to wake up at fixed interval
          */
         public int maxScansToCache;
+        /**
+         * if maxPeriodInMs is non zero or different than period, then this bucket is
+         * an exponential backoff bucket and the scan period will grow exponentially
+         * as per formula: actual_period(N) = period ^ (N/(step_count+1))
+         * to a maximum period of max_period.
+         */
+        public int maxPeriodInMs;
+        /**
+         * for exponential back off bucket: multiplier: new_period=old_period*exponent
+         */
+        public int exponent;
+        /**
+         * for exponential back off bucket, number of scans performed at a given
+         * period and until the exponent is applied
+         */
+        public int stepCount;
 
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
@@ -181,6 +197,9 @@
             dest.writeInt(reportEvents);
             dest.writeInt(numBssidsPerScan);
             dest.writeInt(maxScansToCache);
+            dest.writeInt(maxPeriodInMs);
+            dest.writeInt(exponent);
+            dest.writeInt(stepCount);
 
             if (channels != null) {
                 dest.writeInt(channels.length);
@@ -206,6 +225,9 @@
                         settings.reportEvents = in.readInt();
                         settings.numBssidsPerScan = in.readInt();
                         settings.maxScansToCache = in.readInt();
+                        settings.maxPeriodInMs = in.readInt();
+                        settings.exponent = in.readInt();
+                        settings.stepCount = in.readInt();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {