Merge "hide the nanos even harder" into nyc-dev
diff --git a/Android.mk b/Android.mk
index 9183771..97dfc1d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -223,6 +223,8 @@
 	core/java/android/os/IPermissionController.aidl \
 	core/java/android/os/IProcessInfoService.aidl \
 	core/java/android/os/IPowerManager.aidl \
+	core/java/android/os/IRecoverySystem.aidl \
+	core/java/android/os/IRecoverySystemProgressListener.aidl \
 	core/java/android/os/IRemoteCallback.aidl \
 	core/java/android/os/ISchedulingPolicyService.aidl \
 	core/java/android/os/IUpdateLock.aidl \
diff --git a/api/current.txt b/api/current.txt
index abc9e9a..66c1d39 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -423,6 +423,7 @@
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
     field public static final int controlY2 = 16843775; // 0x10103ff
+    field public static final int countDown = 16844060; // 0x101051c
     field public static final int country = 16843962; // 0x10104ba
     field public static final int cropToPadding = 16843043; // 0x1010123
     field public static final int cursorVisible = 16843090; // 0x1010152
@@ -5829,9 +5830,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
-    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
-    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
@@ -9785,6 +9784,8 @@
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
     field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -13201,8 +13202,8 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
-    field public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
     field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
@@ -19692,6 +19693,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
     field public static final int TYPE_FM_TUNER = 16; // 0x10
@@ -19709,12 +19711,14 @@
     field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
   }
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
+    method public int describeContents();
     method public int getChannelCount();
     method public int getChannelIndexMask();
     method public int getChannelMask();
     method public int getEncoding();
     method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
     field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
     field public static final deprecated int CHANNEL_CONFIGURATION_INVALID = 0; // 0x0
     field public static final deprecated int CHANNEL_CONFIGURATION_MONO = 2; // 0x2
@@ -19756,6 +19760,7 @@
     field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
     field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
     field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+    field public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
     field public static final int ENCODING_AC3 = 5; // 0x5
     field public static final int ENCODING_DEFAULT = 1; // 0x1
     field public static final int ENCODING_DTS = 7; // 0x7
@@ -22752,6 +22757,7 @@
     method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long);
     method public static final android.net.Uri buildRecordedProgramUri(long);
     field public static final java.lang.String AUTHORITY = "android.media.tv";
+    field public static final java.lang.String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
   }
 
   public static abstract interface TvContract.BaseTvColumns implements android.provider.BaseColumns {
@@ -46069,7 +46075,9 @@
     method public long getBase();
     method public java.lang.String getFormat();
     method public android.widget.Chronometer.OnChronometerTickListener getOnChronometerTickListener();
+    method public boolean isCountDown();
     method public void setBase(long);
+    method public void setCountDown(boolean);
     method public void setFormat(java.lang.String);
     method public void setOnChronometerTickListener(android.widget.Chronometer.OnChronometerTickListener);
     method public void start();
diff --git a/api/removed.txt b/api/removed.txt
index 0bf6594..50a24f6 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -9,6 +9,8 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
+    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
   }
@@ -35,7 +37,7 @@
 
 package android.media {
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
     ctor public AudioFormat();
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index b36d7e8..11e5af5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -518,6 +518,7 @@
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
     field public static final int controlY2 = 16843775; // 0x10103ff
+    field public static final int countDown = 16844060; // 0x101051c
     field public static final int country = 16843962; // 0x10104ba
     field public static final int cropToPadding = 16843043; // 0x1010123
     field public static final int cursorVisible = 16843090; // 0x1010152
@@ -5966,9 +5967,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
-    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
-    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
@@ -6025,6 +6024,7 @@
     method public int getStorageEncryptionStatus();
     method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
+    method public int getUserProvisioningState();
     method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
     method public java.lang.String getWifiMacAddress();
     method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]);
@@ -6111,6 +6111,7 @@
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+    field public static final java.lang.String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
@@ -6180,6 +6181,11 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
+    field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
+    field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
+    field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
+    field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
+    field public static final int STATE_USER_UNMANAGED = 0; // 0x0
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
   }
@@ -10143,6 +10149,8 @@
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
     field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -10282,6 +10290,7 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.PermissionInfo> CREATOR;
     field public static final int FLAG_COSTS_MONEY = 1; // 0x1
     field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+    field public static final int FLAG_REMOVED = 2; // 0x2
     field public static final int PROTECTION_DANGEROUS = 1; // 0x1
     field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
     field public static final int PROTECTION_FLAG_DEVELOPMENT = 32; // 0x20
@@ -13603,8 +13612,8 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
-    field public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
     field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
@@ -21168,6 +21177,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
     field public static final int TYPE_FM_TUNER = 16; // 0x10
@@ -21197,12 +21207,14 @@
     field public static final android.os.Parcelable.Creator<android.media.AudioFocusInfo> CREATOR;
   }
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
+    method public int describeContents();
     method public int getChannelCount();
     method public int getChannelIndexMask();
     method public int getChannelMask();
     method public int getEncoding();
     method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
     field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
     field public static final deprecated int CHANNEL_CONFIGURATION_INVALID = 0; // 0x0
     field public static final deprecated int CHANNEL_CONFIGURATION_MONO = 2; // 0x2
@@ -21244,6 +21256,7 @@
     field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
     field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
     field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+    field public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
     field public static final int ENCODING_AC3 = 5; // 0x5
     field public static final int ENCODING_DEFAULT = 1; // 0x1
     field public static final int ENCODING_DTS = 7; // 0x7
@@ -24375,6 +24388,7 @@
     method public static final android.net.Uri buildRecordedProgramUri(long);
     method public static final boolean isChannelUriForPassthroughInput(android.net.Uri);
     field public static final java.lang.String AUTHORITY = "android.media.tv";
+    field public static final java.lang.String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
   }
 
   public static abstract interface TvContract.BaseTvColumns implements android.provider.BaseColumns {
@@ -26827,6 +26841,7 @@
     method public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
     field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000
     field public static final int MIN_SCAN_PERIOD_MS = 1000; // 0x3e8
+    field public static final int REASON_DUPLICATE_REQEUST = -5; // 0xfffffffb
     field public static final int REASON_INVALID_LISTENER = -2; // 0xfffffffe
     field public static final int REASON_INVALID_REQUEST = -3; // 0xfffffffd
     field public static final int REASON_NOT_AUTHORIZED = -4; // 0xfffffffc
@@ -31418,9 +31433,14 @@
   }
 
   public class RecoverySystem {
+    method public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
     method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
+    method public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
+    method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
+    method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
     method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
     method public static void rebootWipeUserData(android.content.Context) throws java.io.IOException;
+    method public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
     method public static void verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File) throws java.security.GeneralSecurityException, java.io.IOException;
   }
 
@@ -34751,6 +34771,7 @@
     field public static final java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
     field public static final java.lang.String USE_GOOGLE_MAIL = "use_google_mail";
     field public static final java.lang.String WAIT_FOR_DEBUGGER = "wait_for_debugger";
+    field public static final java.lang.String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
     field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
     field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
     field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
@@ -49165,7 +49186,9 @@
     method public long getBase();
     method public java.lang.String getFormat();
     method public android.widget.Chronometer.OnChronometerTickListener getOnChronometerTickListener();
+    method public boolean isCountDown();
     method public void setBase(long);
+    method public void setCountDown(boolean);
     method public void setFormat(java.lang.String);
     method public void setOnChronometerTickListener(android.widget.Chronometer.OnChronometerTickListener);
     method public void start();
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 27de913..7347aa3 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -6,6 +6,15 @@
 
 }
 
+package android.app.admin {
+
+  public class DevicePolicyManager {
+    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
+    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
+  }
+
+}
+
 package android.content.pm {
 
   public class PackageInfo implements android.os.Parcelable {
@@ -26,7 +35,7 @@
 
 package android.media {
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
     ctor public AudioFormat();
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 3878fbf..a7bc720 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -423,6 +423,7 @@
     field public static final int controlX2 = 16843774; // 0x10103fe
     field public static final int controlY1 = 16843773; // 0x10103fd
     field public static final int controlY2 = 16843775; // 0x10103ff
+    field public static final int countDown = 16844060; // 0x101051c
     field public static final int country = 16843962; // 0x10104ba
     field public static final int cropToPadding = 16843043; // 0x1010123
     field public static final int cursorVisible = 16843090; // 0x1010152
@@ -5831,9 +5832,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
-    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
-    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
@@ -9793,6 +9792,8 @@
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
     field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+    field public static final java.lang.String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -13209,8 +13210,8 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
-    field public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
     field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
@@ -19701,6 +19702,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
     field public static final int TYPE_FM_TUNER = 16; // 0x10
@@ -19718,12 +19720,14 @@
     field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
   }
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
+    method public int describeContents();
     method public int getChannelCount();
     method public int getChannelIndexMask();
     method public int getChannelMask();
     method public int getEncoding();
     method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
     field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
     field public static final deprecated int CHANNEL_CONFIGURATION_INVALID = 0; // 0x0
     field public static final deprecated int CHANNEL_CONFIGURATION_MONO = 2; // 0x2
@@ -19765,6 +19769,7 @@
     field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
     field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
     field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+    field public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
     field public static final int ENCODING_AC3 = 5; // 0x5
     field public static final int ENCODING_DEFAULT = 1; // 0x1
     field public static final int ENCODING_DTS = 7; // 0x7
@@ -22761,6 +22766,7 @@
     method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long);
     method public static final android.net.Uri buildRecordedProgramUri(long);
     field public static final java.lang.String AUTHORITY = "android.media.tv";
+    field public static final java.lang.String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
   }
 
   public static abstract interface TvContract.BaseTvColumns implements android.provider.BaseColumns {
@@ -46086,7 +46092,9 @@
     method public long getBase();
     method public java.lang.String getFormat();
     method public android.widget.Chronometer.OnChronometerTickListener getOnChronometerTickListener();
+    method public boolean isCountDown();
     method public void setBase(long);
+    method public void setCountDown(boolean);
     method public void setFormat(java.lang.String);
     method public void setOnChronometerTickListener(android.widget.Chronometer.OnChronometerTickListener);
     method public void start();
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 0bf6594..50a24f6 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -9,6 +9,8 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method public deprecated android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
+    method public deprecated android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
   }
@@ -35,7 +37,7 @@
 
 package android.media {
 
-  public class AudioFormat {
+  public class AudioFormat implements android.os.Parcelable {
     ctor public AudioFormat();
   }
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8717353..837ceb6 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -268,154 +268,155 @@
                 return mClassLoader;
             }
 
-            if (mIncludeCode && !mPackageName.equals("android")) {
-                // Avoid the binder call when the package is the current application package.
-                // The activity manager will perform ensure that dexopt is performed before
-                // spinning up the process.
-                if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
-                    final String isa = VMRuntime.getRuntime().vmInstructionSet();
-                    try {
-                        ActivityThread.getPackageManager().notifyPackageUse(mPackageName);
-                    } catch (RemoteException re) {
-                        // Ignored.
-                    }
-                }
-
-                final List<String> zipPaths = new ArrayList<>();
-                final List<String> apkPaths = new ArrayList<>();
-                final List<String> libPaths = new ArrayList<>();
-
-                if (mRegisterPackage) {
-                    try {
-                        ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
-                    } catch (RemoteException e) {
-                    }
-                }
-
-                zipPaths.add(mAppDir);
-                if (mSplitAppDirs != null) {
-                    Collections.addAll(zipPaths, mSplitAppDirs);
-                }
-
-                libPaths.add(mLibDir);
-
-                /*
-                 * The following is a bit of a hack to inject
-                 * instrumentation into the system: If the app
-                 * being started matches one of the instrumentation names,
-                 * then we combine both the "instrumentation" and
-                 * "instrumented" app into the path, along with the
-                 * concatenation of both apps' shared library lists.
-                 */
-
-                String instrumentationPackageName = mActivityThread.mInstrumentationPackageName;
-                String instrumentationAppDir = mActivityThread.mInstrumentationAppDir;
-                String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs;
-                String instrumentationLibDir = mActivityThread.mInstrumentationLibDir;
-
-                String instrumentedAppDir = mActivityThread.mInstrumentedAppDir;
-                String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs;
-                String instrumentedLibDir = mActivityThread.mInstrumentedLibDir;
-                String[] instrumentationLibs = null;
-
-                if (mAppDir.equals(instrumentationAppDir)
-                        || mAppDir.equals(instrumentedAppDir)) {
-                    zipPaths.clear();
-                    zipPaths.add(instrumentationAppDir);
-                    if (instrumentationSplitAppDirs != null) {
-                        Collections.addAll(zipPaths, instrumentationSplitAppDirs);
-                    }
-                    zipPaths.add(instrumentedAppDir);
-                    if (instrumentedSplitAppDirs != null) {
-                        Collections.addAll(zipPaths, instrumentedSplitAppDirs);
-                    }
-
-                    libPaths.clear();
-                    libPaths.add(instrumentationLibDir);
-                    libPaths.add(instrumentedLibDir);
-
-                    if (!instrumentedAppDir.equals(instrumentationAppDir)) {
-                        instrumentationLibs = getLibrariesFor(instrumentationPackageName);
-                    }
-                }
-
-                apkPaths.addAll(zipPaths);
-
-                if (mSharedLibraries != null) {
-                    for (String lib : mSharedLibraries) {
-                        if (!zipPaths.contains(lib)) {
-                            zipPaths.add(0, lib);
-                        }
-                    }
-                }
-
-                if (instrumentationLibs != null) {
-                    for (String lib : instrumentationLibs) {
-                        if (!zipPaths.contains(lib)) {
-                            zipPaths.add(0, lib);
-                        }
-                    }
-                }
-
-                final String zip = TextUtils.join(File.pathSeparator, zipPaths);
-
-                // Add path to libraries in apk for current abi
-                if (mApplicationInfo.primaryCpuAbi != null) {
-                    for (String apk : apkPaths) {
-                      libPaths.add(apk + "!/lib/" + mApplicationInfo.primaryCpuAbi);
-                    }
-                }
-
-                String libraryPermittedPath = mDataDir;
-                boolean isBundledApp = false;
-
-                if (mApplicationInfo.isSystemApp()) {
-                    isBundledApp = true;
-                    // Add path to system libraries to libPaths;
-                    // Access to system libs should be limited
-                    // to bundled applications; this is why updated
-                    // system apps are not included.
-                    libPaths.add(System.getProperty("java.library.path"));
-
-                    // This is necessary to grant bundled apps access to
-                    // libraries located in subdirectories of /system/lib
-                    libraryPermittedPath += File.pathSeparator +
-                                            System.getProperty("java.library.path");
-                }
-                // DO NOT SHIP: this is a workaround for apps loading native libraries
-                // provided by 3rd party apps using absolute path instead of corresponding
-                // classloader; see http://b/26954419 for example.
-                if (mApplicationInfo.targetSdkVersion <= 23) {
-                    libraryPermittedPath += File.pathSeparator + "/data/app";
-                }
-                // -----------------------------------------------------------------------------
-
-                final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
-
-                /*
-                 * With all the combination done (if necessary, actually
-                 * create the class loader.
-                 */
-
-                if (ActivityThread.localLOGV)
-                    Slog.v(ActivityThread.TAG, "Class path: " + zip +
-                            ", JNI path: " + librarySearchPath);
-
-                // Temporarily disable logging of disk reads on the Looper thread
-                // as this is early and necessary.
-                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-
-                mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, isBundledApp,
-                        librarySearchPath, libraryPermittedPath, mBaseClassLoader);
-
-                StrictMode.setThreadPolicy(oldPolicy);
-            } else {
+            if (mPackageName.equals("android")) {
                 if (mBaseClassLoader == null) {
                     mClassLoader = ClassLoader.getSystemClassLoader();
                 } else {
                     mClassLoader = mBaseClassLoader;
                 }
+                return mClassLoader;
             }
+
+            // Avoid the binder call when the package is the current application package.
+            // The activity manager will perform ensure that dexopt is performed before
+            // spinning up the process.
+            if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
+                final String isa = VMRuntime.getRuntime().vmInstructionSet();
+                try {
+                    ActivityThread.getPackageManager().notifyPackageUse(mPackageName);
+                } catch (RemoteException re) {
+                    // Ignored.
+                }
+            }
+
+            final List<String> zipPaths = new ArrayList<>();
+            final List<String> apkPaths = new ArrayList<>();
+            final List<String> libPaths = new ArrayList<>();
+
+            if (mRegisterPackage) {
+                try {
+                    ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
+                } catch (RemoteException e) {
+                }
+            }
+
+            zipPaths.add(mAppDir);
+            if (mSplitAppDirs != null) {
+                Collections.addAll(zipPaths, mSplitAppDirs);
+            }
+
+            libPaths.add(mLibDir);
+
+            /*
+             * The following is a bit of a hack to inject
+             * instrumentation into the system: If the app
+             * being started matches one of the instrumentation names,
+             * then we combine both the "instrumentation" and
+             * "instrumented" app into the path, along with the
+             * concatenation of both apps' shared library lists.
+             */
+
+            String instrumentationPackageName = mActivityThread.mInstrumentationPackageName;
+            String instrumentationAppDir = mActivityThread.mInstrumentationAppDir;
+            String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs;
+            String instrumentationLibDir = mActivityThread.mInstrumentationLibDir;
+
+            String instrumentedAppDir = mActivityThread.mInstrumentedAppDir;
+            String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs;
+            String instrumentedLibDir = mActivityThread.mInstrumentedLibDir;
+            String[] instrumentationLibs = null;
+
+            if (mAppDir.equals(instrumentationAppDir)
+                    || mAppDir.equals(instrumentedAppDir)) {
+                zipPaths.clear();
+                zipPaths.add(instrumentationAppDir);
+                if (instrumentationSplitAppDirs != null) {
+                    Collections.addAll(zipPaths, instrumentationSplitAppDirs);
+                }
+                zipPaths.add(instrumentedAppDir);
+                if (instrumentedSplitAppDirs != null) {
+                    Collections.addAll(zipPaths, instrumentedSplitAppDirs);
+                }
+
+                libPaths.clear();
+                libPaths.add(instrumentationLibDir);
+                libPaths.add(instrumentedLibDir);
+
+                if (!instrumentedAppDir.equals(instrumentationAppDir)) {
+                    instrumentationLibs = getLibrariesFor(instrumentationPackageName);
+                }
+            }
+
+            apkPaths.addAll(zipPaths);
+
+            if (mSharedLibraries != null) {
+                for (String lib : mSharedLibraries) {
+                    if (!zipPaths.contains(lib)) {
+                        zipPaths.add(0, lib);
+                    }
+                }
+            }
+
+            if (instrumentationLibs != null) {
+                for (String lib : instrumentationLibs) {
+                    if (!zipPaths.contains(lib)) {
+                        zipPaths.add(0, lib);
+                    }
+                }
+            }
+
+            final String zip = mIncludeCode ? TextUtils.join(File.pathSeparator, zipPaths) : "";
+
+            // Add path to libraries in apk for current abi
+            if (mApplicationInfo.primaryCpuAbi != null) {
+                for (String apk : apkPaths) {
+                  libPaths.add(apk + "!/lib/" + mApplicationInfo.primaryCpuAbi);
+                }
+            }
+
+            String libraryPermittedPath = mDataDir;
+            boolean isBundledApp = false;
+
+            if (mApplicationInfo.isSystemApp()) {
+                isBundledApp = true;
+                // Add path to system libraries to libPaths;
+                // Access to system libs should be limited
+                // to bundled applications; this is why updated
+                // system apps are not included.
+                libPaths.add(System.getProperty("java.library.path"));
+
+                // This is necessary to grant bundled apps access to
+                // libraries located in subdirectories of /system/lib
+                libraryPermittedPath += File.pathSeparator +
+                                        System.getProperty("java.library.path");
+            }
+            // DO NOT SHIP: this is a workaround for apps loading native libraries
+            // provided by 3rd party apps using absolute path instead of corresponding
+            // classloader; see http://b/26954419 for example.
+            if (mApplicationInfo.targetSdkVersion <= 23) {
+                libraryPermittedPath += File.pathSeparator + "/data/app";
+            }
+            // -----------------------------------------------------------------------------
+
+            final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
+
+            /*
+             * With all the combination done (if necessary, actually
+             * create the class loader.
+             */
+
+            if (ActivityThread.localLOGV)
+                Slog.v(ActivityThread.TAG, "Class path: " + zip +
+                        ", JNI path: " + librarySearchPath);
+
+            // Temporarily disable logging of disk reads on the Looper thread
+            // as this is early and necessary.
+            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+
+            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, isBundledApp,
+                    librarySearchPath, libraryPermittedPath, mBaseClassLoader);
+
+            StrictMode.setThreadPolicy(oldPolicy);
             return mClassLoader;
         }
     }
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index aa14cb5..3c7f48b 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -166,7 +166,8 @@
         String path = classLoader.findLibrary(libname);
 
         if (path == null) {
-            throw new IllegalArgumentException("Unable to find native library: " + libname);
+            throw new IllegalArgumentException("Unable to find native library " + libname +
+                                               " using classloader: " + classLoader.toString());
         }
         
         byte[] nativeSavedState = savedInstanceState != null
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 52fba3b..307c3eb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -91,9 +91,11 @@
 import android.os.IBinder;
 import android.os.IHardwarePropertiesManager;
 import android.os.IPowerManager;
+import android.os.IRecoverySystem;
 import android.os.IUserManager;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RecoverySystem;
 import android.os.ServiceManager;
 import android.os.SystemVibrator;
 import android.os.UserHandle;
@@ -380,6 +382,18 @@
                         service, ctx.mMainThread.getHandler());
             }});
 
+        registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
+                new CachedServiceFetcher<RecoverySystem>() {
+            @Override
+            public RecoverySystem createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(Context.RECOVERY_SERVICE);
+                IRecoverySystem service = IRecoverySystem.Stub.asInterface(b);
+                if (service == null) {
+                    Log.wtf(TAG, "Failed to get recovery service.");
+                }
+                return new RecoverySystem(service);
+            }});
+
         registerService(Context.SEARCH_SERVICE, SearchManager.class,
                 new CachedServiceFetcher<SearchManager>() {
             @Override
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d5ca0e9..a34e8551e 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -383,8 +383,8 @@
      *
      * <p> If the admin is activated by a device owner, then the intent
      * may contain private extras that are relevant to user setup.
-     * {@see DevicePolicyManager#createAndInitializeUser(ComponentName, String, String,
-     *      ComponentName, Intent)}
+     * {@see DevicePolicyManager#createAndManageUser(ComponentName, String, ComponentName,
+     *      PersistableBundle, int)}
      *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 820e0e8..ba8f1f4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -341,6 +341,7 @@
      *
      * @hide
      */
+    @SystemApi
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_PROVISION_FINALIZATION
             = "android.app.action.PROVISION_FINALIZATION";
@@ -932,30 +933,35 @@
      * No management for current user in-effect. This is the default.
      * @hide
      */
+    @SystemApi
     public static final int STATE_USER_UNMANAGED = 0;
 
     /**
      * Management partially setup, user setup needs to be completed.
      * @hide
      */
+    @SystemApi
     public static final int STATE_USER_SETUP_INCOMPLETE = 1;
 
     /**
      * Management partially setup, user setup completed.
      * @hide
      */
+    @SystemApi
     public static final int STATE_USER_SETUP_COMPLETE = 2;
 
     /**
      * Management setup and active on current user.
      * @hide
      */
+    @SystemApi
     public static final int STATE_USER_SETUP_FINALIZED = 3;
 
     /**
      * Management partially setup on a managed profile.
      * @hide
      */
+    @SystemApi
     public static final int STATE_USER_PROFILE_COMPLETE = 4;
 
     /**
@@ -4539,14 +4545,10 @@
      *         user could not be created.
      *
      * @deprecated From {@link android.os.Build.VERSION_CODES#M}
+     * @removed From {@link android.os.Build.VERSION_CODES#N}
      */
     @Deprecated
     public UserHandle createUser(@NonNull ComponentName admin, String name) {
-        try {
-            return mService.createUser(admin, name);
-        } catch (RemoteException re) {
-            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
-        }
         return null;
     }
 
@@ -4576,16 +4578,11 @@
      *         user could not be created.
      *
      * @deprecated From {@link android.os.Build.VERSION_CODES#M}
+     * @removed From {@link android.os.Build.VERSION_CODES#N}
      */
     @Deprecated
     public UserHandle createAndInitializeUser(@NonNull ComponentName admin, String name,
             String ownerName, @NonNull ComponentName profileOwnerComponent, Bundle adminExtras) {
-        try {
-            return mService.createAndInitializeUser(admin, name, ownerName, profileOwnerComponent,
-                    adminExtras);
-        } catch (RemoteException re) {
-            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
-        }
         return null;
     }
 
@@ -5900,6 +5897,7 @@
      *         return {@link #STATE_USER_UNMANAGED}
      * @hide
      */
+    @SystemApi
     @UserProvisioningState
     public int getUserProvisioningState() {
         if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e9af872..e78c0ac 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -185,8 +185,6 @@
     boolean setApplicationHidden(in ComponentName admin, in String packageName, boolean hidden);
     boolean isApplicationHidden(in ComponentName admin, in String packageName);
 
-    UserHandle createUser(in ComponentName who, in String name);
-    UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras);
     UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
     boolean switchUser(in ComponentName who, in UserHandle userHandle);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0cdbef0..b935b25 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2863,6 +2863,16 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.os.RecoverySystem} for accessing the recovery system
+     * service.
+     *
+     * @see #getSystemService
+     * @hide
+     */
+    public static final String RECOVERY_SERVICE = "recovery";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.view.WindowManager} for accessing the system's window
      * manager.
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 188e1d7..b7f968c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1573,6 +1573,48 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: If this feature is supported, the Vulkan native API will enumerate
+     * at least one {@code VkPhysicalDevice}, and the feature version will indicate what
+     * level of optional hardware features limits it supports.
+     * <p>
+     * Level 0 includes the base Vulkan requirements as well as:
+     * <ul><li>{@code VkPhysicalDeviceFeatures::textureCompressionETC2}</li></ul>
+     * <p>
+     * Level 1 additionally includes:
+     * <ul>
+     * <li>{@code VkPhysicalDeviceFeatures::fullDrawIndexUint32}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::imageCubeArray}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::independentBlend}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::geometryShader}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::tessellationShader}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::sampleRateShading}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::textureCompressionASTC_LDR}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::fragmentStoresAndAtomics}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::shaderImageGatherExtended}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::shaderUniformBufferArrayDynamicIndexing}</li>
+     * <li>{@code VkPhysicalDeviceFeatures::shaderSampledImageArrayDynamicIndexing}</li>
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The version of this feature indicates the highest
+     * {@code VkPhysicalDeviceProperties::apiVersion} supported by the physical devices that support
+     * the hardware level indicated by {@link #FEATURE_VULKAN_HARDWARE_LEVEL}. The feature version
+     * uses the same encoding as Vulkan version numbers:
+     * <ul>
+     * <li>Major version number in bits 31-22</li>
+     * <li>Minor version number in bits 21-12</li>
+     * <li>Patch version number in bits 11-0</li>
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device includes an accelerometer.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -4059,7 +4101,7 @@
      *
      * @return A list of {@link InstrumentationInfo} objects containing one
      *         entry for each matching instrumentation. If there are no
-     *         instrumentation available, returns and empty list. 
+     *         instrumentation available, returns an empty list.
      *
      * @see #GET_META_DATA
      */
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 9da2ba9..984a960 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -145,11 +146,12 @@
     public static final int FLAG_COSTS_MONEY = 1<<0;
 
     /**
-     * Flag for {@link #flags}, corresponding to <code>hidden</code>
+     * Flag for {@link #flags}, corresponding to <code>removed</code>
      * value of {@link android.R.attr#permissionFlags}.
      * @hide
      */
-    public static final int FLAG_HIDDEN = 1<<1;
+    @SystemApi
+    public static final int FLAG_REMOVED = 1<<1;
 
     /**
      * Flag for {@link #flags}, indicating that this permission has been
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 02d4e59..3dbe437 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -19,6 +19,7 @@
 import android.app.ActivityThread;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.app.job.JobInfo;
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
@@ -177,19 +178,27 @@
     private static final int NO_ERROR = 0;
 
     /**
+     * @deprecated This broadcast is no longer delivered by the system; use
+     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+     * instead.
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
      * the picture has been added to the media store.
      * {@link android.content.Intent#getData} is URI of the picture.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @Deprecated
     public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
 
     /**
+     * @deprecated This broadcast is no longer delivered by the system; use
+     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+     * instead.
      * Broadcast Action:  A new video is recorded by the camera, and the entry
      * of the video has been added to the media store.
      * {@link android.content.Intent#getData} is URI of the video.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @Deprecated
     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
 
     /**
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
new file mode 100644
index 0000000..12830a4
--- /dev/null
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -0,0 +1,28 @@
+/* //device/java/android/android/os/IRecoverySystem.aidl
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.IRecoverySystemProgressListener;
+
+/** @hide */
+
+interface IRecoverySystem {
+    boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
+    boolean setupBcb(in String command);
+    boolean clearBcb();
+}
diff --git a/core/java/android/os/IRecoverySystemProgressListener.aidl b/core/java/android/os/IRecoverySystemProgressListener.aidl
new file mode 100644
index 0000000..d6f712e
--- /dev/null
+++ b/core/java/android/os/IRecoverySystemProgressListener.aidl
@@ -0,0 +1,24 @@
+/* //device/java/android/android/os/IRecoverySystemProgressListener.aidl
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+/** @hide */
+
+oneway interface IRecoverySystemProgressListener {
+    void onProgress(int progress);
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 314b7d5..dcc28d6 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -385,9 +385,9 @@
     public static final int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
 
     /**
-     * The value to pass as the 'reason' argument to reboot() to
-     * reboot into recovery mode (for applying system updates, doing
-     * factory resets, etc.).
+     * The value to pass as the 'reason' argument to reboot() to reboot into
+     * recovery mode for tasks other than applying system updates, such as
+     * doing factory resets.
      * <p>
      * Requires the {@link android.Manifest.permission#RECOVERY}
      * permission (in addition to
@@ -398,6 +398,18 @@
     public static final String REBOOT_RECOVERY = "recovery";
 
     /**
+     * The value to pass as the 'reason' argument to reboot() to reboot into
+     * recovery mode for applying system updates.
+     * <p>
+     * Requires the {@link android.Manifest.permission#RECOVERY}
+     * permission (in addition to
+     * {@link android.Manifest.permission#REBOOT}).
+     * </p>
+     * @hide
+     */
+    public static final String REBOOT_RECOVERY_UPDATE = "recovery-update";
+
+    /**
      * The value to pass as the 'reason' argument to reboot() when device owner requests a reboot on
      * the device.
      * @hide
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 154c9bb..ddcd635 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.SystemApi;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -66,15 +67,34 @@
     private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
 
     /** Used to communicate with recovery.  See bootable/recovery/recovery.cpp. */
-    private static File RECOVERY_DIR = new File("/cache/recovery");
-    private static File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
-    private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
-    private static File UNCRYPT_FILE = new File(RECOVERY_DIR, "uncrypt_file");
-    private static File LOG_FILE = new File(RECOVERY_DIR, "log");
-    private static String LAST_PREFIX = "last_";
+    private static final File RECOVERY_DIR = new File("/cache/recovery");
+    private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
+    private static final String LAST_PREFIX = "last_";
+
+    /**
+     * The recovery image uses this file to identify the location (i.e. blocks)
+     * of an OTA package on the /data partition. The block map file is
+     * generated by uncrypt.
+     *
+     * @hide
+     */
+    public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
+
+    /**
+     * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
+     * read by uncrypt.
+     *
+     * @hide
+     */
+    public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
 
     // Length limits for reading files.
-    private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
+    private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
+
+    // Prevent concurrent execution of requests.
+    private static final Object sRequestLock = new Object();
+
+    private final IRecoverySystem mService;
 
     /**
      * Interface definition for a callback to be invoked regularly as
@@ -287,6 +307,89 @@
     }
 
     /**
+     * Process a given package with uncrypt. No-op if the package is not on the
+     * /data partition.
+     *
+     * @param Context      the Context to use
+     * @param packageFile  the package to be processed
+     * @param listener     an object to receive periodic progress updates as
+     *                     processing proceeds.  May be null.
+     * @param handler      the Handler upon which the callbacks will be
+     *                     executed.
+     *
+     * @throws IOException if there were any errors processing the package file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void processPackage(Context context,
+                                      File packageFile,
+                                      final ProgressListener listener,
+                                      final Handler handler)
+            throws IOException {
+        String filename = packageFile.getCanonicalPath();
+        if (!filename.startsWith("/data/")) {
+            return;
+        }
+
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        IRecoverySystemProgressListener progressListener = null;
+        if (listener != null) {
+            final Handler progressHandler;
+            if (handler != null) {
+                progressHandler = handler;
+            } else {
+                progressHandler = new Handler(context.getMainLooper());
+            }
+            progressListener = new IRecoverySystemProgressListener.Stub() {
+                int lastProgress = 0;
+                long lastPublishTime = System.currentTimeMillis();
+
+                @Override
+                public void onProgress(final int progress) {
+                    final long now = System.currentTimeMillis();
+                    progressHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (progress > lastProgress &&
+                                    now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
+                                lastProgress = progress;
+                                lastPublishTime = now;
+                                listener.onProgress(progress);
+                            }
+                        }
+                    });
+                }
+            };
+        }
+
+        if (!rs.uncrypt(filename, progressListener)) {
+            throw new IOException("process package failed");
+        }
+    }
+
+    /**
+     * Process a given package with uncrypt. No-op if the package is not on the
+     * /data partition.
+     *
+     * @param Context      the Context to use
+     * @param packageFile  the package to be processed
+     * @param listener     an object to receive periodic progress updates as
+     *                     processing proceeds.  May be null.
+     *
+     * @throws IOException if there were any errors processing the package file.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void processPackage(Context context,
+                                      File packageFile,
+                                      final ProgressListener listener)
+            throws IOException {
+        processPackage(context, packageFile, listener, null);
+    }
+
+    /**
      * Reboots the device in order to install the given update
      * package.
      * Requires the {@link android.Manifest.permission#REBOOT} permission.
@@ -301,30 +404,127 @@
      * fails, or if the reboot itself fails.
      */
     public static void installPackage(Context context, File packageFile)
-        throws IOException {
+            throws IOException {
+        installPackage(context, packageFile, false);
+    }
+
+    /**
+     * If the package hasn't been processed (i.e. uncrypt'd), set up
+     * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
+     * reboot.
+     *
+     * @param context      the Context to use
+     * @param packageFile  the update package to install.  Must be on a
+     * partition mountable by recovery.
+     * @param processed    if the package has been processed (uncrypt'd).
+     *
+     * @throws IOException if writing the recovery command file fails, or if
+     * the reboot itself fails.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void installPackage(Context context, File packageFile, boolean processed)
+            throws IOException {
+        synchronized (sRequestLock) {
+            LOG_FILE.delete();
+            // Must delete the file in case it was created by system server.
+            UNCRYPT_PACKAGE_FILE.delete();
+
+            String filename = packageFile.getCanonicalPath();
+            Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
+
+            if (!processed && filename.startsWith("/data/")) {
+                FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
+                try {
+                    uncryptFile.write(filename + "\n");
+                } finally {
+                    uncryptFile.close();
+                }
+                // UNCRYPT_PACKAGE_FILE needs to be readable and writable by system server.
+                if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
+                        || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
+                    Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
+                }
+
+                BLOCK_MAP_FILE.delete();
+            }
+
+            // If the package is on the /data partition, use the block map file as
+            // the package name instead.
+            if (filename.startsWith("/data/")) {
+                filename = "@/cache/recovery/block.map";
+            }
+
+            final String filenameArg = "--update_package=" + filename + "\n";
+            final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+            final String command = filenameArg + localeArg;
+
+            RecoverySystem rs = (RecoverySystem) context.getSystemService(
+                    Context.RECOVERY_SERVICE);
+            if (!rs.setupBcb(command)) {
+                throw new IOException("Setup BCB failed");
+            }
+
+            // Having set up the BCB (bootloader control block), go ahead and reboot
+            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
+
+            throw new IOException("Reboot failed (no permissions?)");
+        }
+    }
+
+    /**
+     * Schedule to install the given package on next boot. The caller needs to
+     * ensure that the package must have been processed (uncrypt'd) if needed.
+     * It sets up the command in BCB (bootloader control block), which will
+     * be read by the bootloader and the recovery image.
+     *
+     * @param Context      the Context to use.
+     * @param packageFile  the package to be installed.
+     *
+     * @throws IOException if there were any errors setting up the BCB.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void scheduleUpdateOnBoot(Context context, File packageFile)
+            throws IOException {
         String filename = packageFile.getCanonicalPath();
 
-        FileWriter uncryptFile = new FileWriter(UNCRYPT_FILE);
-        try {
-            uncryptFile.write(filename + "\n");
-        } finally {
-            uncryptFile.close();
-        }
-        // UNCRYPT_FILE needs to be readable by system server on bootup.
-        if (!UNCRYPT_FILE.setReadable(true, false)) {
-            Log.e(TAG, "Error setting readable for " + UNCRYPT_FILE.getCanonicalPath());
-        }
-        Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
-
-        // If the package is on the /data partition, write the block map file
-        // into COMMAND_FILE instead.
+        // If the package is on the /data partition, use the block map file as
+        // the package name instead.
         if (filename.startsWith("/data/")) {
             filename = "@/cache/recovery/block.map";
         }
 
-        final String filenameArg = "--update_package=" + filename;
-        final String localeArg = "--locale=" + Locale.getDefault().toString();
-        bootCommand(context, filenameArg, localeArg);
+        final String filenameArg = "--update_package=" + filename + "\n";
+        final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+        final String command = filenameArg + localeArg;
+
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        if (!rs.setupBcb(command)) {
+            throw new IOException("schedule update on boot failed");
+        }
+    }
+
+    /**
+     * Cancel any scheduled update by clearing up the BCB (bootloader control
+     * block).
+     *
+     * @param Context      the Context to use.
+     *
+     * @throws IOException if there were any errors clearing up the BCB.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void cancelScheduledUpdate(Context context)
+            throws IOException {
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        if (!rs.clearBcb()) {
+            throw new IOException("cancel scheduled update failed");
+        }
     }
 
     /**
@@ -434,27 +634,28 @@
      * @throws IOException if something goes wrong.
      */
     private static void bootCommand(Context context, String... args) throws IOException {
-        RECOVERY_DIR.mkdirs();  // In case we need it
-        COMMAND_FILE.delete();  // In case it's not writable
-        LOG_FILE.delete();
+        synchronized (sRequestLock) {
+            LOG_FILE.delete();
 
-        FileWriter command = new FileWriter(COMMAND_FILE);
-        try {
+            StringBuilder command = new StringBuilder();
             for (String arg : args) {
                 if (!TextUtils.isEmpty(arg)) {
-                    command.write(arg);
-                    command.write("\n");
+                    command.append(arg);
+                    command.append("\n");
                 }
             }
-        } finally {
-            command.close();
+
+            // Write the command into BCB (bootloader control block).
+            RecoverySystem rs = (RecoverySystem) context.getSystemService(
+                    Context.RECOVERY_SERVICE);
+            rs.setupBcb(command.toString());
+
+            // Having set up the BCB, go ahead and reboot.
+            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            pm.reboot(PowerManager.REBOOT_RECOVERY);
+
+            throw new IOException("Reboot failed (no permissions?)");
         }
-
-        // Having written the command file, go ahead and reboot
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        pm.reboot(PowerManager.REBOOT_RECOVERY);
-
-        throw new IOException("Reboot failed (no permissions?)");
     }
 
     /**
@@ -476,10 +677,10 @@
 
         // Only remove the OTA package if it's partially processed (uncrypt'd).
         boolean reservePackage = BLOCK_MAP_FILE.exists();
-        if (!reservePackage && UNCRYPT_FILE.exists()) {
+        if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
             String filename = null;
             try {
-                filename = FileUtils.readTextFile(UNCRYPT_FILE, 0, null);
+                filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
             } catch (IOException e) {
                 Log.e(TAG, "Error reading uncrypt file", e);
             }
@@ -487,7 +688,7 @@
             // Remove the OTA package on /data that has been (possibly
             // partially) processed. (Bug: 24973532)
             if (filename != null && filename.startsWith("/data")) {
-                if (UNCRYPT_FILE.delete()) {
+                if (UNCRYPT_PACKAGE_FILE.delete()) {
                     Log.i(TAG, "Deleted: " + filename);
                 } else {
                     Log.e(TAG, "Can't delete: " + filename);
@@ -499,13 +700,13 @@
         // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
         // will be created at the end of a successful uncrypt. If seeing this
         // file, we keep the block map file and the file that contains the
-        // package name (UNCRYPT_FILE). This is to reduce the work for GmsCore
-        // to avoid re-downloading everything again.
+        // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
+        // GmsCore to avoid re-downloading everything again.
         String[] names = RECOVERY_DIR.list();
         for (int i = 0; names != null && i < names.length; i++) {
             if (names[i].startsWith(LAST_PREFIX)) continue;
             if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
-            if (reservePackage && names[i].equals(UNCRYPT_FILE.getName())) continue;
+            if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
 
             recursiveDelete(new File(RECOVERY_DIR, names[i]));
         }
@@ -533,6 +734,39 @@
     }
 
     /**
+     * Talks to RecoverySystemService via Binder to trigger uncrypt.
+     */
+    private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
+        try {
+            return mService.uncrypt(packageFile, listener);
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to set up the BCB.
+     */
+    private boolean setupBcb(String command) {
+        try {
+            return mService.setupBcb(command);
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to clear up the BCB.
+     */
+    private boolean clearBcb() {
+        try {
+            return mService.clearBcb();
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
      * Internally, recovery treats each line of the command file as a separate
      * argv, so we only need to protect against newlines and nulls.
      */
@@ -546,5 +780,14 @@
     /**
      * @removed Was previously made visible by accident.
      */
-    public RecoverySystem() { }
+    public RecoverySystem() {
+        mService = null;
+    }
+
+    /**
+     * @hide
+     */
+    public RecoverySystem(IRecoverySystem service) {
+        mService = service;
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 69d564f..1a093d8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -379,8 +379,7 @@
      * Specifies that the user is not allowed to make outgoing
      * phone calls. Emergency calls are still permitted.
      * The default value is <code>false</code>.
-     * <p>This restriction has no effect on managed profiles since call intents are normally
-     * forwarded to the primary user.
+     * <p>This restriction has no effect on managed profiles.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index ed7c7c5..b921351 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -239,20 +239,20 @@
     public static class SystemContract {
         /**
          * A protected broadcast intent action for letting components with
-         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppressal
-         * status as returned by {@link #getBlockSuppressalStatus(Context)} has been updated.
+         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
+         * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
          */
-        public static final String ACTION_BLOCK_SUPPRESSAL_STATE_CHANGED =
-                "android.provider.action.BLOCK_SUPPRESSAL_STATE_CHANGED";
+        public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
+                "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
 
         public static final String METHOD_NOTIFY_EMERGENCY_CONTACT = "notify_emergency_contact";
 
-        public static final String METHOD_END_BLOCK_SUPPRESSAL = "end_block_suppressal";
+        public static final String METHOD_END_BLOCK_SUPPRESSION = "end_block_suppression";
 
         public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = "should_system_block_number";
 
-        public static final String METHOD_GET_BLOCK_SUPPRESSAL_STATUS =
-                "get_block_suppresal_status";
+        public static final String METHOD_GET_BLOCK_SUPPRESSION_STATUS =
+                "get_block_suppression_status";
 
         public static final String RES_IS_BLOCKING_SUPPRESSED = "blocking_suppressed";
 
@@ -264,7 +264,7 @@
          * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
          * of the contents of the provider for a duration defined by
          * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
-         * the provider unless {@link #endBlockSuppressal(Context)} is called.
+         * the provider unless {@link #endBlockSuppression(Context)} is called.
          */
         public static void notifyEmergencyContact(Context context) {
             context.getContentResolver().call(
@@ -275,9 +275,9 @@
          * Notifies the provider to disable suppressing blocking. If emergency services were not
          * contacted recently at all, calling this method is a no-op.
          */
-        public static void endBlockSuppressal(Context context) {
+        public static void endBlockSuppression(Context context) {
             context.getContentResolver().call(
-                    AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSAL, null, null);
+                    AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSION, null, null);
         }
 
         /**
@@ -292,26 +292,26 @@
             return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
         }
 
-        public static BlockSuppressalStatus getBlockSuppressalStatus(Context context) {
+        public static BlockSuppressionStatus getBlockSuppressionStatus(Context context) {
             final Bundle res = context.getContentResolver().call(
-                    AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSAL_STATUS, null, null);
-            return new BlockSuppressalStatus(res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false),
+                    AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
+            return new BlockSuppressionStatus(res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false),
                     res.getLong(RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
         }
 
         /**
          * Represents the current status of {@link #shouldSystemBlockNumber(Context, String)}. If
          * emergency services have been contacted recently, {@link #isSuppressed} is {@code true},
-         *  and blocking is disabled until the timestamp {@link #untilTimestampMillis}.
+         * and blocking is disabled until the timestamp {@link #untilTimestampMillis}.
          */
-        public static class BlockSuppressalStatus {
+        public static class BlockSuppressionStatus {
             public final boolean isSuppressed;
             /**
              * Timestamp in milliseconds from epoch.
              */
             public final long untilTimestampMillis;
 
-            public BlockSuppressalStatus(boolean isSuppressed, long untilTimestampMillis) {
+            public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
                 this.isSuppressed = isSuppressed;
                 this.untilTimestampMillis = untilTimestampMillis;
             }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 770cde7..0074788 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7043,6 +7043,7 @@
          * Developer setting to enable WebView multiprocess rendering.
          * @hide
          */
+        @SystemApi
         public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
 
        /**
diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java
index 8803c4b..22fbee2 100644
--- a/core/java/android/security/net/config/ResourceCertificateSource.java
+++ b/core/java/android/security/net/config/ResourceCertificateSource.java
@@ -44,7 +44,7 @@
 
     public ResourceCertificateSource(int resourceId, Context context) {
         mResourceId = resourceId;
-        mContext = context.getApplicationContext();
+        mContext = context;
     }
 
     private void ensureInitialized() {
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 51e1f4b..e0c6770 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1780,6 +1780,7 @@
             case KeyEvent.KEYCODE_DPAD_CENTER:
             case KeyEvent.KEYCODE_ENTER:
             case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                 return true;
             default:
                 return false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0517788..673c609 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3797,7 +3797,8 @@
                 return true;
             } else if ((!mAttachInfo.mHasWindowFocus
                     && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
-                    || mIsAmbientMode || (mPausedForTransition && !isBack(q.mEvent))) {
+                    || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
+                    || (mPausedForTransition && !isBack(q.mEvent))) {
                 // This is a focus event and the window doesn't currently have input focus or
                 // has stopped. This could be an event that came back from the previous stage
                 // but the window has lost focus or stopped in the meantime.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 18dfdfd..6e02516 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -30,7 +30,7 @@
 import android.util.Log;
 
 import java.util.List;
-
+import java.util.Objects;
 
 /**
  * The interface that apps use to talk to the window manager.
@@ -1950,7 +1950,7 @@
                 // already have one.
                 packageName = o.packageName;
             }
-            if (o.mTitle != null) {
+            if (!Objects.equals(mTitle, o.mTitle) && o.mTitle != null) {
                 // NOTE: mTitle only copied if the originator set one.
                 mTitle = o.mTitle;
                 changes |= TITLE_CHANGED;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index a8b7a7b..e36077b 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1362,4 +1362,11 @@
      */
     public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
             Rect outInsets);
+
+    /**
+     * @return True if a specified {@param dockSide} is allowed on the current device, or false
+     *         otherwise. It is guaranteed that at least one dock side for a particular orientation
+     *         is allowed, so for example, if DOCKED_RIGHT is not allowed, DOCKED_LEFT is allowed.
+     */
+    public boolean isDockSideAllowed(int dockSide);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2de9897..4c015ba 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1245,15 +1245,9 @@
                 if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
                         + ic + " tba=" + tba + " controlFlags=#"
                         + Integer.toHexString(controlFlags));
-                InputBindResult res;
-                if (windowGainingFocus != null) {
-                    res = mService.windowGainedFocus(startInputReason, mClient, windowGainingFocus,
-                            controlFlags, softInputMode, windowFlags,
-                            tba, servedContext);
-                } else {
-                    res = mService.startInput(startInputReason, mClient,
-                            servedContext, tba, controlFlags);
-                }
+                final InputBindResult res = mService.startInputOrWindowGainedFocus(
+                        startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
+                        windowFlags, tba, servedContext);
                 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                 if (res != null) {
                     if (res.id != null) {
@@ -1471,13 +1465,13 @@
                 return;
             }
         }
-        
+
         // For some reason we didn't do a startInput + windowFocusGain, so
         // we'll just do a window focus gain and call it a day.
         synchronized (mH) {
             try {
                 if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
-                mService.windowGainedFocus(
+                mService.startInputOrWindowGainedFocus(
                         InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
                         rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
                         null);
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 9a68593..34f3a47 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -694,7 +694,7 @@
                 final int halfH = h >= 0 ? h / 2 : 1;
                 mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
 
-                final int spacing = (getWidth() - mPaddingLeft - mPaddingRight) / count;
+                final float spacing = (getWidth() - mPaddingLeft - mPaddingRight) / (float) count;
                 final int saveCount = canvas.save();
                 canvas.translate(mPaddingLeft, getHeight() / 2);
                 for (int i = 0; i <= count; i++) {
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 4d707e3..3421790 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -19,15 +19,14 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.os.Handler;
-import android.os.Message;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.RemoteViews.RemoteView;
 
+import com.android.internal.R;
+
 import java.util.Formatter;
 import java.util.IllegalFormatException;
 import java.util.Locale;
@@ -37,11 +36,17 @@
  * <p>
  * You can give it a start time in the {@link SystemClock#elapsedRealtime} timebase,
  * and it counts up from that, or if you don't give it a base time, it will use the
- * time at which you call {@link #start}.  By default it will display the current
+ * time at which you call {@link #start}.
+ *
+ * <p>The timer can also count downward towards the base time by
+ * setting {@link #setCountDown(boolean)} to true.
+ *
+ *  <p>By default it will display the current
  * timer value in the form "MM:SS" or "H:MM:SS", or you can use {@link #setFormat}
  * to format the timer value into an arbitrary string.
  *
  * @attr ref android.R.styleable#Chronometer_format
+ * @attr ref android.R.styleable#Chronometer_countDown
  */
 @RemoteView
 public class Chronometer extends TextView {
@@ -72,6 +77,7 @@
     private StringBuilder mFormatBuilder;
     private OnChronometerTickListener mOnChronometerTickListener;
     private StringBuilder mRecycle = new StringBuilder(8);
+    private boolean mCountDown;
     
     /**
      * Initialize this Chronometer object.
@@ -102,7 +108,8 @@
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.Chronometer, defStyleAttr, defStyleRes);
-        setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format));
+        setFormat(a.getString(R.styleable.Chronometer_format));
+        setCountDown(a.getBoolean(R.styleable.Chronometer_countDown, false));
         a.recycle();
 
         init();
@@ -114,6 +121,27 @@
     }
 
     /**
+     * Set this view to count down to the base instead of counting up from it.
+     *
+     * @param countDown whether this view should count down
+     *
+     * @see #setBase(long)
+     */
+    @android.view.RemotableViewMethod
+    public void setCountDown(boolean countDown) {
+        mCountDown = countDown;
+    }
+
+    /**
+     * @return whether this view counts down
+     *
+     * @see #setCountDown(boolean)
+     */
+    public boolean isCountDown() {
+        return mCountDown;
+    }
+
+    /**
      * Set the time that the count-up timer is in reference to.
      *
      * @param base Use the {@link SystemClock#elapsedRealtime} time base.
@@ -226,9 +254,17 @@
 
     private synchronized void updateText(long now) {
         mNow = now;
-        long seconds = now - mBase;
+        long seconds = mCountDown ? mBase - now : now - mBase;
         seconds /= 1000;
+        boolean negative = false;
+        if (seconds < 0) {
+            seconds = -seconds;
+            negative = true;
+        }
         String text = DateUtils.formatElapsedTime(mRecycle, seconds);
+        if (negative) {
+            text = getResources().getString(R.string.negative_duration, text);
+        }
 
         if (mFormat != null) {
             Locale loc = Locale.getDefault();
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 1826dd8..b2ef687 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -965,20 +965,21 @@
     private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) {
         final Layout layout = mTextView.getLayout();
         if (layout == null) return offset;
-        final CharSequence text = mTextView.getText();
-        final int nextOffset = layout.getPaint().getTextRunCursor(text, 0, text.length(),
-                layout.isRtlCharAt(offset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR,
-                offset, findAfterGivenOffset ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE);
-        return nextOffset == -1 ? offset : nextOffset;
+        return findAfterGivenOffset == layout.isRtlCharAt(offset) ?
+                layout.getOffsetToLeftOf(offset) : layout.getOffsetToRightOf(offset);
     }
 
     private long getCharClusterRange(int offset) {
         final int textLength = mTextView.getText().length();
         if (offset < textLength) {
-            return TextUtils.packRangeInLong(offset, getNextCursorOffset(offset, true));
+            final int clusterEndOffset = getNextCursorOffset(offset, true);
+            return TextUtils.packRangeInLong(
+                    getNextCursorOffset(clusterEndOffset, false), clusterEndOffset);
         }
         if (offset - 1 >= 0) {
-            return TextUtils.packRangeInLong(getNextCursorOffset(offset, false), offset);
+            final int clusterStartOffset = getNextCursorOffset(offset, false);
+            return TextUtils.packRangeInLong(clusterStartOffset,
+                    getNextCursorOffset(clusterStartOffset, true));
         }
         return TextUtils.packRangeInLong(offset, offset);
     }
@@ -1089,7 +1090,7 @@
         CharSequence selectedText = mTextView.getTransformedText(start, end);
         ClipData data = ClipData.newPlainText(null, selectedText);
         DragLocalState localState = new DragLocalState(mTextView, start, end);
-        mTextView.startDragAndDrop(data, getTextThumbnailBuilder(selectedText), localState,
+        mTextView.startDragAndDrop(data, getTextThumbnailBuilder(start, end), localState,
                 View.DRAG_FLAG_GLOBAL);
         stopTextActionMode();
         if (hasSelectionController()) {
@@ -2296,7 +2297,7 @@
         }
     }
 
-    private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
+    private DragShadowBuilder getTextThumbnailBuilder(int start, int end) {
         TextView shadowView = (TextView) View.inflate(mTextView.getContext(),
                 com.android.internal.R.layout.text_drag_thumbnail, null);
 
@@ -2304,9 +2305,11 @@
             throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
         }
 
-        if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
-            text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
+        if (end - start > DRAG_SHADOW_MAX_TEXT_LENGTH) {
+            final long range = getCharClusterRange(start + DRAG_SHADOW_MAX_TEXT_LENGTH);
+            end = TextUtils.unpackRangeEndFromLong(range);
         }
+        final CharSequence text = mTextView.getTransformedText(start, end);
         shadowView.setText(text);
         shadowView.setTextColor(mTextView.getTextColors());
 
diff --git a/core/java/com/android/internal/util/MessageUtils.java b/core/java/com/android/internal/util/MessageUtils.java
index 1014bfd..184245e 100644
--- a/core/java/com/android/internal/util/MessageUtils.java
+++ b/core/java/com/android/internal/util/MessageUtils.java
@@ -21,6 +21,7 @@
 import android.util.SparseArray;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 
 /**
  * Static utility class for dealing with {@link Message} objects.
@@ -62,8 +63,12 @@
             }
 
             for (Field field : fields) {
-                String name = field.getName();
+                int modifiers = field.getModifiers();
+                if (!Modifier.isStatic(modifiers) | !Modifier.isFinal(modifiers)) {
+                    continue;
+                }
 
+                String name = field.getName();
                 for (String prefix : prefixes) {
                     // Does this look like a constant?
                     if (!name.startsWith(prefix)) {
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index db3ecc6..5576f13 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -46,17 +46,14 @@
             in IInputContext inputContext, int uid, int pid);
     void removeClient(in IInputMethodClient client);
 
-    InputBindResult startInput(/* @InputMethodClient.StartInputReason */ int startInputReason,
-            in IInputMethodClient client, IInputContext inputContext, in EditorInfo attribute,
-            int controlFlags);
     void finishInput(in IInputMethodClient client);
     boolean showSoftInput(in IInputMethodClient client, int flags,
             in ResultReceiver resultReceiver);
     boolean hideSoftInput(in IInputMethodClient client, int flags,
             in ResultReceiver resultReceiver);
-    // Report that a window has gained focus.  If 'attribute' is non-null,
-    // this will also do a startInput.
-    InputBindResult windowGainedFocus(
+    // If windowToken is null, this just does startInput().  Otherwise this reports that a window
+    // has gained focus, and if 'attribute' is non-null then also does startInput.
+    InputBindResult startInputOrWindowGainedFocus(
             /* @InputMethodClient.StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken, int controlFlags,
             int softInputMode, int windowFlags, in EditorInfo attribute,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index eeff00f..30d6f29 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -296,10 +296,6 @@
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
     <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
-    <protected-broadcast android:name="android.intent.action.BUGREPORT_STARTED" />
-    <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
-    <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" />
-    <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_DISPATCH" />
 
     <!-- Legacy -->
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
@@ -893,77 +889,77 @@
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.READ_PROFILE"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_PROFILE"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.READ_SOCIAL_STREAM"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_SOCIAL_STREAM"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.READ_USER_DICTIONARY"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_USER_DICTIONARY"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.WRITE_SMS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.MANAGE_ACCOUNTS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.USE_CREDENTIALS"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.SUBSCRIBED_FEEDS_READ"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- @hide We need to keep this around for backwards compatibility -->
     <permission android:name="android.permission.FLASHLIGHT"
         android:protectionLevel="normal"
-        android:permissionFlags="hidden"/>
+        android:permissionFlags="removed"/>
 
     <!-- ====================================================================== -->
     <!-- INSTALL PERMISSIONS                                                    -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4480944..2ab95412 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3539,6 +3539,9 @@
              If no format string is specified, the Chronometer will simply display
              "MM:SS" or "H:MM:SS". -->
         <attr name="format" format="string" localization="suggested" />
+        <!-- Specifies whether this Chronometer counts down or counts up from the base.
+              If not specified this is false and the Chronometer counts up. -->
+        <attr name="countDown" format="boolean" />
     </declare-styleable>
     <declare-styleable name="CompoundButton">
         <!-- Indicates the initial checked state of this button. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4b81987..d0d1d5a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -248,9 +248,12 @@
              may cost the user money.  Such permissions may be highlighted
              when shown to the user with this additional information.  -->
         <flag name="costsMoney" value="0x0001" />
-        <!-- Additional flag from base permission type: this permission is hidden
-             and should not show in the UI. -->
-        <flag name="hidden" value="0x2" />
+        <!-- Additional flag from base permission type: this permission has been
+             removed and it is no longer enforced. It shouldn't be shown in the
+             UI. Removed permissions are kept as normal permissions for backwards
+             compatibility as apps may be checking them before calling an API.
+        -->
+        <flag name="removed" value="0x2" />
     </attr>
 
     <!-- Specified the name of a group that this permission is associated
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 13812630..dbaa737 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2702,6 +2702,7 @@
     <public type="attr" name="hotSpotY" />
     <public type="attr" name="version" />
     <public type="attr" name="backupInForeground" />
+    <public type="attr" name="countDown" />
 
     <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
     <public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index da88146..3e9a6ca 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4230,4 +4230,7 @@
     <!-- View application info for a target. -->
     <string name="app_info">App info</string>
 
+    <!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.-->
+    <string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string>
+
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 9efcfda..db418f7 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -996,7 +996,6 @@
         <item name="alpha">.87</item>
         <item name="textColor">@color/black</item>
         <item name="drawablePadding">8dip</item>
-        <item name="ellipsize">marquee</item>
         <item name="gravity">start|center_vertical</item>
         <item name="layout_gravity">start|center_vertical</item>
         <item name="layout_height">48dip</item>
@@ -1017,7 +1016,6 @@
         <item name="alpha">.87</item>
         <item name="textColor">#009688</item>
         <item name="drawablePadding">8dip</item>
-        <item name="ellipsize">marquee</item>
         <item name="gravity">start|center_vertical</item>
         <item name="layout_gravity">start|center_vertical</item>
         <item name="layout_height">48dip</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9950884..a7a9292 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2541,4 +2541,5 @@
   <java-symbol type="string" name="carrier_app_dialog_not_now" />
   <java-symbol type="string" name="carrier_app_notification_title" />
   <java-symbol type="string" name="carrier_app_notification_text" />
+  <java-symbol type="string" name="negative_duration" />
 </resources>
diff --git a/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java
new file mode 100644
index 0000000..32b969a
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static org.junit.Assert.*;
+
+import com.android.internal.util.MessageUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+
+class A {
+    // Should not see these.
+    private int mMember;
+    public final int CMD_NOT_STATIC = 7;
+    private static final String CMD_NOT_INT = "not an integer";
+    public static int CMD_NOT_FINAL = 34;
+    public static final int kWrongPrefix = 99;
+
+    // Should see these.
+    private static final int CMD_DO_SOMETHING = 12;
+    public static final int EVENT_SOMETHING_HAPPENED = 45;
+}
+
+class B {
+    public static final int CMD_FOO = 56;
+    public static final int EVENT_BAR = 55;
+    public static final int NOTIFICATION_BAZ = 12;
+}
+
+/**
+ * Unit tests for {@link com.android.util.MessageUtils}.
+ */
+@SmallTest
+public class MessageUtilsTest {
+
+    private static final Class[] CLASSES = { A.class, B.class };
+
+    private SparseArray<String> makeSparseArray(int[] keys, String[] values) throws Exception {
+        assertEquals("Must specify same number of keys and values", keys.length, values.length);
+        SparseArray<String> out = new SparseArray<>();
+        for (int i = 0; i < keys.length; i++) {
+            out.put(keys[i], values[i]);
+        }
+        return out;
+    }
+
+    private void assertSparseArrayEquals(
+            SparseArray<String> a1, SparseArray<String> a2) throws Exception {
+        String msg = String.format("%s != %s", a1.toString(), a2.toString());
+        assertEquals(msg, a1.size(), a2.size());
+        int size = a1.size();
+        for (int i = 0; i < size; i++) {
+            assertEquals(msg, a1.keyAt(i), a2.keyAt(i));
+            assertEquals(msg, a1.valueAt(i), a2.valueAt(i));
+        }
+    }
+
+    @Test
+    public void basicOperation() throws Exception {
+        SparseArray<String> expected = makeSparseArray(
+            new int[]{12, 45, 55, 56},
+            new String[]{"CMD_DO_SOMETHING", "EVENT_SOMETHING_HAPPENED", "EVENT_BAR", "CMD_FOO"});
+        assertSparseArrayEquals(expected, MessageUtils.findMessageNames(CLASSES));
+    }
+
+    @Test
+    public void withPrefixes() throws Exception {
+        SparseArray<String> expected = makeSparseArray(
+            new int[]{45, 55},
+            new String[]{"EVENT_SOMETHING_HAPPENED", "EVENT_BAR"});
+        assertSparseArrayEquals(expected, MessageUtils.findMessageNames(CLASSES,
+                new String[]{"EVENT_"}));
+    }
+
+    @Test(expected=MessageUtils.DuplicateConstantError.class)
+    public void duplicateConstants() {
+        MessageUtils.findMessageNames(CLASSES, new String[]{"CMD_", "NOTIFICATION_"});
+    }
+
+}
diff --git a/docs/html/preview/behavior-changes.jd b/docs/html/preview/behavior-changes.jd
index cab4163..264e741 100644
--- a/docs/html/preview/behavior-changes.jd
+++ b/docs/html/preview/behavior-changes.jd
@@ -393,7 +393,7 @@
     only affects notifications generated by applications in the managed profile.</li>
     </ul>
   </li>
-  <li>The {@link android.app.admin.DevicePolicyManager#createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle) createAndInitializeUser()} and {@link android.app.admin.DevicePolicyManager#createUser(android.content.ComponentName, java.lang.String) createUser()} methods have been deprecated.</li>
+  <li>The {@link android.app.admin.DevicePolicyManager#createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int) createAndManageUser()} method replaces createAndInitializeUser(), which has been removed.</li>
   <li>The {@link android.app.admin.DevicePolicyManager#setScreenCaptureDisabled(android.content.ComponentName, boolean) setScreenCaptureDisabled()}
 method now also blocks the assist structure when an app of the given user is in the foreground. </li>
   <li>{@link android.app.admin.DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 294edb6..bd71e0d 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -160,6 +160,10 @@
     }
 
     if (!mStagingRequests.empty()) {
+        // No interpolator was set, use the default
+        if (mPlayState == PlayState::NotStarted && !mInterpolator) {
+            mInterpolator.reset(Interpolator::createDefaultInterpolator());
+        }
         // Keep track of the play state and play time before they are changed when
         // staging requests are resolved.
         nsecs_t currentPlayTime = mPlayTime;
@@ -222,10 +226,6 @@
         // Set to 0 so that the animate() basically instantly finishes
         mStartTime = 0;
     }
-    // No interpolator was set, use the default
-    if (!mInterpolator) {
-        mInterpolator.reset(Interpolator::createDefaultInterpolator());
-    }
     if (mDuration < 0 || mDuration > 50000) {
         ALOGW("Your duration is strange and confusing: %" PRId64, mDuration);
     }
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index f83e1fa..78764b5 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -334,15 +334,15 @@
 }
 
 static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state,
-        PathTexture& texture, const RecordedOp& op) {
+        float xOffset, float yOffset, PathTexture& texture, const SkPaint& paint) {
     Rect dest(texture.width(), texture.height());
-    dest.translate(texture.left - texture.offset,
-            texture.top - texture.offset);
+    dest.translate(xOffset + texture.left - texture.offset,
+            yOffset + texture.top - texture.offset);
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshTexturedUnitQuad(nullptr)
-            .setFillPathTexturePaint(texture, *(op.paint), state.alpha)
+            .setFillPathTexturePaint(texture, paint, state.alpha)
             .setTransform(state.computedState.transform,  TransformFlags::None)
             .setModelViewMapUnitToRect(dest)
             .build();
@@ -368,7 +368,8 @@
                 op.startAngle, op.sweepAngle, op.useCenter, op.paint);
         const AutoTexture holder(texture);
         if (CC_LIKELY(holder.texture)) {
-            renderPathTexture(renderer, state, *texture, op);
+            renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.right,
+                    *texture, *(op.paint));
         }
     } else {
         SkRect rect = getBoundsOfFill(op);
@@ -519,7 +520,8 @@
                 op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
         const AutoTexture holder(texture);
         if (CC_LIKELY(holder.texture)) {
-            renderPathTexture(renderer, state, *texture, op);
+            renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.right,
+                    *texture, *(op.paint));
         }
     } else {
         SkPath path;
@@ -562,7 +564,9 @@
     PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint);
     const AutoTexture holder(texture);
     if (CC_LIKELY(holder.texture)) {
-        renderPathTexture(renderer, state, *texture, op);
+        // Unlike other callers to renderPathTexture, no offsets are used because PathOp doesn't
+        // have any translate built in, other than what's in the SkPath itself
+        renderPathTexture(renderer, state, 0, 0, *texture, *(op.paint));
     }
 }
 
@@ -588,7 +592,8 @@
                      op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
              const AutoTexture holder(texture);
              if (CC_LIKELY(holder.texture)) {
-                 renderPathTexture(renderer, state, *texture, op);
+                 renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+                         *texture, *(op.paint));
              }
         } else {
             SkPath path;
@@ -622,7 +627,8 @@
                 op.rx, op.ry, op.paint);
         const AutoTexture holder(texture);
         if (CC_LIKELY(holder.texture)) {
-            renderPathTexture(renderer, state, *texture, op);
+            renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+                    *texture, *(op.paint));
         }
     } else {
         const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect(
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index 1ba3bf2..c5d7191 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -239,7 +239,7 @@
         // put the verts in the frame allocator, since
         //     1) SimpleRectsOps needs verts, not rects
         //     2) even if mClearRects stored verts, std::vectors will move their contents
-        Vertex* const verts = (Vertex*) allocator.alloc<Vertex>(vertCount * sizeof(Vertex));
+        Vertex* const verts = (Vertex*) allocator.create_trivial_array<Vertex>(vertCount);
 
         Vertex* currentVert = verts;
         Rect bounds = mClearRects[0];
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 0adb21c..78a0b13 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -290,7 +290,7 @@
 void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
     if (rects == nullptr) return;
 
-    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc<Vertex>(vertexCount * sizeof(Vertex));
+    Vertex* rectData = (Vertex*) mDisplayList->allocator.create_trivial_array<Vertex>(vertexCount);
     Vertex* vertex = rectData;
 
     float left = FLT_MAX;
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 0ac2f14..b03643f 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -44,7 +44,7 @@
  */
 class RoundRectClipState {
 public:
-    /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+    static void* operator new(size_t size) = delete;
     static void* operator new(size_t size, LinearAllocator& allocator) {
         return allocator.alloc<RoundRectClipState>(size);
     }
@@ -65,7 +65,7 @@
 
 class ProjectionPathMask {
 public:
-    /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+    static void* operator new(size_t size) = delete;
     static void* operator new(size_t size, LinearAllocator& allocator) {
         return allocator.alloc<ProjectionPathMask>(size);
     }
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index 0a0e185..34c8c6b 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -84,6 +84,13 @@
         return new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...);
     }
 
+    template<class T>
+    T* create_trivial_array(int count) {
+        static_assert(std::is_trivially_destructible<T>::value,
+                "Error, called create_trivial_array on a non-trivial type");
+        return reinterpret_cast<T*>(allocImpl(sizeof(T) * count));
+    }
+
     /**
      * Attempt to deallocate the given buffer, with the LinearAllocator attempting to rewind its
      * state if possible.
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 45529ef..9922b72 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -111,6 +111,10 @@
      * A device type connected over IP.
      */
     public static final int TYPE_IP               = 20;
+    /**
+     * A type-agnostic device used for communication with external audio systems
+     */
+    public static final int TYPE_BUS              = 21;
 
     private final AudioDevicePort mPort;
 
@@ -279,6 +283,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_FM, TYPE_FM);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_AUX_LINE, TYPE_AUX_LINE);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -296,6 +301,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_SPDIF, TYPE_LINE_DIGITAL);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
 
         // not covered here, legacy
         //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
@@ -323,6 +329,7 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
     }
 }
 
diff --git a/media/java/android/media/AudioFormat.aidl b/media/java/android/media/AudioFormat.aidl
new file mode 100644
index 0000000..8613f55
--- /dev/null
+++ b/media/java/android/media/AudioFormat.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.media;
+
+parcelable AudioFormat;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 000a56d..22f4f04 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -18,10 +18,13 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * The {@link AudioFormat} class is used to access a number of audio format and
@@ -209,7 +212,7 @@
  * AudioTrack.getPlaybackHeadPosition()}),
  * depending on the context where audio frame is used.
  */
-public class AudioFormat {
+public class AudioFormat implements Parcelable {
 
     //---------------------------------------------------------
     // Constants
@@ -874,6 +877,44 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(mPropertySetMask, mSampleRate, mEncoding, mChannelMask,
+                mChannelIndexMask);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPropertySetMask);
+        dest.writeInt(mEncoding);
+        dest.writeInt(mSampleRate);
+        dest.writeInt(mChannelMask);
+        dest.writeInt(mChannelIndexMask);
+    }
+
+    private AudioFormat(Parcel in) {
+        mPropertySetMask = in.readInt();
+        mEncoding = in.readInt();
+        mSampleRate = in.readInt();
+        mChannelMask = in.readInt();
+        mChannelIndexMask = in.readInt();
+    }
+
+    public static final Parcelable.Creator<AudioFormat> CREATOR =
+            new Parcelable.Creator<AudioFormat>() {
+        public AudioFormat createFromParcel(Parcel p) {
+            return new AudioFormat(p);
+        }
+        public AudioFormat[] newArray(int size) {
+            return new AudioFormat[size];
+        }
+    };
+
+    @Override
     public String toString () {
         return new String("AudioFormat:"
                 + " props=" + mPropertySetMask
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index aa0d78d..b3f73be 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -346,6 +346,7 @@
     public static final int DEVICE_OUT_AUX_LINE = 0x200000;
     public static final int DEVICE_OUT_SPEAKER_SAFE = 0x400000;
     public static final int DEVICE_OUT_IP = 0x800000;
+    public static final int DEVICE_OUT_BUS = 0x1000000;
 
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
 
@@ -373,6 +374,7 @@
                                               DEVICE_OUT_AUX_LINE |
                                               DEVICE_OUT_SPEAKER_SAFE |
                                               DEVICE_OUT_IP |
+                                              DEVICE_OUT_BUS |
                                               DEVICE_OUT_DEFAULT);
     public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP |
                                                    DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
@@ -412,6 +414,7 @@
     public static final int DEVICE_IN_BLUETOOTH_A2DP = DEVICE_BIT_IN | 0x20000;
     public static final int DEVICE_IN_LOOPBACK = DEVICE_BIT_IN | 0x40000;
     public static final int DEVICE_IN_IP = DEVICE_BIT_IN | 0x80000;
+    public static final int DEVICE_IN_BUS = DEVICE_BIT_IN | 0x100000;
     public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT;
 
     public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION |
@@ -434,6 +437,7 @@
                                              DEVICE_IN_BLUETOOTH_A2DP |
                                              DEVICE_IN_LOOPBACK |
                                              DEVICE_IN_IP |
+                                             DEVICE_IN_BUS |
                                              DEVICE_IN_DEFAULT);
     public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET;
     public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY |
@@ -469,6 +473,7 @@
     public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
     public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe";
     public static final String DEVICE_OUT_IP_NAME = "ip";
+    public static final String DEVICE_OUT_BUS_NAME = "bus";
 
     public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
     public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -490,6 +495,7 @@
     public static final String DEVICE_IN_BLUETOOTH_A2DP_NAME = "bt_a2dp";
     public static final String DEVICE_IN_LOOPBACK_NAME = "loopback";
     public static final String DEVICE_IN_IP_NAME = "ip";
+    public static final String DEVICE_IN_BUS_NAME = "bus";
 
     public static String getOutputDeviceName(int device)
     {
@@ -542,6 +548,8 @@
             return DEVICE_OUT_SPEAKER_SAFE_NAME;
         case DEVICE_OUT_IP:
             return DEVICE_OUT_IP_NAME;
+        case DEVICE_OUT_BUS:
+            return DEVICE_OUT_BUS_NAME;
         case DEVICE_OUT_DEFAULT:
         default:
             return Integer.toString(device);
@@ -591,6 +599,8 @@
             return DEVICE_IN_LOOPBACK_NAME;
         case DEVICE_IN_IP:
             return DEVICE_IN_IP_NAME;
+        case DEVICE_IN_BUS:
+            return DEVICE_IN_BUS_NAME;
         case DEVICE_IN_DEFAULT:
         default:
             return Integer.toString(device);
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 1c11842..3e6aa9f 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -53,6 +53,12 @@
     /** The authority for the TV provider. */
     public static final String AUTHORITY = "android.media.tv";
 
+    /**
+     * Permission to read TV listings. This is required to read all the TV channel and program
+     * information available on the system.
+     */
+    public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
+
     private static final String PATH_CHANNEL = "channel";
     private static final String PATH_PROGRAM = "program";
     private static final String PATH_RECORDED_PROGRAM = "recorded_program";
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 58e7709..637e06e 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -1,6 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.documentsui">
 
+    <uses-permission android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
     <uses-permission android:name="android.permission.REMOVE_TASKS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index b67a6915..845e32c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -23,9 +23,11 @@
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
 
 import android.app.Activity;
 import android.app.Fragment;
+import android.app.FragmentManager;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -46,7 +48,7 @@
 import android.view.MenuItem;
 import android.widget.Spinner;
 
-import com.android.documentsui.SearchManager.SearchManagerListener;
+import com.android.documentsui.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.State.ViewMode;
 import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.dirlist.Model;
@@ -64,14 +66,12 @@
 public abstract class BaseActivity extends Activity
         implements SearchManagerListener, NavigationView.Environment {
 
-    static final String EXTRA_STATE = "state";
-
     // See comments where this const is referenced for details.
     private static final int DRAWER_NO_FIDDLE_DELAY = 1500;
 
     State mState;
     RootsCache mRoots;
-    SearchManager mSearchManager;
+    SearchViewManager mSearchManager;
     DrawerController mDrawer;
     NavigationView mNavigator;
 
@@ -121,7 +121,7 @@
                     }
                 });
 
-        mSearchManager = new SearchManager(this);
+        mSearchManager = new SearchViewManager(this, icicle);
 
         DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
         setActionBar(toolbar);
@@ -141,6 +141,7 @@
         boolean showMenu = super.onCreateOptionsMenu(menu);
 
         getMenuInflater().inflate(R.menu.activity, menu);
+        mNavigator.update();
         mSearchManager.install((DocumentsToolbar) findViewById(R.id.toolbar));
 
         return showMenu;
@@ -188,7 +189,7 @@
 
     private State getState(@Nullable Bundle icicle) {
         if (icicle != null) {
-            State state = icicle.<State>getParcelable(EXTRA_STATE);
+            State state = icicle.<State>getParcelable(Shared.EXTRA_STATE);
             if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
             return state;
         }
@@ -224,6 +225,9 @@
     }
 
     void onRootPicked(RootInfo root) {
+        // Clicking on the current root removes search
+        mSearchManager.cancelSearch();
+
         // Skip refreshing if root nor directory didn't change
         if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
             return;
@@ -233,7 +237,6 @@
 
         // Clear entire backstack and start in new root
         mState.onRootChanged(root);
-        mSearchManager.update(root);
 
         // Recents is always in memory, so we just load it directly.
         // Otherwise we delegate loading data from disk to a task
@@ -314,6 +317,12 @@
         CreateDirectoryFragment.show(getFragmentManager());
     }
 
+    void onDirectoryCreated(DocumentInfo doc) {
+        // By default we do nothing, just let the new directory appear.
+        // DocumentsActivity auto-opens directories after creating them
+        // As that is more attuned to the "picker" use cases it supports.
+    }
+
     /**
      * Returns true if a directory can be created in the current location.
      * @return
@@ -328,11 +337,6 @@
                 && !root.isDownloads();
     }
 
-    void onDirectoryCreated(DocumentInfo doc) {
-        checkArgument(doc.isDirectory());
-        openContainerDocument(doc);
-    }
-
     void openContainerDocument(DocumentInfo doc) {
         checkArgument(doc.isContainer());
         mState.pushDocument(doc);
@@ -370,18 +374,18 @@
      * e.g. The current directory name displayed on the action bar won't get updated.
      */
     @Override
-    public void onSearchChanged() {
-        refreshDirectory(ANIM_NONE);
+    public void onSearchChanged(@Nullable String query) {
+        // We should not get here if root is not searchable
+        checkState(canSearchRoot());
+        reloadSearch(query);
     }
 
-    /**
-     * Called when search query changed.
-     * Updates the state object.
-     * @param query - New query
-     */
-    @Override
-    public void onSearchQueryChanged(String query) {
-        mState.currentSearch = query;
+    private void reloadSearch(String query) {
+        FragmentManager fm = getFragmentManager();
+        RootInfo root = getCurrentRoot();
+        DocumentInfo cwd = getCurrentDirectory();
+
+        DirectoryFragment.reloadSearch(fm, root, cwd, query);
     }
 
     final List<String> getExcludedAuthorities() {
@@ -486,7 +490,8 @@
     @Override
     protected void onSaveInstanceState(Bundle state) {
         super.onSaveInstanceState(state);
-        state.putParcelable(EXTRA_STATE, mState);
+        state.putParcelable(Shared.EXTRA_STATE, mState);
+        mSearchManager.onSaveInstanceState(state);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index b0542b9..13b7b14 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -53,19 +53,22 @@
     private final RootInfo mRoot;
     private final Uri mUri;
     private final int mUserSortOrder;
+    private final boolean mSearchMode;
 
     private DocumentInfo mDoc;
     private CancellationSignal mSignal;
     private DirectoryResult mResult;
 
+
     public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
-            int userSortOrder) {
+            int userSortOrder, boolean inSearchMode) {
         super(context, ProviderExecutor.forAuthority(root.authority));
         mType = type;
         mRoot = root;
         mUri = uri;
         mUserSortOrder = userSortOrder;
         mDoc = doc;
+        mSearchMode = inSearchMode;
     }
 
     @Override
@@ -83,7 +86,7 @@
         final DirectoryResult result = new DirectoryResult();
 
         // Use default document when searching
-        if (mType == DirectoryFragment.TYPE_SEARCH) {
+        if (mSearchMode) {
             final Uri docUri = DocumentsContract.buildDocumentUri(
                     mRoot.authority, mRoot.documentId);
             try {
@@ -106,7 +109,7 @@
         }
 
         // Search always uses ranking from provider
-        if (mType == DirectoryFragment.TYPE_SEARCH) {
+        if (mSearchMode) {
             result.sortOrder = State.SORT_ORDER_UNKNOWN;
         }
 
@@ -127,7 +130,7 @@
 
             cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
 
-            if (mType == DirectoryFragment.TYPE_SEARCH) {
+            if (mSearchMode) {
                 // Filter directories out of search results, for now
                 cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index ec7dde9..6ecdc90 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -23,6 +23,7 @@
 import static com.android.documentsui.State.ACTION_OPEN_TREE;
 import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
+import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -290,13 +291,8 @@
                 mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
             }
         } else {
-            if (mSearchManager.isSearching()) {
-                // Ongoing search
-                DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
-            } else {
                 // Normal boring directory
                 DirectoryFragment.showDirectory(fm, root, cwd, anim);
-            }
         }
 
         // Forget any replacement target
@@ -321,6 +317,12 @@
                 .executeOnExecutor(getExecutorForCurrentDirectory());
     }
 
+    @Override
+    void onDirectoryCreated(DocumentInfo doc) {
+        checkArgument(doc.isDirectory());
+        openContainerDocument(doc);
+    }
+
     void onSaveRequested(String mimeType, String displayName) {
         new CreateFinishTask(this, mimeType, displayName)
                 .executeOnExecutor(getExecutorForCurrentDirectory());
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index f7a45e2..9609dee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -16,8 +16,10 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.State.ACTION_MANAGE;
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
+import static com.android.internal.util.Preconditions.checkState;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -119,17 +121,14 @@
         final RootInfo root = getCurrentRoot();
         final DocumentInfo cwd = getCurrentDirectory();
 
+        if (DEBUG) checkState(!mSearchManager.isSearching());
+
         // If started in manage roots mode, there has to be a cwd (i.e. the root dir of the managed
         // root).
         Preconditions.checkNotNull(cwd);
 
-        if (mState.currentSearch != null) {
-            // Ongoing search
-            DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
-        } else {
-            // Normal boring directory
-            DirectoryFragment.showDirectory(fm, root, cwd, anim);
-        }
+        // Normal boring directory
+        DirectoryFragment.showDirectory(fm, root, cwd, anim);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index c0faba3..8b42ac5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -33,7 +33,6 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
-import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -82,7 +81,6 @@
 
         if (mState.restored) {
             if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
-            refreshCurrentRootAndDirectory(ANIM_NONE);
         } else if (!mState.stack.isEmpty()) {
             // If a non-empty stack is present in our state it was read (presumably)
             // from EXTRA_STACK intent extra. In this case, we'll skip other means of
@@ -248,16 +246,13 @@
         final RootInfo root = getCurrentRoot();
         final DocumentInfo cwd = getCurrentDirectory();
 
+        if (DEBUG) checkState(!mSearchManager.isSearching());
+
         if (cwd == null) {
             DirectoryFragment.showRecentsOpen(fm, anim);
         } else {
-            if (mState.currentSearch != null) {
-                // Ongoing search
-                DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
-            } else {
-                // Normal boring directory
-                DirectoryFragment.showDirectory(fm, root, cwd, anim);
-            }
+            // Normal boring directory
+            DirectoryFragment.showDirectory(fm, root, cwd, anim);
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index 025faea..27d6797 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -22,6 +22,7 @@
 import static com.android.documentsui.Shared.DEBUG;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -33,6 +34,7 @@
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
+import android.content.UriPermission;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.Uri;
@@ -62,6 +64,8 @@
     private static final String EXTRA_APP_LABEL = "com.android.documentsui.APP_LABEL";
     private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
 
+    private ContentProviderClient mExternalStorageClient;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -98,12 +102,20 @@
         }
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mExternalStorageClient != null) {
+            mExternalStorageClient.close();
+        }
+    }
+
     /**
      * Validates the given path (volume + directory) and display the appropriate dialog asking the
      * user to grant access to it.
      */
-    private static boolean showFragment(Activity activity, int userId, StorageVolume storageVolume,
-            String directoryName) {
+    private static boolean showFragment(OpenExternalDirectoryActivity activity, int userId,
+            StorageVolume storageVolume, String directoryName) {
         if (DEBUG)
             Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
                     + directoryName + ", and user " + userId);
@@ -129,7 +141,7 @@
             return false;
         }
 
-        // Gets volume label and converted path
+        // Gets volume label and converted path.
         String volumeLabel = null;
         final List<VolumeInfo> volumes = sm.getVolumes();
         if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
@@ -143,6 +155,15 @@
                 break;
             }
         }
+
+        // Checks if the user has granted the permission already.
+        final Intent intent = getIntentForExistingPermission(activity, file);
+        if (intent != null) {
+            activity.setResult(RESULT_OK, intent);
+            activity.finish();
+            return true;
+        }
+
         if (volumeLabel == null) {
             Log.e(TAG, "Could not get volume for " + file);
             return false;
@@ -196,8 +217,7 @@
         return volume.isVisibleForWrite(userId) && root.equals(path);
     }
 
-    private static Intent createGrantedUriPermissionsIntent(ContentProviderClient provider,
-            File file) {
+    private static Uri getGrantedUriPermission(ContentProviderClient provider, File file) {
         // Calls ExternalStorageProvider to get the doc id for the file
         final Bundle bundle;
         try {
@@ -218,8 +238,17 @@
             Log.e(TAG, "Could not get URI for doc id " + docId);
             return null;
         }
-
         if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
+        return uri;
+    }
+
+    private static Intent createGrantedUriPermissionsIntent(ContentProviderClient provider,
+            File file) {
+        final Uri uri = getGrantedUriPermission(provider, file);
+        return createGrantedUriPermissionsIntent(uri);
+    }
+
+    private static Intent createGrantedUriPermissionsIntent(Uri uri) {
         final Intent intent = new Intent();
         intent.setData(uri);
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -229,13 +258,31 @@
         return intent;
     }
 
+    private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
+            File file) {
+        final String packageName = activity.getCallingPackage();
+        final Uri grantedUri = getGrantedUriPermission(activity.getExternalStorageClient(), file);
+        if (DEBUG)
+            Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri);
+        final ActivityManager am =
+                (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
+        for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
+            final Uri uri = uriPermission.getUri();
+            if (uri.equals(grantedUri)) {
+                if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
+                return createGrantedUriPermissionsIntent(uri);
+            }
+        }
+        if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
+        return null;
+    }
+
     public static class OpenExternalDirectoryDialogFragment extends DialogFragment {
 
         private File mFile;
         private String mVolumeLabel;
         private String mAppLabel;
-        private ContentProviderClient mExternalStorageClient;
-        private ContentResolver mResolver;
+        private OpenExternalDirectoryActivity mActivity;
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -245,16 +292,8 @@
                 mFile = new File(args.getString(EXTRA_FILE));
                 mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
                 mAppLabel = args.getString(EXTRA_APP_LABEL);
-                mResolver = getContext().getContentResolver();
             }
-        }
-
-        @Override
-        public void onDestroy() {
-            super.onDestroy();
-            if (mExternalStorageClient != null) {
-                mExternalStorageClient.close();
-            }
+            mActivity = (OpenExternalDirectoryActivity) getActivity();
         }
 
         @Override
@@ -267,8 +306,8 @@
                 public void onClick(DialogInterface dialog, int which) {
                     Intent intent = null;
                     if (which == DialogInterface.BUTTON_POSITIVE) {
-                        intent = createGrantedUriPermissionsIntent(getExternalStorageClient(),
-                                mFile);
+                        intent = createGrantedUriPermissionsIntent(
+                                mActivity.getExternalStorageClient(), mFile);
                     }
                     if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
                         activity.setResult(RESULT_CANCELED);
@@ -297,13 +336,13 @@
             activity.setResult(RESULT_CANCELED);
             activity.finish();
         }
+    }
 
-        private synchronized ContentProviderClient getExternalStorageClient() {
-            if (mExternalStorageClient == null) {
-                mExternalStorageClient =
-                        mResolver.acquireContentProviderClient(EXTERNAL_STORAGE_AUTH);
-            }
-            return mExternalStorageClient;
+    private synchronized ContentProviderClient getExternalStorageClient() {
+        if (mExternalStorageClient == null) {
+            mExternalStorageClient =
+                    getContentResolver().acquireContentProviderClient(EXTERNAL_STORAGE_AUTH);
         }
+        return mExternalStorageClient;
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
similarity index 78%
rename from packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
rename to packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
index 69f54c7..0496862 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import android.annotation.Nullable;
+import android.os.Bundle;
 import android.provider.DocumentsContract.Root;
 import android.text.TextUtils;
 import android.util.Log;
@@ -31,28 +33,27 @@
 /**
  * Manages searching UI behavior.
  */
-final class SearchManager implements
+final class SearchViewManager implements
         SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener {
 
     public interface SearchManagerListener {
-        void onSearchChanged();
-
-        void onSearchQueryChanged(String query);
+        void onSearchChanged(@Nullable String query);
     }
 
     public static final String TAG = "SearchManger";
 
     private SearchManagerListener mListener;
-    private String currentSearch;
     private boolean mSearchExpanded;
+    private String mCurrentSearch;
     private boolean mIgnoreNextClose;
 
     private DocumentsToolbar mActionBar;
     private MenuItem mMenu;
     private SearchView mView;
 
-    public SearchManager(SearchManagerListener listener) {
+    public SearchViewManager(SearchManagerListener listener, @Nullable Bundle savedState) {
         mListener = listener;
+        mCurrentSearch = savedState != null ? savedState.getString(Shared.EXTRA_QUERY) : null;
     }
 
     public void setSearchMangerListener(SearchManagerListener listener) {
@@ -69,6 +70,8 @@
         mView.setOnCloseListener(this);
         mView.setOnSearchClickListener(this);
         mView.setOnQueryTextFocusChangeListener(this);
+
+        restoreSearch();
     }
 
     /**
@@ -80,12 +83,12 @@
             return;
         }
 
-        if (currentSearch != null) {
+        if (mCurrentSearch != null) {
             mMenu.expandActionView();
 
             mView.setIconified(false);
             mView.clearFocus();
-            mView.setQuery(currentSearch, false);
+            mView.setQuery(mCurrentSearch, false);
         } else {
             mView.clearFocus();
             if (!mView.isIconified()) {
@@ -108,13 +111,11 @@
             return;
         }
 
-        mMenu.setVisible(visible);
         if (!visible) {
-            currentSearch = null;
-            if (mListener != null) {
-                mListener.onSearchQueryChanged(currentSearch);
-            }
+            mCurrentSearch = null;
         }
+
+        mMenu.setVisible(visible);
     }
 
     /**
@@ -133,8 +134,23 @@
         return false;
     }
 
+    private void restoreSearch() {
+        if (isSearching()) {
+            onSearchExpanded();
+            mView.setIconified(false);
+            mView.setQuery(mCurrentSearch, false);
+            mView.clearFocus();
+        }
+    }
+
+    private void onSearchExpanded() {
+        mSearchExpanded = true;
+        mView.setBackgroundColor(
+                mView.getResources().getColor(R.color.menu_search_background, null));
+    }
+
     boolean isSearching() {
-        return currentSearch != null;
+        return mCurrentSearch != null;
     }
 
     boolean isExpanded() {
@@ -142,6 +158,14 @@
     }
 
     /**
+     * Called when owning activity is saving state to be used to restore state during creation.
+     * @param state Bundle to save state too
+     */
+    public void onSaveInstanceState(Bundle state) {
+        state.putString(Shared.EXTRA_QUERY, mCurrentSearch);
+    }
+
+    /**
      * Clears the search. Clears the SearchView background color. Triggers refreshing of the
      * directory content.
      * @return True if the default behavior of clearing/dismissing SearchView should be overridden.
@@ -159,11 +183,10 @@
                 mView.getResources().getColor(android.R.color.transparent, null));
 
         // Refresh the directory if a search was done
-        if (currentSearch != null) {
-            currentSearch = null;
+        if (mCurrentSearch != null) {
+            mCurrentSearch = null;
             if (mListener != null) {
-                mListener.onSearchQueryChanged(currentSearch);
-                mListener.onSearchChanged();
+                mListener.onSearchChanged(mCurrentSearch);
             }
         }
         return false;
@@ -176,18 +199,15 @@
      */
     @Override
     public void onClick(View v) {
-        mSearchExpanded = true;
-        mView.setBackgroundColor(
-                mView.getResources().getColor(R.color.menu_search_background, null));
+        onSearchExpanded();
     }
 
     @Override
     public boolean onQueryTextSubmit(String query) {
-        currentSearch = query;
+        mCurrentSearch = query;
         mView.clearFocus();
         if (mListener != null) {
-            mListener.onSearchQueryChanged(currentSearch);
-            mListener.onSearchChanged();
+            mListener.onSearchChanged(mCurrentSearch);
         }
         return true;
     }
@@ -195,7 +215,7 @@
     @Override
     public void onFocusChange(View v, boolean hasFocus) {
         if (!hasFocus) {
-            if (currentSearch == null) {
+            if (mCurrentSearch == null) {
                 mView.setIconified(true);
             } else if (TextUtils.isEmpty(mView.getQuery())) {
                 cancelSearch();
@@ -207,4 +227,9 @@
     public boolean onQueryTextChange(String newText) {
         return false;
     }
+
+    String getCurrentSearch() {
+        return mCurrentSearch;
+    }
+
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index b90a119..a288fe8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -37,10 +37,50 @@
      * 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 String EXTRA_STACK = "com.android.documentsui.STACK";
+
+    /**
+     * Extra flag used to store query of type String in the bundle.
+     */
+    public static final String EXTRA_QUERY = "query";
+
+    /**
+     * Extra flag used to store state of type State in the bundle.
+     */
+    public static final String EXTRA_STATE = "state";
+
+    /**
+     * Extra flag used to store type of DirectoryFragment's type ResultType type in the bundle.
+     */
+    public static final String EXTRA_TYPE = "type";
+
+    /**
+     * Extra flag used to store root of type RootInfo in the bundle.
+     */
+    public static final String EXTRA_ROOT = "root";
+
+    /**
+     * Extra flag used to store document of DocumentInfo type in the bundle.
+     */
+    public static final String EXTRA_DOC = "document";
+
+    /**
+     * Extra flag used to store DirectoryFragment's selection of Selection type in the bundle.
+     */
+    public static final String EXTRA_SELECTION = "selection";
+
+    /**
+     * Extra flag used to store DirectoryFragment's search mode of boolean type in the bundle.
+     */
+    public static final String EXTRA_SEARCH_MODE = "searchMode";
+
+    /**
+     * Extra flag used to store DirectoryFragment's ignore state of boolean type in the bundle.
+     */
+    public static final String EXTRA_IGNORE_STATE = "ignoreState";
 
     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 0948ab1..2ecbdf6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -105,9 +105,6 @@
     private boolean mInitialRootChanged;
     private boolean mInitialDocChanged;
 
-    /** Currently active search, overriding any stack. */
-    public String currentSearch;
-
     /** Instance state for every shown directory */
     public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
 
@@ -186,7 +183,6 @@
         out.writeInt(showAdvanced ? 1 : 0);
         out.writeInt(restored ? 1 : 0);
         DurableUtils.writeToParcel(out, stack);
-        out.writeString(currentSearch);
         out.writeMap(dirState);
         out.writeParcelable(selectedDocuments, 0);
         out.writeList(selectedDocumentsForCopy);
@@ -217,7 +213,6 @@
             state.showAdvanced = in.readInt() != 0;
             state.restored = in.readInt() != 0;
             DurableUtils.readFromParcel(in, state.stack);
-            state.currentSearch = in.readString();
             in.readMap(state.dirState, loader);
             state.selectedDocuments = in.readParcelable(loader);
             in.readList(state.selectedDocumentsForCopy, loader);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4583dec..7138c2d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -113,19 +113,26 @@
 /**
  * Display the documents inside a single directory.
  */
-public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {
+public class DirectoryFragment extends Fragment
+        implements DocumentsAdapter.Environment, LoaderCallbacks<DirectoryResult> {
 
     @IntDef(flag = true, value = {
             TYPE_NORMAL,
-            TYPE_SEARCH,
             TYPE_RECENT_OPEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultType {}
     public static final int TYPE_NORMAL = 1;
-    public static final int TYPE_SEARCH = 2;
-    public static final int TYPE_RECENT_OPEN = 3;
+    public static final int TYPE_RECENT_OPEN = 2;
 
+    @IntDef(flag = true, value = {
+            ANIM_NONE,
+            ANIM_SIDE,
+            ANIM_LEAVE,
+            ANIM_ENTER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AnimationType {}
     public static final int ANIM_NONE = 1;
     public static final int ANIM_SIDE = 2;
     public static final int ANIM_LEAVE = 3;
@@ -146,12 +153,6 @@
     private static final int DELETE_JOB_DELAY = 5500;
     private static final int EMPTY_REVEAL_DURATION = 250;
 
-    private static final String EXTRA_TYPE = "type";
-    private static final String EXTRA_ROOT = "root";
-    private static final String EXTRA_DOC = "doc";
-    private static final String EXTRA_QUERY = "query";
-    private static final String EXTRA_IGNORE_STATE = "ignoreState";
-
     private Model mModel;
     private MultiSelectManager mSelectionManager;
     private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
@@ -164,12 +165,10 @@
     private RecyclerView mRecView;
     private ListeningGestureDetector mGestureDetector;
 
-    private @ResultType int mType = TYPE_NORMAL;
     private String mStateKey;
 
     private int mLastSortOrder = SORT_ORDER_UNKNOWN;
     private DocumentsAdapter mAdapter;
-    private LoaderCallbacks<DirectoryResult> mCallbacks;
     private FragmentTuner mTuner;
     private DocumentClipper mClipper;
     private GridLayoutManager mLayout;
@@ -178,6 +177,14 @@
     private MessageBar mMessageBar;
     private View mProgressBar;
 
+    // Directory fragment state is defined by: root, document, query, type, selection
+    private @ResultType int mType = TYPE_NORMAL;
+    private RootInfo mRoot;
+    private DocumentInfo mDocument;
+    private String mQuery = null;
+    private Selection mSelection = null;
+    private boolean mSearchMode = false;
+
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -226,9 +233,16 @@
         final Context context = getActivity();
         final State state = getDisplayState();
 
-        final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
-        final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
-        mStateKey = buildStateKey(root, doc);
+        // Read arguments when object created for the first time.
+        // Restore state if fragment recreated.
+        Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
+        mRoot = args.getParcelable(Shared.EXTRA_ROOT);
+        mDocument = args.getParcelable(Shared.EXTRA_DOC);
+        mStateKey = buildStateKey(mRoot, mDocument);
+        mQuery = args.getString(Shared.EXTRA_QUERY);
+        mType = args.getInt(Shared.EXTRA_TYPE);
+        mSelection = args.getParcelable(Shared.EXTRA_SELECTION);
+        mSearchMode = args.getBoolean(Shared.EXTRA_SEARCH_MODE);
 
         mIconHelper = new IconHelper(context, MODE_GRID);
 
@@ -248,13 +262,6 @@
 
         mRecView.addOnItemTouchListener(mGestureDetector);
 
-        // final here because we'll manually bump the listener iwhen we had an initial selection,
-        // but only after the model is fully loaded.
-        final SelectionModeListener selectionListener = new SelectionModeListener();
-        final Selection initialSelection = state.selectedDocuments.hasDirectoryKey(mStateKey)
-            ? state.selectedDocuments
-            : null;
-
         // TODO: instead of inserting the view into the constructor, extract listener-creation code
         // and set the listener on the view after the fact.  Then the view doesn't need to be passed
         // into the selection manager.
@@ -264,9 +271,9 @@
                 state.allowMultiple
                     ? MultiSelectManager.MODE_MULTIPLE
                     : MultiSelectManager.MODE_SINGLE,
-                initialSelection);
+                null);
 
-        mSelectionManager.addCallback(selectionListener);
+        mSelectionManager.addCallback(new SelectionModeListener());
 
         mModel = new Model();
         mModel.addUpdateListener(mAdapter);
@@ -275,8 +282,6 @@
         // Make sure this is done after the RecyclerView is set up.
         mFocusManager = new FocusManager(context, mRecView, mModel);
 
-        mType = getArguments().getInt(EXTRA_TYPE);
-
         mTuner = FragmentTuner.pick(getContext(), state);
         mClipper = new DocumentClipper(context);
 
@@ -286,7 +291,7 @@
             hideGridTitles = MimePredicate.mimeMatches(
                     MimePredicate.VISUAL_MIMES, state.acceptMimes);
         } else {
-            hideGridTitles = (doc != null) && doc.isGridTitlesHidden();
+            hideGridTitles = (mDocument != null) && mDocument.isGridTitlesHidden();
         }
         GridDocumentHolder.setHideTitles(hideGridTitles);
 
@@ -295,86 +300,20 @@
         boolean svelte = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
         mIconHelper.setThumbnailsEnabled(!svelte);
 
-        mCallbacks = new LoaderCallbacks<DirectoryResult>() {
-            @Override
-            public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
-                final String query = getArguments().getString(EXTRA_QUERY);
-
-                Uri contentsUri;
-                switch (mType) {
-                    case TYPE_NORMAL:
-                        contentsUri = DocumentsContract.buildChildDocumentsUri(
-                                doc.authority, doc.documentId);
-                        if (state.action == ACTION_MANAGE) {
-                            contentsUri = DocumentsContract.setManageMode(contentsUri);
-                        }
-                        return new DirectoryLoader(
-                                context, mType, root, doc, contentsUri, state.userSortOrder);
-                    case TYPE_SEARCH:
-                        contentsUri = DocumentsContract.buildSearchDocumentsUri(
-                                root.authority, root.rootId, query);
-                        if (state.action == ACTION_MANAGE) {
-                            contentsUri = DocumentsContract.setManageMode(contentsUri);
-                        }
-                        return new DirectoryLoader(
-                                context, mType, root, doc, contentsUri, state.userSortOrder);
-                    case TYPE_RECENT_OPEN:
-                        final RootsCache roots = DocumentsApplication.getRootsCache(context);
-                        return new RecentsLoader(context, roots, state);
-                    default:
-                        throw new IllegalStateException("Unknown type " + mType);
-                }
-            }
-
-            @Override
-            public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
-                if (!isAdded()) return;
-
-                mModel.update(result);
-                state.derivedSortOrder = result.sortOrder;
-
-                updateDisplayState();
-
-                if (initialSelection != null) {
-                    selectionListener.onSelectionChanged();
-                }
-
-                // Restore any previous instance state
-                final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
-                if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
-                    getView().restoreHierarchyState(container);
-                } else if (mLastSortOrder != state.derivedSortOrder) {
-                    // The derived sort order takes the user sort order into account, but applies
-                    // directory-specific defaults when the user doesn't explicitly set the sort
-                    // order. Scroll to the top if the sort order actually changed.
-                    mRecView.smoothScrollToPosition(0);
-                }
-
-                mLastSortOrder = state.derivedSortOrder;
-
-                mTuner.onModelLoaded(mModel, mType);
-            }
-
-            @Override
-            public void onLoaderReset(Loader<DirectoryResult> loader) {
-                mModel.update(null);
-            }
-        };
-
         // Kick off loader at least once
-        getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
+        getLoaderManager().restartLoader(LOADER_ID, null, this);
     }
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
-        State state = getDisplayState();
-        if (mSelectionManager.hasSelection()) {
-            mSelectionManager.getSelection(state.selectedDocuments);
-            state.selectedDocuments.setDirectoryKey(mStateKey);
-            if (!state.selectedDocuments.isEmpty()) {
-                if (DEBUG) Log.d(TAG, "Persisted selection: " + state.selectedDocuments);
-            }
-        }
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(Shared.EXTRA_TYPE, mType);
+        outState.putParcelable(Shared.EXTRA_ROOT, mRoot);
+        outState.putParcelable(Shared.EXTRA_DOC, mDocument);
+        outState.putString(Shared.EXTRA_QUERY, mQuery);
+        outState.putParcelable(Shared.EXTRA_SELECTION, mSelectionManager.getSelection());
+        outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
     }
 
     @Override
@@ -449,7 +388,7 @@
     public void onSortOrderChanged() {
         // Sort order is implemented as a sorting wrapper around directory
         // results. So when sort order changes, we force a reload of the directory.
-        getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
+        getLoaderManager().restartLoader(LOADER_ID, null, this);
     }
 
     public void onViewModeChanged() {
@@ -552,7 +491,8 @@
             // triggered on "silent" selection updates (i.e. we might be reacting to unfinalized
             // selection changes here)
             final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-            if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
+            if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0
+                    && (docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
                 mNoDeleteCount += selected ? 1 : -1;
             }
             if ((docFlags & Document.FLAG_SUPPORTS_RENAME) != 0) {
@@ -1342,7 +1282,7 @@
             mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
 
             if (model.isEmpty()) {
-                if (getDisplayState().currentSearch != null) {
+                if (mSearchMode) {
                     showNoResults(getDisplayState().stack.root);
                 } else {
                     showEmptyDirectory();
@@ -1468,32 +1408,50 @@
 
     public static void showDirectory(
             FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
-        show(fm, TYPE_NORMAL, root, doc, null, anim);
-    }
-
-    public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
-        show(fm, TYPE_SEARCH, root, null, query, anim);
+        create(fm, TYPE_NORMAL, root, doc, null, anim);
     }
 
     public static void showRecentsOpen(FragmentManager fm, int anim) {
-        show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
+        create(fm, TYPE_RECENT_OPEN, null, null, null, anim);
     }
 
-    private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
+    public static void reloadSearch(FragmentManager fm, RootInfo root, DocumentInfo doc,
+            String query) {
+        DirectoryFragment df = get(fm);
+
+        df.mQuery = query;
+        df.mRoot = root;
+        df.mDocument = doc;
+        df.mSearchMode =  query != null;
+        df.getLoaderManager().restartLoader(LOADER_ID, null, df);
+    }
+
+    public static void reload(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
+            String query) {
+        DirectoryFragment df = get(fm);
+        df.mType = type;
+        df.mQuery = query;
+        df.mRoot = root;
+        df.mDocument = doc;
+        df.mSearchMode =  query != null;
+        df.getLoaderManager().restartLoader(LOADER_ID, null, df);
+    }
+
+    public static void create(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
             String query, int anim) {
         final Bundle args = new Bundle();
-        args.putInt(EXTRA_TYPE, type);
-        args.putParcelable(EXTRA_ROOT, root);
-        args.putParcelable(EXTRA_DOC, doc);
-        args.putString(EXTRA_QUERY, query);
+        args.putInt(Shared.EXTRA_TYPE, type);
+        args.putParcelable(Shared.EXTRA_ROOT, root);
+        args.putParcelable(Shared.EXTRA_DOC, doc);
+        args.putString(Shared.EXTRA_QUERY, query);
 
         final FragmentTransaction ft = fm.beginTransaction();
         switch (anim) {
             case ANIM_SIDE:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                args.putBoolean(Shared.EXTRA_IGNORE_STATE, true);
                 break;
             case ANIM_ENTER:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                args.putBoolean(Shared.EXTRA_IGNORE_STATE, true);
                 ft.setCustomAnimations(R.animator.dir_enter, R.animator.dir_frozen);
                 break;
             case ANIM_LEAVE:
@@ -1504,7 +1462,7 @@
         final DirectoryFragment fragment = new DirectoryFragment();
         fragment.setArguments(args);
 
-        ft.replace(R.id.container_directory, fragment);
+        ft.replace(getFragmentId(), fragment);
         ft.commitAllowingStateLoss();
     }
 
@@ -1518,9 +1476,78 @@
 
     public static @Nullable DirectoryFragment get(FragmentManager fm) {
         // TODO: deal with multiple directories shown at once
-        Fragment fragment = fm.findFragmentById(R.id.container_directory);
+        Fragment fragment = fm.findFragmentById(getFragmentId());
         return fragment instanceof DirectoryFragment
                 ? (DirectoryFragment) fragment
                 : null;
     }
-}
+
+    private static int getFragmentId() {
+        return R.id.container_directory;
+    }
+
+    @Override
+    public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
+        Context context = getActivity();
+        State state = getDisplayState();
+
+        Uri contentsUri;
+        switch (mType) {
+            case TYPE_NORMAL:
+                contentsUri = mSearchMode ? DocumentsContract.buildSearchDocumentsUri(
+                        mRoot.authority, mRoot.rootId, mQuery)
+                        : DocumentsContract.buildChildDocumentsUri(
+                                mDocument.authority, mDocument.documentId);
+                if (state.action == ACTION_MANAGE) {
+                    contentsUri = DocumentsContract.setManageMode(contentsUri);
+                }
+                return new DirectoryLoader(
+                        context, mType, mRoot, mDocument, contentsUri, state.userSortOrder,
+                        mSearchMode);
+            case TYPE_RECENT_OPEN:
+                final RootsCache roots = DocumentsApplication.getRootsCache(context);
+                return new RecentsLoader(context, roots, state);
+            default:
+                throw new IllegalStateException("Unknown type " + mType);
+        }
+    }
+
+    @Override
+    public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
+        if (!isAdded()) return;
+
+        State state = getDisplayState();
+
+        mAdapter.notifyDataSetChanged();
+        mModel.update(result);
+
+        state.derivedSortOrder = result.sortOrder;
+
+        updateLayout(state.derivedMode);
+
+        if (mSelection != null) {
+            mSelectionManager.setItemsSelected(mSelection.toList(), true);
+        }
+
+        // Restore any previous instance state
+        final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
+        if (container != null && !getArguments().getBoolean(Shared.EXTRA_IGNORE_STATE, false)) {
+            getView().restoreHierarchyState(container);
+        } else if (mLastSortOrder != state.derivedSortOrder) {
+            // The derived sort order takes the user sort order into account, but applies
+            // directory-specific defaults when the user doesn't explicitly set the sort
+            // order. Scroll to the top if the sort order actually changed.
+            mRecView.smoothScrollToPosition(0);
+        }
+
+        mLastSortOrder = state.derivedSortOrder;
+
+        mTuner.onModelLoaded(mModel, mType, mSearchMode);
+
+    }
+
+    @Override
+    public void onLoaderReset(Loader<DirectoryResult> loader) {
+        mModel.update(null);
+    }
+  }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index a9b0fd1..59efed6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -26,7 +26,6 @@
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.content.Context;
-import android.os.SystemProperties;
 import android.provider.DocumentsContract.Document;
 import android.util.Log;
 import android.view.Menu;
@@ -66,8 +65,8 @@
     }
 
 
-    public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete,
-            boolean canRename);
+    public abstract void updateActionMenu(
+            Menu menu, @ResultType int dirType, boolean canDelete, boolean canRename);
 
     // 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
@@ -84,7 +83,7 @@
         return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
     }
 
-    abstract void onModelLoaded(Model model, @ResultType int resultType);
+    abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch);
 
     /**
      * Provides support for Platform specific specializations of DirectoryFragment.
@@ -140,33 +139,22 @@
         }
 
         @Override
-        public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
-                boolean canRename) {
+        public void updateActionMenu(
+                Menu menu, @ResultType int dirType, boolean canDelete, boolean canRename) {
 
-            boolean copyEnabled = dirType != DirectoryFragment.TYPE_RECENT_OPEN;
-            boolean moveEnabled =
-                    SystemProperties.getBoolean("debug.documentsui.enable_move", false);
-            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
-
-            final MenuItem open = menu.findItem(R.id.menu_open);
-            final MenuItem share = menu.findItem(R.id.menu_share);
-            final MenuItem delete = menu.findItem(R.id.menu_delete);
-            final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
-            final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
-            final MenuItem rename = menu.findItem(R.id.menu_rename);
+            MenuItem open = menu.findItem(R.id.menu_open);
+            MenuItem share = menu.findItem(R.id.menu_share);
+            MenuItem delete = menu.findItem(R.id.menu_delete);
+            MenuItem rename = menu.findItem(R.id.menu_rename);
 
             open.setVisible(true);
             share.setVisible(false);
             delete.setVisible(false);
-            copyTo.setVisible(copyEnabled);
-            copyTo.setEnabled(copyEnabled);
-            moveTo.setVisible(moveEnabled);
-            moveTo.setEnabled(moveEnabled);
             rename.setVisible(false);
         }
 
         @Override
-        void onModelLoaded(Model model, @ResultType int resultType) {
+        void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
             // When launched into empty recents, show drawer
             if (resultType == DirectoryFragment.TYPE_RECENT_OPEN
                     && model.isEmpty()
@@ -186,32 +174,29 @@
         }
 
         @Override
-        public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
-                boolean canRename) {
-            checkArgument(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+        public void updateActionMenu(
+                Menu menu, @ResultType int resultType, boolean canDelete, boolean canRename) {
+            checkArgument(resultType != DirectoryFragment.TYPE_RECENT_OPEN);
 
-            boolean moveEnabled =
-                    SystemProperties.getBoolean("debug.documentsui.enable_move", false);
-            menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(true);
-
-            final MenuItem open = menu.findItem(R.id.menu_open);
-            final MenuItem share = menu.findItem(R.id.menu_share);
-            final MenuItem delete = menu.findItem(R.id.menu_delete);
-            final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
-            final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
-            final MenuItem rename = menu.findItem(R.id.menu_rename);
+            MenuItem open = menu.findItem(R.id.menu_open);
+            MenuItem delete = menu.findItem(R.id.menu_delete);
+            MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+            MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+            MenuItem rename = menu.findItem(R.id.menu_rename);
+            MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
 
             open.setVisible(false);
             delete.setVisible(canDelete);
+            copy.setEnabled(true);  // to clipboard
             copyTo.setVisible(true);
             copyTo.setEnabled(true);
-            moveTo.setVisible(moveEnabled);
-            moveTo.setEnabled(moveEnabled);
+            moveTo.setVisible(true);
+            moveTo.setEnabled(true);
             rename.setVisible(false);
         }
 
         @Override
-        void onModelLoaded(Model model, @ResultType int resultType) {}
+        void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {}
     }
 
     /**
@@ -226,8 +211,8 @@
         }
 
         @Override
-        public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
-                boolean canRename) {
+        public void updateActionMenu(
+                Menu menu, @ResultType int dirType, boolean canDelete, boolean canRename) {
 
             MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
             MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
@@ -239,20 +224,19 @@
 
             menu.findItem(R.id.menu_share).setVisible(true);
             menu.findItem(R.id.menu_delete).setVisible(canDelete);
-
             menu.findItem(R.id.menu_open).setVisible(false);
             menu.findItem(R.id.menu_copy_to).setVisible(true);
             menu.findItem(R.id.menu_move_to).setVisible(true);
+            menu.findItem(R.id.menu_move_to).setEnabled(canDelete);
 
             Menus.disableHiddenItems(menu, copy, paste);
         }
 
         @Override
-        void onModelLoaded(Model model, @ResultType int resultType) {
+        void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
             if (DEBUG) Log.d(TAG, "Handling model loaded. Has Location shcnage: " + mState.initialLocationHasChanged());
             // When launched into empty root, open drawer.
-            if (model.isEmpty() && !mState.initialLocationHasChanged()
-                    && resultType != DirectoryFragment.TYPE_SEARCH) {
+            if (model.isEmpty() && !mState.initialLocationHasChanged() && !isSearch) {
                 if (DEBUG) Log.d(TAG, "Showing roots drawer cuz stuffs empty.");
 
                 // This noops on layouts without drawer, so no need to guard.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index c8b6f85..4cf1048 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -657,15 +657,22 @@
         // item A is tapped (and selected), then an in-progress band select covers A then uncovers
         // A, A should still be selected as it has been saved. To ensure this behavior, the saved
         // selection must be tracked separately.
-        private Set<String> mSelection = new HashSet<>();
-        private Set<String> mProvisionalSelection = new HashSet<>();
+        private final Set<String> mSelection;
+        private final Set<String> mProvisionalSelection;
         private String mDirectoryKey;
 
-        @VisibleForTesting
-        public Selection(String... ids) {
-            for (int i = 0; i < ids.length; i++) {
-                add(ids[i]);
-            }
+        public Selection() {
+            mSelection = new HashSet<String>();
+            mProvisionalSelection = new HashSet<String>();
+        }
+
+        /**
+         * Used by CREATOR.
+         */
+        private Selection(String directoryKey, List<String> selection) {
+            mDirectoryKey = directoryKey;
+            mSelection = new HashSet<String>(selection);
+            mProvisionalSelection = new HashSet<String>();
         }
 
         /**
@@ -687,7 +694,7 @@
          * Returns an unordered array of selected positions (including any
          * provisional selections current in effect).
          */
-        private List<String> toList() {
+        public List<String> toList() {
             ArrayList<String> selection = new ArrayList<String>(mSelection);
             selection.addAll(mProvisionalSelection);
             return selection;
@@ -810,8 +817,11 @@
 
         @VisibleForTesting
         void copyFrom(Selection source) {
-            mSelection = new HashSet<>(source.mSelection);
-            mProvisionalSelection = new HashSet<>(source.mProvisionalSelection);
+            mSelection.clear();
+            mSelection.addAll(source.mSelection);
+
+            mProvisionalSelection.clear();
+            mProvisionalSelection.addAll(source.mProvisionalSelection);
         }
 
         @Override
@@ -878,6 +888,26 @@
             // We don't include provisional selection since it is
             // typically coupled to some other runtime state (like a band).
         }
+
+        public static final ClassLoaderCreator<Selection> CREATOR =
+                new ClassLoaderCreator<Selection>() {
+            @Override
+            public Selection createFromParcel(Parcel in) {
+                return createFromParcel(in, null);
+            }
+
+            @Override
+            public Selection createFromParcel(Parcel in, ClassLoader loader) {
+                return new Selection(
+                        in.readString(),
+                        (ArrayList<String>) in.readArrayList(loader));
+            }
+
+            @Override
+            public Selection[] newArray(int size) {
+                return new Selection[size];
+            }
+        };
     }
 
     /**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
index 7394c12..2957bf0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
@@ -46,6 +46,7 @@
 import com.android.documentsui.BaseActivity;
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.R;
+import com.android.documentsui.Shared;
 import com.android.documentsui.Snackbars;
 import com.android.documentsui.model.DocumentInfo;
 
@@ -55,6 +56,7 @@
 public class RenameDocumentFragment extends DialogFragment {
     private static final String TAG_RENAME_DOCUMENT = "rename_document";
     private DocumentInfo mDocument;
+    private EditText mEditText;
 
     public static void show(FragmentManager fm, DocumentInfo document) {
         final RenameDocumentFragment dialog = new RenameDocumentFragment();
@@ -62,6 +64,11 @@
         dialog.show(fm, TAG_RENAME_DOCUMENT);
     }
 
+    /**
+     * Creates the dialog UI.
+     * @param savedInstanceState
+     * @return
+     */
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         Context context = getActivity();
@@ -69,8 +76,7 @@
         LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
         View view = dialogInflater.inflate(R.layout.dialog_file_name, null, false);
 
-        final EditText editText = (EditText) view.findViewById(android.R.id.text1);
-        fillWithFileName(editText, mDocument.displayName);
+        mEditText = (EditText) view.findViewById(android.R.id.text1);
         builder.setTitle(R.string.menu_rename);
         builder.setView(view);
 
@@ -79,7 +85,7 @@
                 new OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
-                        renameDocuments(editText.getText().toString());
+                        renameDocuments(mEditText.getText().toString());
                     }
                 });
 
@@ -87,7 +93,7 @@
 
         final AlertDialog dialog = builder.create();
 
-        editText.setOnEditorActionListener(
+        mEditText.setOnEditorActionListener(
                 new OnEditorActionListener() {
                     @Override
                     public boolean onEditorAction(
@@ -95,18 +101,51 @@
                         if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null
                                 && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                                 && event.hasNoModifiers())) {
-                            renameDocuments(editText.getText().toString());
+                            renameDocuments(mEditText.getText().toString());
                             dialog.dismiss();
                             return true;
                         }
                         return false;
                     }
                 });
-
         return dialog;
     }
 
     /**
+     * Sets/Restores the data.
+     * @param savedInstanceState
+     * @return
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        if(savedInstanceState == null) {
+            // Fragment created for the first time, we set the text.
+            // mDocument value was set in show
+            mEditText.setText(mDocument.displayName);
+        }
+        else {
+            // Fragment restored, text was restored automatically.
+            // mDocument value needs to be restored.
+            mDocument = savedInstanceState.getParcelable(Shared.EXTRA_DOC);
+        }
+        // Do selection in both cases, because we cleared it.
+        selectFileName(mEditText);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        // Clear selection before storing state and restore it manually,
+        // because otherwise after rotation selection is displayed with cut/copy menu visible :/
+        clearFileNameSelection(mEditText);
+
+        super.onSaveInstanceState(outState);
+
+        outState.putParcelable(Shared.EXTRA_DOC, mDocument);
+    }
+
+    /**
      * Validates if string is a proper document name.
      * Checks if string is not empty. More rules might be added later.
      * @param docName string representing document name
@@ -120,12 +159,20 @@
      * Fills text field with the file name and selects the name without extension.
      *
      * @param editText text field to be filled
-     * @param name full name of the file
      */
-    private void fillWithFileName(EditText editText, String name) {
-        editText.setText(name);
-        int separatorIndex = name.indexOf(".");
-        editText.setSelection(0, separatorIndex == -1 ? name.length() : separatorIndex);
+    private void selectFileName(EditText editText) {
+        String text = editText.getText().toString();
+        int separatorIndex = text.indexOf(".");
+        editText.setSelection(0, separatorIndex == -1 ? text.length() : separatorIndex);
+    }
+
+    /**
+     * Clears selection in text field.
+     *
+     * @param editText text field to be cleared.
+     */
+    private void clearFileNameSelection(EditText editText) {
+        editText.setSelection(0, 0);
     }
 
     private void renameDocuments(String newDisplayName) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index e9fdab0..d74121e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -207,7 +207,13 @@
         return "Document{"
                 + "docId=" + documentId
                 + ", name=" + displayName
+                + ", isContainer=" + isContainer()
                 + ", isDirectory=" + isDirectory()
+                + ", isArchive=" + isArchive()
+                + ", isVirtualDocument=" + isVirtualDocument()
+                + ", isDeleteSupported=" + isDeleteSupported()
+                + ", isCreateSupported=" + isCreateSupported()
+                + ", isRenameSupported=" + isRenameSupported()
                 + "}";
     }
 
@@ -231,6 +237,10 @@
         return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
     }
 
+    public boolean isRemoveSupported() {
+        return (flags & Document.FLAG_SUPPORTS_REMOVE) != 0;
+    }
+
     public boolean isRenameSupported() {
         return (flags & Document.FLAG_SUPPORTS_RENAME) != 0;
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index dad8697..34a35ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -129,10 +129,19 @@
     }
 
     Notification getProgressNotification(@StringRes int msgId) {
-        double completed = (double) this.mBytesCopied / mBatchSize;
-        mProgressBuilder.setProgress(100, (int) (completed * 100), false);
-        mProgressBuilder.setContentInfo(
-                NumberFormat.getPercentInstance().format(completed));
+        if (mBatchSize >= 0) {
+            double completed = (double) this.mBytesCopied / mBatchSize;
+            mProgressBuilder.setProgress(100, (int) (completed * 100), false);
+            mProgressBuilder.setContentInfo(
+                    NumberFormat.getPercentInstance().format(completed));
+        } else {
+            // If the total file size failed to compute on some files, then show
+            // an indeterminate spinner. CopyJob would most likely fail on those
+            // files while copying, but would continue with another files.
+            // Also, if the total size is 0 bytes, show an indeterminate spinner.
+            mProgressBuilder.setProgress(0, 0, true);
+        }
+
         if (mRemainingTime > 0) {
             mProgressBuilder.setContentText(service.getString(msgId,
                     DateUtils.formatDuration(mRemainingTime)));
@@ -208,11 +217,15 @@
     }
 
     @Override
-    void start() throws RemoteException {
+    void start() {
         mStartTime = elapsedRealtime();
 
-        // client
-        mBatchSize = calculateSize(mSrcs);
+        try {
+            mBatchSize = calculateSize(mSrcs);
+        } catch (ResourceException e) {
+            Log.w(TAG, "Failed to calculate total size. Copying without progress.");
+            mBatchSize = -1;
+        }
 
         DocumentInfo srcInfo;
         DocumentInfo dstInfo = stack.peek();
@@ -220,9 +233,13 @@
             srcInfo = mSrcs.get(i);
 
             // Guard unsupported recursive operation.
-            if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
-                onFileFailed(srcInfo,
-                        "Skipping recursive operation on directory " + dstInfo.derivedUri + ".");
+            try {
+                if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
+                    throw new ResourceException("Cannot copy to itself recursively.");
+                }
+            } catch (ResourceException e) {
+                Log.e(TAG, e.toString());
+                onFileFailed(srcInfo);
                 continue;
             }
 
@@ -230,7 +247,12 @@
                     "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")"
                     + " to " + dstInfo.displayName + " (" + dstInfo.derivedUri + ")");
 
-            processDocument(srcInfo, null, dstInfo);
+            try {
+                processDocument(srcInfo, null, dstInfo);
+            } catch (ResourceException e) {
+                Log.e(TAG, e.toString());
+                onFileFailed(srcInfo);
+            }
         }
         Metrics.logFileOperation(service, operationType, mSrcs, dstInfo);
     }
@@ -259,13 +281,12 @@
      * @param src DocumentInfos for the documents to copy.
      * @param srcParent DocumentInfo for the parent of the document to process.
      * @param dstDirInfo The destination directory.
-     * @return True on success, false on failure.
-     * @throws RemoteException
+     * @throws ResourceException
      *
      * TODO: Stop passing srcParent, as it's not used for copy, but for move only.
      */
-    boolean processDocument(DocumentInfo src, DocumentInfo srcParent,
-            DocumentInfo dstDirInfo) throws RemoteException {
+    void processDocument(DocumentInfo src, DocumentInfo srcParent,
+            DocumentInfo dstDirInfo) throws ResourceException {
 
         // TODO: When optimized copy kicks in, we'll not making any progress updates.
         // For now. Local storage isn't using optimized copy.
@@ -274,22 +295,28 @@
         // If not supported, then fallback to byte-by-byte copy/move.
         if (src.authority.equals(dstDirInfo.authority)) {
             if ((src.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
-                if (DocumentsContract.copyDocument(getClient(src), src.derivedUri,
-                        dstDirInfo.derivedUri) == null) {
-                    onFileFailed(src,
-                            "Provider side copy failed for documents: " + src.derivedUri + ".");
-                    return false;
+                try {
+                    if (DocumentsContract.copyDocument(getClient(src), src.derivedUri,
+                            dstDirInfo.derivedUri) == null) {
+                        throw new ResourceException("Provider side copy failed for document %s.",
+                                src.derivedUri);
+                    }
+                } catch (ResourceException e) {
+                    throw e;
+                } catch (RemoteException | RuntimeException e) {
+                    throw new ResourceException(
+                            "Provider side copy failed for document %s due to an exception.",
+                            src.derivedUri, e);
                 }
-                return true;
+                return;
             }
         }
 
         // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
-        return byteCopyDocument(src, dstDirInfo);
+        byteCopyDocument(src, dstDirInfo);
     }
 
-    boolean byteCopyDocument(DocumentInfo src, DocumentInfo dest)
-            throws RemoteException {
+    void byteCopyDocument(DocumentInfo src, DocumentInfo dest) throws ResourceException {
         final String dstMimeType;
         final String dstDisplayName;
 
@@ -297,8 +324,14 @@
         // If the file is virtual, but can be converted to another format, then try to copy it
         // as such format. Also, append an extension for the target mime type (if known).
         if (src.isVirtualDocument()) {
-            final String[] streamTypes = getContentResolver().getStreamTypes(
-                    src.derivedUri, "*/*");
+            String[] streamTypes = null;
+            try {
+                streamTypes = getContentResolver().getStreamTypes(src.derivedUri, "*/*");
+            } catch (RuntimeException e) {
+                throw new ResourceException(
+                        "Failed to obtain streamable types for %s due to an exception.",
+                        src.derivedUri, e);
+            }
             if (streamTypes != null && streamTypes.length > 0) {
                 dstMimeType = streamTypes[0];
                 final String extension = MimeTypeMap.getSingleton().
@@ -306,8 +339,8 @@
                 dstDisplayName = src.displayName +
                         (extension != null ? "." + extension : src.displayName);
             } else {
-                onFileFailed(src, "Cannot copy virtual file. No streamable formats available.");
-                return false;
+                throw new ResourceException("Cannot copy virtual file %s. No streamable formats "
+                        + "available.", src.derivedUri);
             }
         } else {
             dstMimeType = src.mimeType;
@@ -316,33 +349,35 @@
 
         // Create the target document (either a file or a directory), then copy recursively the
         // contents (bytes or children).
-        final Uri dstUri = DocumentsContract.createDocument(
-                getClient(dest), dest.derivedUri, dstMimeType, dstDisplayName);
+        Uri dstUri = null;
+        try {
+            dstUri = DocumentsContract.createDocument(
+                    getClient(dest), dest.derivedUri, dstMimeType, dstDisplayName);
+        } catch (RemoteException | RuntimeException e) {
+            throw new ResourceException(
+                    "Couldn't create destination document " + dstDisplayName + " in directory %s "
+                    + "due to an exception.", dest.derivedUri, e);
+        }
         if (dstUri == null) {
             // If this is a directory, the entire subdir will not be copied over.
-            onFileFailed(src,
-                    "Couldn't create destination document " + dstDisplayName
-                    + " in directory " + dest.displayName + ".");
-            return false;
+            throw new ResourceException(
+                    "Couldn't create destination document " + dstDisplayName + " in directory %s.",
+                    dest.derivedUri);
         }
 
         DocumentInfo dstInfo = null;
         try {
             dstInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
-        } catch (FileNotFoundException e) {
-            onFileFailed(src,
-                    "Could not load DocumentInfo for newly created file: " + dstUri + ".");
-            return false;
+        } catch (FileNotFoundException | RuntimeException e) {
+            throw new ResourceException("Could not load DocumentInfo for newly created file %s.",
+                    dstUri);
         }
 
-        final boolean success;
         if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
-            success = copyDirectoryHelper(src, dstInfo);
+            copyDirectoryHelper(src, dstInfo);
         } else {
-            success = copyFileHelper(src, dstInfo, dstMimeType);
+            copyFileHelper(src, dstInfo, dstMimeType);
         }
-
-        return success;
     }
 
     /**
@@ -352,11 +387,10 @@
      * @param srcDir Info of the directory to copy from. The routine will copy the directory's
      *            contents, not the directory itself.
      * @param destDir Info of the directory to copy to. Must be created beforehand.
-     * @return True on success, false if some of the children failed to copy.
-     * @throws RemoteException
+     * @throws ResourceException
      */
-    private boolean copyDirectoryHelper(DocumentInfo srcDir, DocumentInfo destDir)
-            throws RemoteException {
+    private void copyDirectoryHelper(DocumentInfo srcDir, DocumentInfo destDir)
+            throws ResourceException {
         // Recurse into directories. Copy children into the new subdirectory.
         final String queryColumns[] = new String[] {
                 Document.COLUMN_DISPLAY_NAME,
@@ -367,19 +401,39 @@
         };
         Cursor cursor = null;
         boolean success = true;
+        // Iterate over srcs in the directory; copy to the destination directory.
+        final Uri queryUri = buildChildDocumentsUri(srcDir.authority, srcDir.documentId);
         try {
-            // Iterate over srcs in the directory; copy to the destination directory.
-            final Uri queryUri = buildChildDocumentsUri(srcDir.authority, srcDir.documentId);
-            cursor = getClient(srcDir).query(queryUri, queryColumns, null, null, null);
-            while (cursor.moveToNext() && !isCanceled()) {
-                DocumentInfo src = DocumentInfo.fromCursor(cursor, srcDir.authority);
-                success &= processDocument(src, srcDir, destDir);
+            try {
+                cursor = getClient(srcDir).query(queryUri, queryColumns, null, null, null);
+            } catch (RemoteException | RuntimeException e) {
+                throw new ResourceException("Failed to query children of %s due to an exception.",
+                        srcDir.derivedUri, e);
             }
+
+            DocumentInfo src;
+            while (cursor.moveToNext() && !isCanceled()) {
+                try {
+                    src = DocumentInfo.fromCursor(cursor, srcDir.authority);
+                    processDocument(src, srcDir, destDir);
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "Failed to recursively process a file %s due to an exception."
+                            .format(srcDir.derivedUri.toString()), e);
+                    success = false;
+                }
+            }
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to copy a file %s to %s. "
+                    .format(srcDir.derivedUri.toString(), destDir.derivedUri.toString()), e);
+            success = false;
         } finally {
             IoUtils.closeQuietly(cursor);
         }
 
-        return success;
+        if (!success) {
+            throw new RuntimeException("Some files failed to copy during a recursive "
+                    + "directory copy.");
+        }
     }
 
     /**
@@ -388,85 +442,101 @@
      * @param srcUriInfo Info of the file to copy from.
      * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand.
      * @param mimeType Mime type for the target. Can be different than source for virtual files.
-     * @return True on success, false on error.
-     * @throws RemoteException
+     * @throws ResourceException
      */
-    private boolean copyFileHelper(DocumentInfo src, DocumentInfo dest, String mimeType)
-            throws RemoteException {
+    private void copyFileHelper(DocumentInfo src, DocumentInfo dest, String mimeType)
+            throws ResourceException {
         CancellationSignal canceller = new CancellationSignal();
+        AssetFileDescriptor srcFileAsAsset = null;
         ParcelFileDescriptor srcFile = null;
         ParcelFileDescriptor dstFile = null;
         InputStream in = null;
         OutputStream out = null;
+        boolean success = false;
 
-        boolean success = true;
         try {
             // If the file is virtual, but can be converted to another format, then try to copy it
             // as such format.
             if (src.isVirtualDocument()) {
-                final AssetFileDescriptor srcFileAsAsset =
-                        getClient(src).openTypedAssetFileDescriptor(
+                try {
+                    srcFileAsAsset = getClient(src).openTypedAssetFileDescriptor(
                                 src.derivedUri, mimeType, null, canceller);
+                } catch (FileNotFoundException | RemoteException | RuntimeException e) {
+                    throw new ResourceException("Failed to open a file as asset for %s due to an "
+                            + "exception.", src.derivedUri, e);
+                }
                 srcFile = srcFileAsAsset.getParcelFileDescriptor();
-                in = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
+                try {
+                    in = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
+                } catch (IOException e) {
+                    throw new ResourceException("Failed to open a file input stream for %s due "
+                            + "an exception.", src.derivedUri, e);
+                }
             } else {
-                srcFile = getClient(src).openFile(src.derivedUri, "r", canceller);
+                try {
+                    srcFile = getClient(src).openFile(src.derivedUri, "r", canceller);
+                } catch (FileNotFoundException | RemoteException | RuntimeException e) {
+                    throw new ResourceException(
+                            "Failed to open a file for %s due to an exception.", src.derivedUri, e);
+                }
                 in = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
             }
 
-            dstFile = getClient(dest).openFile(dest.derivedUri, "w", canceller);
+            try {
+                dstFile = getClient(dest).openFile(dest.derivedUri, "w", canceller);
+            } catch (FileNotFoundException | RemoteException | RuntimeException e) {
+                throw new ResourceException("Failed to open the destination file %s for writing "
+                        + "due to an exception.", dest.derivedUri, e);
+            }
             out = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
 
             byte[] buffer = new byte[32 * 1024];
             int len;
-            while ((len = in.read(buffer)) != -1) {
-                if (isCanceled()) {
-                    if (DEBUG) Log.d(TAG, "Canceled copy mid-copy. Id:" + id);
-                    success = false;
-                    break;
+            try {
+                while ((len = in.read(buffer)) != -1) {
+                    if (isCanceled()) {
+                        throw new ResourceException("Canceled copy mid-copy of %s",
+                                src.derivedUri);
+                    }
+                    out.write(buffer, 0, len);
+                    makeCopyProgress(len);
                 }
-                out.write(buffer, 0, len);
-                makeCopyProgress(len);
+
+                srcFile.checkError();
+            } catch (IOException e) {
+                throw new ResourceException(
+                        "Failed to copy bytes from %s to %s due to an IO exception.",
+                        src.derivedUri, dest.derivedUri, e);
             }
 
-            srcFile.checkError();
-        } catch (IOException e) {
-            success = false;
-            onFileFailed(src, "Exception thrown while copying from "
-                    + src.derivedUri + " to " + dest.derivedUri + ".");
-
-            if (dstFile != null) {
-                try {
-                    dstFile.closeWithError(e.getMessage());
-                } catch (IOException closeError) {
-                    Log.e(TAG, "Error closing destination", closeError);
-                }
+            if (src.isVirtualDocument()) {
+               convertedFiles.add(src);
             }
+
+            success = true;
         } finally {
+            if (!success) {
+                if (dstFile != null) {
+                    try {
+                        dstFile.closeWithError("Error copying bytes.");
+                    } catch (IOException closeError) {
+                        Log.w(TAG, "Error closing destination.", closeError);
+                    }
+                }
+
+                if (DEBUG) Log.d(TAG, "Cleaning up failed operation leftovers.");
+                canceller.cancel();
+                try {
+                    DocumentsContract.deleteDocument(getClient(dest), dest.derivedUri);
+                } catch (RemoteException | RuntimeException e) {
+                    Log.w(TAG, "Failed to cleanup after copy error: " + src.derivedUri, e);
+                }
+            }
+
             // This also ensures the file descriptors are closed.
             IoUtils.closeQuietly(in);
             IoUtils.closeQuietly(out);
         }
-
-        if (!success) {
-            if (DEBUG) Log.d(TAG, "Cleaning up failed operation leftovers.");
-            canceller.cancel();
-            try {
-                DocumentsContract.deleteDocument(getClient(dest), dest.derivedUri);
-            } catch (RemoteException e) {
-                // RemoteExceptions usually signal that the connection is dead, so there's no
-                // point attempting to continue. Propagate the exception up so the copy job is
-                // cancelled.
-                Log.w(TAG, "Failed to cleanup after copy error: " + src.derivedUri, e);
-                throw e;
-            }
-        }
-
-        if (src.isVirtualDocument() && success) {
-           convertedFiles.add(src);
-        }
-
-        return success;
     }
 
     /**
@@ -475,16 +545,20 @@
      *
      * @param srcs
      * @return Size in bytes.
-     * @throws RemoteException
+     * @throws ResourceException
      */
-    private long calculateSize(List<DocumentInfo> srcs)
-            throws RemoteException {
+    private long calculateSize(List<DocumentInfo> srcs) throws ResourceException {
         long result = 0;
 
         for (DocumentInfo src : srcs) {
             if (src.isDirectory()) {
                 // Directories need to be recursed into.
-                result += calculateFileSizesRecursively(getClient(src), src.derivedUri);
+                try {
+                    result += calculateFileSizesRecursively(getClient(src), src.derivedUri);
+                } catch (RemoteException e) {
+                    throw new ResourceException("Failed to obtain the client for %s.",
+                            src.derivedUri);
+                }
             } else {
                 result += src.size;
             }
@@ -495,10 +569,10 @@
     /**
      * Calculates (recursively) the cumulative size of all the files under the given directory.
      *
-     * @throws RemoteException
+     * @throws ResourceException
      */
     private static long calculateFileSizesRecursively(
-            ContentProviderClient client, Uri uri) throws RemoteException {
+            ContentProviderClient client, Uri uri) throws ResourceException {
         final String authority = uri.getAuthority();
         final Uri queryUri = buildChildDocumentsUri(authority, getDocumentId(uri));
         final String queryColumns[] = new String[] {
@@ -524,6 +598,9 @@
                     result += size > 0 ? size : 0;
                 }
             }
+        } catch (RemoteException | RuntimeException e) {
+            throw new ResourceException(
+                    "Failed to calculate size for %s due to an exception.", uri, e);
         } finally {
             IoUtils.closeQuietly(cursor);
         }
@@ -533,21 +610,22 @@
 
     /**
      * Returns true if {@code doc} is a descendant of {@code parentDoc}.
-     * @throws RemoteException
+     * @throws ResourceException
      */
     boolean isDescendentOf(DocumentInfo doc, DocumentInfo parent)
-            throws RemoteException {
+            throws ResourceException {
         if (parent.isDirectory() && doc.authority.equals(parent.authority)) {
-            return isChildDocument(getClient(doc), doc.derivedUri, parent.derivedUri);
+            try {
+                return isChildDocument(getClient(doc), doc.derivedUri, parent.derivedUri);
+            } catch (RemoteException | RuntimeException e) {
+                throw new ResourceException(
+                        "Failed to check if %s is a child of %s due to an exception.",
+                        doc.derivedUri, parent.derivedUri, e);
+            }
         }
         return false;
     }
 
-    private void onFileFailed(DocumentInfo file, String msg) {
-        Log.w(TAG, msg);
-        onFileFailed(file);
-    }
-
     @Override
     public String toString() {
         return new StringBuilder()
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index 11c3a29..374d27b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -22,7 +22,6 @@
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.content.Context;
-import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.documentsui.Metrics;
@@ -81,13 +80,13 @@
     }
 
     @Override
-    void start() throws RemoteException {
+    void start() {
         for (DocumentInfo doc : mSrcs) {
             if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri);
-            // TODO: Start using mSrcParent as soon as DocumentsProvider::removeDocument() is
-            // implemented.
-            if (!deleteDocument(doc)) {
-                Log.w(TAG, "Failed to delete document @ " + doc.derivedUri);
+            try {
+                deleteDocument(doc);
+            } catch (ResourceException e) {
+                Log.e(TAG, "Failed to delete document @ " + doc.derivedUri);
                 onFileFailed(doc);
             }
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index 77517ca..572c0d7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -116,19 +116,17 @@
         listener.onStart(this);
         try {
             start();
-        } catch (Exception e) {
-            // In the case of an unmanaged failure, we still want
-            // to resolve business in an orderly fashion. That'll
-            // ensure the service is shut down and notifications
-            // shown/closed.
-            Log.e(TAG, "Operation failed due to an exception.", e);
+        } catch (RuntimeException e) {
+            // No exceptions should be thrown here, as all calls to the provider must be
+            // handled within Job implementations. However, just in case catch them here.
+            Log.e(TAG, "Operation failed due to an unhandled runtime exception.", e);
             Metrics.logFileOperationErrors(service, operationType, failedFiles);
         } finally {
             listener.onFinished(this);
         }
     }
 
-    abstract void start() throws RemoteException;
+    abstract void start();
 
     abstract Notification getSetupNotification();
     // TODO: Progress notification for deletes.
@@ -186,15 +184,13 @@
         return false;
     }
 
-    final boolean deleteDocument(DocumentInfo doc) {
+    final void deleteDocument(DocumentInfo doc) throws ResourceException {
         try {
             DocumentsContract.deleteDocument(getClient(doc), doc.derivedUri);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to delete file: " + doc.derivedUri, e);
-            return false;
+        } catch (Exception e) {
+            throw new ResourceException("Failed to delete file %s due to an exception.",
+                    doc.derivedUri, e);
         }
-
-        return true;  // victory dance!
     }
 
     Notification getSetupNotification(String content) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index 9b72077..8835a9f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -80,8 +80,8 @@
                 R.plurals.move_error_notification_title, R.drawable.ic_menu_copy);
     }
 
-    boolean processDocument(DocumentInfo src, DocumentInfo srcParent, DocumentInfo dest)
-            throws RemoteException {
+    void processDocument(DocumentInfo src, DocumentInfo srcParent, DocumentInfo dest)
+            throws ResourceException {
 
         // TODO: When optimized move kicks in, we're not making any progress updates. FIX IT!
 
@@ -89,13 +89,19 @@
         // If not supported, then fallback to byte-by-byte copy/move.
         if (src.authority.equals(dest.authority)) {
             if ((src.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
-                if (DocumentsContract.moveDocument(getClient(src), src.derivedUri,
-                        srcParent != null ? srcParent.derivedUri : mSrcParent.derivedUri,
-                        dest.derivedUri) == null) {
-                    onFileFailed(src);
-                    return false;
+                try {
+                    if (DocumentsContract.moveDocument(getClient(src), src.derivedUri,
+                            srcParent != null ? srcParent.derivedUri : mSrcParent.derivedUri,
+                            dest.derivedUri) == null) {
+                        throw new ResourceException("Provider side move failed for document %s.",
+                                src.derivedUri);
+                    }
+                } catch (RuntimeException | RemoteException e) {
+                    throw new ResourceException(
+                            "Provider side move failed for document %s due to an exception.",
+                            src.derivedUri, e);
                 }
-                return true;
+                return;
             }
         }
 
@@ -103,16 +109,12 @@
         // conversion, and the source file should not be deleted in such case (as it's a different
         // file).
         if (src.isVirtualDocument()) {
-            Log.w(TAG, "Cannot move virtual files byte by byte.");
-            onFileFailed(src);
-            return false;
+            throw new ResourceException("Cannot move virtual file %s byte by byte.",
+                    src.derivedUri);
         }
 
         // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
-        boolean copied = byteCopyDocument(src, dest);
-
-        // TODO: Replace deleteDocument() with removeDocument() once implemented.
-        return copied && !isCanceled() && deleteDocument(src);
+        byteCopyDocument(src, dest);
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/ResourceException.java b/packages/DocumentsUI/src/com/android/documentsui/services/ResourceException.java
new file mode 100644
index 0000000..9c6b51c
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/ResourceException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.services;
+
+import android.net.Uri;
+
+public class ResourceException extends Exception {
+    public ResourceException(String message, Exception e) {
+        super(message, e);
+    }
+
+    public ResourceException(String message, Uri uri1, Exception e) {
+        super(message.format(uri1.toString()), e);
+    }
+
+    public ResourceException(String message, Uri uri1, Uri uri2, Exception e) {
+        super(message.format(uri1.toString(), uri2.toString()), e);
+    }
+
+    public ResourceException(String message) {
+        super(message);
+    }
+
+    public ResourceException(String message, Uri uri1) {
+        super(message.format(uri1.toString()));
+    }
+
+    public ResourceException(String message, Uri uri1, Uri uri2) {
+        super(message.format(uri1.toString(), uri2.toString()));
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java
index 34f1120..08d366a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java
@@ -21,21 +21,22 @@
 import static com.android.documentsui.StubProvider.ROOT_1_ID;
 
 import android.app.Activity;
-import android.app.Instrumentation;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.RemoteException;
 import android.provider.DocumentsContract.Document;
-import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.Until;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.MotionEvent;
 
+import com.android.documentsui.bots.DirectoryListBot;
+import com.android.documentsui.bots.KeyboardBot;
+import com.android.documentsui.bots.RootsListBot;
+import com.android.documentsui.bots.UiBot;
 import com.android.documentsui.model.RootInfo;
 
 /**
@@ -56,7 +57,7 @@
     public static final String fileName4 = "poodles.text";
     public static final String fileNameNoRename = "NO_RENAMEfile.txt";
 
-    public UiBot bot;
+    public Bots bots;
     public UiDevice device;
     public Context context;
 
@@ -76,10 +77,11 @@
         device = UiDevice.getInstance(getInstrumentation());
         // NOTE: Must be the "target" context, else security checks in content provider will fail.
         context = getInstrumentation().getTargetContext();
-        bot = new UiBot(device, context, TIMEOUT);
+
+        bots = new Bots(device, context, TIMEOUT);
 
         Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
-        bot.revealLauncher();
+        bots.main.revealLauncher();
 
         mResolver = context.getContentResolver();
         mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
@@ -90,7 +92,7 @@
 
         launchActivity();
 
-        bot.revealApp();
+        bots.main.revealApp();
         resetStorage();
     }
 
@@ -125,12 +127,31 @@
     }
 
     void assertDefaultContentOfTestDir0() throws UiObjectNotFoundException {
-        bot.assertDocumentsCount(ROOT_0_ID, 4);
-        bot.assertHasDocuments(fileName1, fileName2, dirName1, fileNameNoRename);
+        bots.roots.openRoot(ROOT_0_ID);
+        bots.directory.assertDocumentsCount(4);
+        bots.directory.assertDocumentsPresent(fileName1, fileName2, dirName1, fileNameNoRename);
     }
 
     void assertDefaultContentOfTestDir1() throws UiObjectNotFoundException {
-        bot.assertDocumentsCount(ROOT_1_ID, 2);
-        bot.assertHasDocuments(fileName3, fileName4);
+        bots.roots.openRoot(ROOT_1_ID);
+        bots.directory.assertDocumentsCount(2);
+        bots.directory.assertDocumentsPresent(fileName3, fileName4);
+    }
+
+    /**
+     * Handy collection of bots for working with Files app.
+     */
+    public static final class Bots {
+        public final UiBot main;
+        public final RootsListBot roots;
+        public final DirectoryListBot directory;
+        public final KeyboardBot keyboard;
+
+        private Bots(UiDevice device, Context context, int timeout) {
+            this.main = new UiBot(device, context, TIMEOUT);
+            this.roots = new RootsListBot(device, context, TIMEOUT);
+            this.directory = new DirectoryListBot(device, context, TIMEOUT);
+            this.keyboard = new KeyboardBot(device, context, TIMEOUT);
+        }
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index c51f927..79d7887 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -52,13 +52,13 @@
     public void testWindowTitle() throws Exception {
         initTestFiles();
 
-        bot.assertWindowTitle(ROOT_0_ID);
+        bots.main.assertWindowTitle(ROOT_0_ID);
     }
 
     public void testFilesListed() throws Exception {
         initTestFiles();
 
-        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+        bots.directory.assertDocumentsPresent("file0.log", "file1.png", "file2.csv");
     }
 
     public void testFilesList_LiveUpdate() throws Exception {
@@ -66,30 +66,31 @@
 
         mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
 
-        bot.waitForDocument("Ham & Cheese.sandwich");
-        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+        bots.directory.waitForDocument("Ham & Cheese.sandwich");
+        bots.directory.assertDocumentsPresent(
+                "file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
     }
 
     public void testDeleteDocument() throws Exception {
         initTestFiles();
 
-        bot.clickDocument("file1.png");
+        bots.directory.clickDocument("file1.png");
         device.waitForIdle();
-        bot.menuDelete().click();
+        bots.main.menuDelete().click();
 
-        bot.waitForDeleteSnackbar();
-        assertFalse(bot.hasDocuments("file1.png"));
+        bots.directory.waitForDeleteSnackbar();
+        bots.directory.assertDocumentsAbsent("file1.png");
 
-        bot.waitForDeleteSnackbarGone();
-        assertFalse(bot.hasDocuments("file1.png"));
+        bots.directory.waitForDeleteSnackbarGone();
+        bots.directory.assertDocumentsAbsent("file1.png");
     }
 
     public void testSupportsShare() throws Exception {
         initTestFiles();
 
-        bot.clickDocument("file1.png");
+        bots.directory.clickDocument("file1.png");
         device.waitForIdle();
-        assertNotNull(bot.menuShare());
+        assertNotNull(bots.main.menuShare());
     }
 
     public void testClosesOnBack() throws Exception {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 95515db..34ea96e 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -43,11 +43,11 @@
     public void testRootsListed() throws Exception {
         initTestFiles();
 
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
 
         // Should also have Drive, but that requires pre-configuration of devices
         // We omit for now.
-        bot.assertHasRoots(
+        bots.roots.assertHasRoots(
                 "Images",
                 "Videos",
                 "Audio",
@@ -60,93 +60,106 @@
     public void testFilesListed() throws Exception {
         initTestFiles();
 
-        bot.openRoot(ROOT_0_ID);
-        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+        bots.roots.openRoot(ROOT_0_ID);
+        bots.directory.assertDocumentsPresent("file0.log", "file1.png", "file2.csv");
     }
 
     public void testLoadsHomeDirectoryByDefault() throws Exception {
         initTestFiles();
 
         device.waitForIdle();
-        bot.assertWindowTitle("Documents");
+        bots.main.assertWindowTitle("Documents");
     }
 
     public void testRootClickSetsWindowTitle() throws Exception {
         initTestFiles();
 
-        bot.openRoot("Downloads");
-        bot.assertWindowTitle("Downloads");
+        bots.roots.openRoot("Downloads");
+        bots.main.assertWindowTitle("Downloads");
     }
 
     public void testFilesList_LiveUpdate() throws Exception {
         initTestFiles();
 
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
 
-        bot.waitForDocument("Ham & Cheese.sandwich");
-        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+        bots.directory.waitForDocument("Ham & Cheese.sandwich");
+        bots.directory.assertDocumentsPresent(
+                "file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+    }
+
+    public void testCreateDirectory() throws Exception {
+        initTestFiles();
+
+        bots.main.openOverflowMenu();
+        bots.main.menuNewFolder().click();
+        bots.main.setDialogText("Kung Fu Panda");
+
+        bots.keyboard.pressEnter();
+
+        bots.directory.assertDocumentsPresent("Kung Fu Panda");
     }
 
     public void testDeleteDocument() throws Exception {
         initTestFiles();
 
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
 
-        bot.clickDocument("file1.png");
+        bots.directory.clickDocument("file1.png");
         device.waitForIdle();
-        bot.menuDelete().click();
+        bots.main.menuDelete().click();
 
-        bot.waitForDeleteSnackbar();
-        assertFalse(bot.hasDocuments("file1.png"));
+        bots.directory.waitForDeleteSnackbar();
+        bots.directory.assertDocumentsAbsent("file1.png");
 
-        bot.waitForDeleteSnackbarGone();
-        assertFalse(bot.hasDocuments("file1.png"));
+        bots.directory.waitForDeleteSnackbarGone();
+        bots.directory.assertDocumentsAbsent("file1.png");
 
         // Now delete from another root.
-        bot.openRoot(ROOT_1_ID);
+        bots.roots.openRoot(ROOT_1_ID);
 
-        bot.clickDocument("poodles.text");
+        bots.directory.clickDocument("poodles.text");
         device.waitForIdle();
-        bot.menuDelete().click();
+        bots.main.menuDelete().click();
 
-        bot.waitForDeleteSnackbar();
-        assertFalse(bot.hasDocuments("poodles.text"));
+        bots.directory.waitForDeleteSnackbar();
+        bots.directory.assertDocumentsAbsent("poodles.text");
 
-        bot.waitForDeleteSnackbarGone();
-        assertFalse(bot.hasDocuments("poodles.text"));
+        bots.directory.waitForDeleteSnackbarGone();
+        bots.directory.assertDocumentsAbsent("poodles.text");
     }
 
     // Tests that pressing tab switches focus between the roots and directory listings.
     public void testKeyboard_tab() throws Exception {
-        bot.pressKey(KeyEvent.KEYCODE_TAB);
-        bot.assertHasFocus("com.android.documentsui:id/roots_list");
-        bot.pressKey(KeyEvent.KEYCODE_TAB);
-        bot.assertHasFocus("com.android.documentsui:id/dir_list");
+        bots.main.pressKey(KeyEvent.KEYCODE_TAB);
+        bots.roots.assertHasFocus();
+        bots.main.pressKey(KeyEvent.KEYCODE_TAB);
+        bots.directory.assertHasFocus();
     }
 
     // Tests that arrow keys do not switch focus away from the dir list.
     public void testKeyboard_arrowsDirList() throws Exception {
         for (int i = 0; i < 10; i++) {
-            bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
-            bot.assertHasFocus("com.android.documentsui:id/dir_list");
+            bots.main.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
+            bots.directory.assertHasFocus();
         }
         for (int i = 0; i < 10; i++) {
-            bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-            bot.assertHasFocus("com.android.documentsui:id/dir_list");
+            bots.main.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+            bots.directory.assertHasFocus();
         }
     }
 
     // Tests that arrow keys do not switch focus away from the roots list.
     public void testKeyboard_arrowsRootsList() throws Exception {
-        bot.pressKey(KeyEvent.KEYCODE_TAB);
+        bots.main.pressKey(KeyEvent.KEYCODE_TAB);
         for (int i = 0; i < 10; i++) {
-            bot.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-            bot.assertHasFocus("com.android.documentsui:id/roots_list");
+            bots.main.pressKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+            bots.roots.assertHasFocus();
         }
         for (int i = 0; i < 10; i++) {
-            bot.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
-            bot.assertHasFocus("com.android.documentsui:id/roots_list");
+            bots.main.pressKey(KeyEvent.KEYCODE_DPAD_LEFT);
+            bots.roots.assertHasFocus();
         }
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
index 770bc2c..b866033 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -23,8 +23,6 @@
 @LargeTest
 public class RenameDocumentUiTest extends ActivityTest<FilesActivity> {
 
-    private static final String TAG = "RenamDocumentUiTest";
-
     private final String newName = "kitties.log";
 
     public RenameDocumentUiTest() {
@@ -35,125 +33,119 @@
     public void setUp() throws Exception {
         super.setUp();
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
     }
 
     public void testRenameEnabled_SingleSelection() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.openOverflowMenu();
-        bot.assertMenuEnabled(R.string.menu_rename, true);
+        bots.directory.selectDocument(fileName1);
+        bots.main.openOverflowMenu();
+        bots.main.assertMenuEnabled(R.string.menu_rename, true);
 
         // Dismiss more options window
         device.pressBack();
     }
 
     public void testNoRenameSupport_SingleSelection() throws Exception {
-        bot.selectDocument(fileNameNoRename);
-        bot.openOverflowMenu();
-        bot.assertMenuEnabled(R.string.menu_rename, false);
+        bots.directory.selectDocument(fileNameNoRename);
+        bots.main.openOverflowMenu();
+        bots.main.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
         device.pressBack();
     }
 
     public void testOneHasRenameSupport_MultipleSelection() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.selectDocument(fileNameNoRename);
-        bot.openOverflowMenu();
-        bot.assertMenuEnabled(R.string.menu_rename, false);
+        bots.directory.selectDocument(fileName1);
+        bots.directory.selectDocument(fileNameNoRename);
+        bots.main.openOverflowMenu();
+        bots.main.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
         device.pressBack();
     }
 
     public void testRenameDisabled_MultipleSelection() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.selectDocument(fileName2);
-        bot.openOverflowMenu();
-        bot.assertMenuEnabled(R.string.menu_rename, false);
+        bots.directory.selectDocument(fileName1);
+        bots.directory.selectDocument(fileName2);
+        bots.main.openOverflowMenu();
+        bots.main.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
         device.pressBack();
     }
 
     public void testRenameFile_OkButton() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.openOverflowMenu();
-        bot.openDialog(R.string.menu_rename);
-        bot.setDialogText(newName);
+        bots.directory.selectDocument(fileName1);
+        bots.main.openOverflowMenu();
+        bots.main.menuRename().click();
+        bots.main.setDialogText(newName);
 
         device.waitForIdle(TIMEOUT);
-        bot.findRenameDialogOkButton().click();
+        bots.main.findRenameDialogOkButton().click();
         device.waitForIdle(TIMEOUT);
 
-        bot.assertDocument(fileName1, false);
-        bot.assertDocument(newName, true);
-        bot.assertDocumentsCount(4);
+        bots.directory.assertDocumentsAbsent(fileName1);
+        bots.directory.assertDocumentsPresent(newName);
+        bots.directory.assertDocumentsCount(4);
     }
 
     public void testRenameFile_Enter() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.openOverflowMenu();
-        bot.openDialog(R.string.menu_rename);
-        bot.setDialogText(newName);
+        bots.directory.selectDocument(fileName1);
+        bots.main.openOverflowMenu();
+        bots.main.menuRename().click();
+        bots.main.setDialogText(newName);
 
-        pressEnter();
+        bots.keyboard.pressEnter();
 
-        bot.assertDocument(fileName1, false);
-        bot.assertDocument(newName, true);
-        bot.assertDocumentsCount(4);
+        bots.directory.assertDocumentsAbsent(fileName1);
+        bots.directory.assertDocumentsPresent(newName);
+        bots.directory.assertDocumentsCount(4);
     }
 
     public void testRenameFile_Cancel() throws Exception {
-        bot.selectDocument(fileName1);
-        bot.openOverflowMenu();
-        bot.openDialog(R.string.menu_rename);
-        bot.setDialogText(newName);
+        bots.directory.selectDocument(fileName1);
+        bots.main.openOverflowMenu();
+        bots.main.menuRename().click();
+        bots.main.setDialogText(newName);
 
         device.waitForIdle(TIMEOUT);
-        bot.findRenameDialogCancelButton().click();
+        bots.main.findRenameDialogCancelButton().click();
         device.waitForIdle(TIMEOUT);
 
-        bot.assertDocument(fileName1, true);
-        bot.assertDocument(newName, false);
-        bot.assertDocumentsCount(4);
+        bots.directory.assertDocumentsPresent(fileName1);
+        bots.directory.assertDocumentsAbsent(newName);
+        bots.directory.assertDocumentsCount(4);
     }
 
     public void testRenameDir() throws Exception {
         String oldName = "Dir1";
         String newName = "Dir123";
 
-        bot.selectDocument(oldName);
-        bot.openOverflowMenu();
-        bot.openDialog(R.string.menu_rename);
-        bot.setDialogText(newName);
+        bots.directory.selectDocument(oldName);
+        bots.main.openOverflowMenu();
+        bots.main.menuRename().click();
+        bots.main.setDialogText(newName);
 
-        pressEnter();
+        bots.keyboard.pressEnter();
 
-        bot.assertDocument(oldName, false);
-        bot.assertDocument(newName, true);
-        bot.assertDocumentsCount(4);
+        bots.directory.assertDocumentsAbsent(oldName);
+        bots.directory.assertDocumentsPresent(newName);
+        bots.directory.assertDocumentsCount(4);
     }
 
     public void testRename_NameExists() throws Exception {
         // Check that document with the new name exists
-        bot.assertDocument(fileName2, true);
-        bot.selectDocument(fileName1);
-        bot.openOverflowMenu();
-        bot.openDialog(R.string.menu_rename);
-        bot.setDialogText(fileName2);
+        bots.directory.assertDocumentsPresent(fileName2);
+        bots.directory.selectDocument(fileName1);
+        bots.main.openOverflowMenu();
+        bots.main.menuRename().click();
+        bots.main.setDialogText(fileName2);
 
-        pressEnter();
+        bots.keyboard.pressEnter();
 
-        bot.assertSnackbar(R.string.rename_error);
-        bot.assertDocument(fileName1, true);
-        bot.assertDocument(fileName2, true);
-        bot.assertDocumentsCount(4);
-    }
-
-    private void pressEnter() {
-        device.waitForIdle(TIMEOUT);
-        device.pressEnter();
-        device.waitForIdle(TIMEOUT);
+        bots.directory.assertSnackbar(R.string.rename_error);
+        bots.directory.assertDocumentsPresent(fileName1);
+        bots.directory.assertDocumentsPresent(fileName2);
+        bots.directory.assertDocumentsCount(4);
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java
similarity index 68%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java
index 1d1d3b5..dc41a2c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java
@@ -18,19 +18,14 @@
 
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.view.MotionEvent;
 
 @LargeTest
-public class RootUiTest extends ActivityTest<FilesActivity> {
+public class RootsUiTest extends ActivityTest<FilesActivity> {
 
     private static final String TAG = "RootUiTest";
 
-    public RootUiTest() {
+    public RootsUiTest() {
         super(FilesActivity.class);
     }
 
@@ -38,14 +33,14 @@
     public void setUp() throws Exception {
         super.setUp();
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
     }
 
     public void testRootTapped_GoToRootFromChildDir() throws Exception {
-        bot.openDocument(dirName1);
-        bot.assertWindowTitle(dirName1);
-        bot.openRoot(ROOT_0_ID);
-        bot.assertWindowTitle(ROOT_0_ID);
+        bots.directory.openDocument(dirName1);
+        bots.main.assertWindowTitle(dirName1);
+        bots.roots.openRoot(ROOT_0_ID);
+        bots.main.assertWindowTitle(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index b8d8795..478c70c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -24,61 +24,59 @@
 @LargeTest
 public class SearchViewUiTest extends ActivityTest<FilesActivity> {
 
-    private static final String TAG = "SearchViewUiTest";
-
     public SearchViewUiTest() {
         super(FilesActivity.class);
     }
 
     public void testSearchView_ExpandsOnClick() throws Exception {
-        bot.openSearchView();
-        bot.assertSearchTextFiledAndIcon(true, false);
+        bots.main.openSearchView();
+        bots.main.assertSearchTextFiledAndIcon(true, false);
     }
 
     public void testSearchView_CollapsesOnBack() throws Exception {
-        bot.openSearchView();
+        bots.main.openSearchView();
 
         device.pressBack();
 
-        bot.assertSearchTextFiledAndIcon(false, true);
+        bots.main.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearchView_ClearsTextOnBack() throws Exception {
         String query = "file2";
-        bot.openSearchView();
-        bot.setSearchQuery(query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
 
         device.pressBack();
 
-        bot.assertSearchTextFiledAndIcon(false, true);
+        bots.main.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearch_ResultsFound() throws Exception {
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
 
         String query = "file1";
-        bot.openSearchView();
-        bot.setSearchQuery(query);
-        bot.assertSearchTextField(true, query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
+        bots.main.assertSearchTextField(true, query);
 
         device.pressEnter();
 
-        bot.assertDocumentsCountOnList(true, 2);
-        bot.assertHasDocuments(fileName1, fileName2);
+        bots.directory.assertDocumentsCountOnList(true, 2);
+        bots.directory.assertDocumentsPresent(fileName1, fileName2);
 
-        bot.assertSearchTextField(false, query);
+        bots.main.assertSearchTextField(false, query);
     }
 
     public void testSearchResultsFound_ClearsOnBack() throws Exception {
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
 
         String query = fileName1;
-        bot.openSearchView();
-        bot.setSearchQuery(query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
 
         device.pressEnter();
         device.pressBack();
@@ -88,32 +86,32 @@
 
     public void testSearch_NoResults() throws Exception {
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
 
         String query = "chocolate";
-        bot.openSearchView();
-        bot.setSearchQuery(query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
 
         device.pressEnter();
 
-        bot.assertDocumentsCountOnList(false, 0);
+        bots.directory.assertDocumentsCountOnList(false, 0);
 
         device.waitForIdle();
         String msg = String.valueOf(context.getString(R.string.no_results));
-        bot.assertMessageTextView(String.format(msg, "TEST_ROOT_0"));
+        bots.directory.assertMessageTextView(String.format(msg, "TEST_ROOT_0"));
 
-        bot.assertSearchTextField(false, query);
+        bots.main.assertSearchTextField(false, query);
     }
 
     public void testSearchNoResults_ClearsOnBack() throws Exception {
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
 
         String query = "chocolate";
-        bot.openSearchView();
-        bot.setSearchQuery(query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
 
         device.pressEnter();
         device.pressBack();
@@ -124,30 +122,30 @@
 
     public void testSearchResultsFound_ClearsOnDirectoryChange() throws Exception {
         initTestFiles();
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
 
         String query = fileName1;
-        bot.openSearchView();
-        bot.setSearchQuery(query);
+        bots.main.openSearchView();
+        bots.main.setSearchQuery(query);
 
         device.pressEnter();
 
-        bot.openRoot(ROOT_1_ID);
+        bots.roots.openRoot(ROOT_1_ID);
         assertDefaultContentOfTestDir1();
 
-        bot.openRoot(ROOT_0_ID);
+        bots.roots.openRoot(ROOT_0_ID);
         assertDefaultContentOfTestDir0();
     }
 
     public void testSearchIconVisible_RootWithSearchSupport() throws Exception {
-        bot.openRoot(ROOT_0_ID);
-        bot.assertSearchTextFiledAndIcon(false, true);
+        bots.roots.openRoot(ROOT_0_ID);
+        bots.main.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearchIconHidden_RootNoSearchSupport() throws Exception {
-        bot.openRoot(ROOT_1_ID);
-        bot.assertSearchTextFiledAndIcon(false, false);
+        bots.roots.openRoot(ROOT_1_ID);
+        bots.main.assertSearchTextFiledAndIcon(false, false);
     }
 
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
deleted file mode 100644
index d2f8403..0000000
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ /dev/null
@@ -1,432 +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.documentsui;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertFalse;
-
-import android.app.Activity;
-import android.content.Context;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.inputmethod.InputMethodManager;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * A test helper class that provides support for controlling DocumentsUI activities
- * programmatically, and making assertions against the state of the UI.
- */
-class UiBot {
-    public static final String TARGET_PKG = "com.android.documentsui";
-
-    private static final String TAG = "UiBot";
-    private static final String LAUNCHER_PKG = "com.android.launcher";
-
-    private static final BySelector SNACK_DELETE =
-            By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
-
-    private UiDevice mDevice;
-    private Context mContext;
-    private int mTimeout;
-
-    public UiBot(UiDevice device, Context context, int timeout) {
-        mDevice = device;
-        mContext = context;
-        mTimeout = timeout;
-    }
-
-    UiObject findRoot(String label) throws UiObjectNotFoundException {
-        final UiSelector rootsList = new UiSelector().resourceId(
-                "com.android.documentsui:id/container_roots").childSelector(
-                new UiSelector().resourceId("com.android.documentsui:id/roots_list"));
-
-        // We might need to expand drawer if not visible
-        if (!new UiObject(rootsList).waitForExists(mTimeout)) {
-            Log.d(TAG, "Failed to find roots list; trying to expand");
-            final UiSelector hamburger = new UiSelector().resourceId(
-                    "com.android.documentsui:id/toolbar").childSelector(
-                    new UiSelector().className("android.widget.ImageButton").clickable(true));
-            new UiObject(hamburger).click();
-        }
-
-        // Wait for the first list item to appear
-        new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(mTimeout);
-
-        // Now scroll around to find our item
-        new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
-        return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
-    }
-
-    void openRoot(String label) throws UiObjectNotFoundException {
-        findRoot(label).click();
-        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) {
-            if (!findRoot(label).exists()) {
-                missing.add(label);
-            }
-        }
-        if (!missing.isEmpty()) {
-            Assert.fail(
-                    "Expected roots " + Arrays.asList(labels) + ", but missing " + missing);
-        }
-    }
-
-    void assertMenuEnabled(int id, boolean enabled) {
-        UiObject2 menu= findMenuWithName(mContext.getString(id));
-        assertNotNull(menu);
-        assertEquals(enabled, menu.isEnabled());
-    }
-
-    void assertDocumentsCount(int count) throws UiObjectNotFoundException {
-        UiObject docsList = findDocumentsList();
-        assertEquals(count, docsList.getChildCount());
-    }
-
-    void assertDocumentsCount(String dir, int count) throws UiObjectNotFoundException {
-        openRoot(dir);
-        UiObject docsList = findDocumentsList();
-        assertEquals(count, docsList.getChildCount());
-    }
-
-    void assertSearchTextField(boolean isFocused, String query)
-            throws UiObjectNotFoundException {
-        UiObject textField = findSearchViewTextField();
-        UiObject searchIcon = findSearchViewIcon();
-
-        assertFalse(searchIcon.exists());
-        assertTrue(textField.exists());
-        assertEquals(isFocused, textField.isFocused());
-        if(query != null) {
-            assertEquals(query, textField.getText());
-        }
-    }
-
-    void assertSearchTextFiledAndIcon(boolean searchTextFieldExists, boolean searchIconExists) {
-        assertEquals(searchTextFieldExists, findSearchViewTextField().exists());
-        assertEquals(searchIconExists, findSearchViewIcon().exists());
-    }
-
-    void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
-        List<String> missing = new ArrayList<>();
-        for (String label : labels) {
-            if (!findDocument(label).exists()) {
-                missing.add(label);
-            }
-        }
-        if (!missing.isEmpty()) {
-            Assert.fail(
-                    "Expected documents " + Arrays.asList(labels) + ", but missing " + missing);
-        }
-    }
-
-    void assertDocument(String name, boolean exists) throws UiObjectNotFoundException {
-        UiObject doc = findDocument(name);
-        assertEquals(exists, doc.exists());
-    }
-
-    void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException {
-        UiObject docsList = findDocumentsList();
-        assertEquals(exists, docsList.exists());
-        if(docsList.exists()) {
-            assertEquals(count, docsList.getChildCount());
-        }
-    }
-
-    void assertMessageTextView(String message) throws UiObjectNotFoundException {
-        UiObject messageTextView = findMessageTextView();
-        assertTrue(messageTextView.exists());
-
-        String msg = String.valueOf(message);
-        assertEquals(String.format(msg, "TEST_ROOT_0"), messageTextView.getText());
-
-    }
-    void assertSnackbar(int id) {
-        assertNotNull(getSnackbar(mContext.getString(id)));
-    }
-
-    /**
-     * Asserts that the specified view or one of its descendents has focus.
-     */
-    void assertHasFocus(String resourceName) {
-        UiObject2 candidate = mDevice.findObject(By.res(resourceName));
-        assertNotNull("Expected " + resourceName + " to have focus, but it didn't.",
-            candidate.findObject(By.focused(true)));
-    }
-
-    void openDocument(String label) throws UiObjectNotFoundException {
-        int toolType = Configurator.getInstance().getToolType();
-        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
-        UiObject doc = findDocument(label);
-        doc.click();
-        Configurator.getInstance().setToolType(toolType);
-    }
-
-    void clickDocument(String label) throws UiObjectNotFoundException {
-        findDocument(label).click();
-    }
-
-    void openSearchView() throws UiObjectNotFoundException {
-        UiObject searchView = findSearchView();
-        searchView.click();
-        assertTrue(searchView.exists());
-    }
-
-    void setSearchQuery(String query) throws UiObjectNotFoundException {
-        UiObject searchView = findSearchView();
-        assertTrue(searchView.exists());
-        UiObject searchTextField = findSearchViewTextField();
-        searchTextField.setText(query);
-        assertSearchTextField(true, query);
-    }
-
-    UiObject openOverflowMenu() throws UiObjectNotFoundException {
-        UiObject obj = findMenuMoreOptions();
-        obj.click();
-        mDevice.waitForIdle(mTimeout);
-        return obj;
-    }
-
-    void openDialog(int id) {
-        UiObject2 menu= findMenuWithName(mContext.getString(id));
-        assertNotNull(menu);
-        assertEquals(true, menu.isEnabled());
-        menu.click();
-    }
-
-    void setDialogText(String text) throws UiObjectNotFoundException {
-        findDialogEditText().setText(text);
-    }
-
-    UiObject selectDocument(String label) throws UiObjectNotFoundException {
-        UiObject doc = findDocument(label);
-        doc.longClick();
-        return doc;
-    }
-
-    UiObject2 getSnackbar(String message) {
-        return mDevice.wait(Until.findObject(By.text(message)), mTimeout);
-    }
-
-    void waitForDeleteSnackbar() {
-        mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
-    }
-
-    void waitForDeleteSnackbarGone() {
-        // wait a little longer for snackbar to go away, as it disappears after a timeout.
-        mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
-    }
-
-    void waitForDocument(String label) throws UiObjectNotFoundException {
-        findDocument(label).waitForExists(mTimeout);
-    }
-
-    void switchViewMode() {
-        UiObject2 mode = menuGridMode();
-        if (mode != null) {
-            mode.click();
-        } else {
-            menuListMode().click();
-        }
-    }
-
-    UiObject2 menuGridMode() {
-        // Note that we're using By.desc rather than By.res, because of b/25285770
-        return find(By.desc("Grid view"));
-    }
-
-    UiObject2 menuListMode() {
-        // Note that we're using By.desc rather than By.res, because of b/25285770
-        return find(By.desc("List view"));
-    }
-
-    UiObject2 menuDelete() {
-        return find(By.res("com.android.documentsui:id/menu_delete"));
-    }
-
-    UiObject2 menuShare() {
-        return find(By.res("com.android.documentsui:id/menu_share"));
-    }
-
-    private UiObject2 find(BySelector selector) {
-        mDevice.wait(Until.findObject(selector), mTimeout);
-        return mDevice.findObject(selector);
-    }
-
-    private UiObject findObject(String resourceId) {
-        final UiSelector object = new UiSelector().resourceId(resourceId);
-        return mDevice.findObject(object);
-    }
-
-    private UiObject findObject(String parentResourceId, String childResourceId) {
-        final UiSelector selector = new UiSelector()
-                .resourceId(parentResourceId)
-                .childSelector(new UiSelector().resourceId(childResourceId));
-        return mDevice.findObject(selector);
-    }
-
-    UiObject findDocument(String label) throws UiObjectNotFoundException {
-        final UiSelector docList = new UiSelector().resourceId(
-                "com.android.documentsui:id/container_directory").childSelector(
-                        new UiSelector().resourceId("com.android.documentsui:id/dir_list"));
-
-        // Wait for the first list item to appear
-        new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
-
-        // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
-        return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
-    }
-
-    boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
-        for (String label : labels) {
-            if (!findDocument(label).exists()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    UiObject findDocumentsList() {
-        return findObject(
-                "com.android.documentsui:id/container_directory",
-                "com.android.documentsui:id/dir_list");
-    }
-
-    UiObject findSearchView() {
-        return findObject("com.android.documentsui:id/menu_search");
-    }
-
-    UiObject findSearchViewTextField() {
-        return findObject("com.android.documentsui:id/menu_search", "android:id/search_src_text");
-    }
-
-    UiObject findSearchViewIcon() {
-        return findObject("com.android.documentsui:id/menu_search", "android:id/search_button");
-    }
-
-    UiObject findMessageTextView() {
-        return findObject(
-                "com.android.documentsui:id/container_directory",
-                "com.android.documentsui:id/message");
-    }
-
-    UiObject findActionModeBar() {
-        return findObject("android:id/action_mode_bar");
-    }
-
-    UiObject findDialogEditText() {
-        return findObject("android:id/content", "android:id/text1");
-    }
-
-    UiObject findRenameDialogOkButton() {
-        return findObject("android:id/content", "android:id/button1");
-    }
-
-    UiObject findRenameDialogCancelButton() {
-        return findObject("android:id/content", "android:id/button2");
-    }
-
-    UiObject findMenuLabelWithName(String label) {
-        UiSelector selector = new UiSelector().text(label);
-        return mDevice.findObject(selector);
-    }
-
-    UiObject2 findMenuWithName(String label) {
-        List<UiObject2> menuItems = mDevice.findObjects(By.clazz("android.widget.LinearLayout"));
-        Iterator<UiObject2> it = menuItems.iterator();
-
-        UiObject2 menuItem = null;
-        while(it.hasNext()) {
-            menuItem = it.next();
-            UiObject2 text = menuItem.findObject(By.text(label));
-            if(text != null) {
-                break;
-            }
-        }
-        return menuItem;
-    }
-
-    UiObject findMenuMoreOptions() {
-        UiSelector selector = new UiSelector().className("android.widget.ImageButton")
-                .descriptionContains("More options");
-        //TODO: use the system string ? android.R.string.action_menu_overflow_description
-        return mDevice.findObject(selector);
-    }
-
-    // Indirect way to detect the keyboard.
-    boolean isKeyboardPresent() {
-        InputMethodManager inputManager = (InputMethodManager) mContext
-                .getSystemService(Context.INPUT_METHOD_SERVICE);
-        return inputManager.isAcceptingText();
-    }
-
-    void dismissKeyboardIfPresent() {
-        if(isKeyboardPresent()) {
-            mDevice.pressBack();
-        }
-    }
-
-    void revealLauncher() {
-        mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), mTimeout);
-    }
-
-    void revealApp() {
-        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), mTimeout);
-        mDevice.waitForIdle();
-    }
-
-    void pressKey(int keyCode) {
-        mDevice.pressKeyCode(keyCode);
-    }
-}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/BaseBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/BaseBot.java
new file mode 100644
index 0000000..1d2b47f
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/BaseBot.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bots;
+
+import static junit.framework.Assert.assertNotNull;
+
+import android.content.Context;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+/**
+ * A test helper class that provides support for controlling directory list
+ * and making assertions against the state of it.
+ */
+abstract class BaseBot {
+    final UiDevice mDevice;
+    final Context mContext;
+    final int mTimeout;
+
+    BaseBot(UiDevice device, Context context, int timeout) {
+        mDevice = device;
+        mContext = context;
+        mTimeout = timeout;
+    }
+
+    /**
+     * Asserts that the specified view or one of its descendents has focus.
+     */
+    protected void assertHasFocus(String resourceName) {
+        UiObject2 candidate = mDevice.findObject(By.res(resourceName));
+        assertNotNull("Expected " + resourceName + " to have focus, but it didn't.",
+            candidate.findObject(By.focused(true)));
+    }
+
+    protected UiObject2 find(BySelector selector) {
+        mDevice.wait(Until.findObject(selector), mTimeout);
+        return mDevice.findObject(selector);
+    }
+
+    protected UiObject findObject(String resourceId) {
+        final UiSelector object = new UiSelector().resourceId(resourceId);
+        return mDevice.findObject(object);
+    }
+
+    protected UiObject findObject(String parentResourceId, String childResourceId) {
+        final UiSelector selector = new UiSelector()
+                .resourceId(parentResourceId)
+                .childSelector(new UiSelector().resourceId(childResourceId));
+        return mDevice.findObject(selector);
+    }
+
+    protected void waitForIdle() {
+        mDevice.waitForIdle(mTimeout);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
new file mode 100644
index 0000000..da9f9c3
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bots;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.view.MotionEvent;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A test helper class that provides support for controlling directory list
+ * and making assertions against the state of it.
+ */
+public class DirectoryListBot extends BaseBot {
+    private static final String DIR_LIST_ID = "com.android.documentsui:id/dir_list";
+
+    private static final BySelector SNACK_DELETE =
+            By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
+
+    public DirectoryListBot(UiDevice device, Context context, int timeout) {
+        super(device, context, timeout);
+    }
+
+    public void assertDocumentsCount(int count) throws UiObjectNotFoundException {
+        UiObject docsList = findDocumentsList();
+        assertEquals(count, docsList.getChildCount());
+    }
+
+    public void assertDocumentsPresent(String... labels) throws UiObjectNotFoundException {
+        List<String> absent = new ArrayList<>();
+        for (String label : labels) {
+            if (!findDocument(label).exists()) {
+                absent.add(label);
+            }
+        }
+        if (!absent.isEmpty()) {
+            Assert.fail("Expected documents " + Arrays.asList(labels)
+                    + ", but missing " + absent);
+        }
+    }
+
+    public void assertDocumentsAbsent(String... labels) throws UiObjectNotFoundException {
+        List<String> found = new ArrayList<>();
+        for (String label : labels) {
+            if (findDocument(label).exists()) {
+                found.add(label);
+            }
+        }
+        if (!found.isEmpty()) {
+            Assert.fail("Expected documents not present" + Arrays.asList(labels)
+                    + ", but present " + found);
+        }
+    }
+
+    public void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException {
+        UiObject docsList = findDocumentsList();
+        assertEquals(exists, docsList.exists());
+        if(docsList.exists()) {
+            assertEquals(count, docsList.getChildCount());
+        }
+    }
+
+    public void assertMessageTextView(String message) throws UiObjectNotFoundException {
+        UiObject messageTextView = findMessageTextView();
+        assertTrue(messageTextView.exists());
+
+        String msg = String.valueOf(message);
+        assertEquals(String.format(msg, "TEST_ROOT_0"), messageTextView.getText());
+
+    }
+
+    private UiObject findMessageTextView() {
+        return findObject(
+                "com.android.documentsui:id/container_directory",
+                "com.android.documentsui:id/message");
+    }
+
+    public void assertSnackbar(int id) {
+        assertNotNull(getSnackbar(mContext.getString(id)));
+    }
+
+    public void openDocument(String label) throws UiObjectNotFoundException {
+        int toolType = Configurator.getInstance().getToolType();
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
+        UiObject doc = findDocument(label);
+        doc.click();
+        Configurator.getInstance().setToolType(toolType);
+    }
+
+    public void clickDocument(String label) throws UiObjectNotFoundException {
+        findDocument(label).click();
+    }
+
+    public UiObject selectDocument(String label) throws UiObjectNotFoundException {
+        UiObject doc = findDocument(label);
+        doc.longClick();
+        return doc;
+    }
+
+    public UiObject2 getSnackbar(String message) {
+        return mDevice.wait(Until.findObject(By.text(message)), mTimeout);
+    }
+
+    public void waitForDeleteSnackbar() {
+        mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
+    }
+
+    public void waitForDeleteSnackbarGone() {
+        // wait a little longer for snackbar to go away, as it disappears after a timeout.
+        mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
+    }
+
+    public void waitForDocument(String label) throws UiObjectNotFoundException {
+        findDocument(label).waitForExists(mTimeout);
+    }
+
+    public UiObject findDocument(String label) throws UiObjectNotFoundException {
+        final UiSelector docList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_directory").childSelector(
+                        new UiSelector().resourceId(DIR_LIST_ID));
+
+        // Wait for the first list item to appear
+        new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+        // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+        return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+    }
+
+    public boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
+        for (String label : labels) {
+            if (!findDocument(label).exists()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private UiObject findDocumentsList() {
+        return findObject(
+                "com.android.documentsui:id/container_directory",
+                DIR_LIST_ID);
+    }
+
+    public void assertHasFocus() {
+        assertHasFocus(DIR_LIST_ID);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/KeyboardBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/KeyboardBot.java
new file mode 100644
index 0000000..4c47cfa
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/KeyboardBot.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.bots;
+
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * A test helper class that provides support for keyboard manipulation.
+ */
+public class KeyboardBot extends BaseBot {
+
+    public KeyboardBot(UiDevice device, Context context, int timeout) {
+        super(device, context, timeout);
+    }
+
+    public void dismissKeyboardIfPresent() {
+        if(isKeyboardPresent()) {
+            mDevice.pressBack();
+        }
+    }
+
+    // Indirect way to detect the keyboard.
+    private boolean isKeyboardPresent() {
+        InputMethodManager inputManager = (InputMethodManager) mContext
+                .getSystemService(Context.INPUT_METHOD_SERVICE);
+        return inputManager.isAcceptingText();
+    }
+
+    public void pressEnter() {
+        waitForIdle();
+        mDevice.pressEnter();
+        waitForIdle();
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/RootsListBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/RootsListBot.java
new file mode 100644
index 0000000..356fd01
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/RootsListBot.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.documentsui.bots;
+
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A test helper class that provides support for controlling and asserting against
+ * the roots list drawer.
+ */
+public class RootsListBot extends BaseBot {
+    private static final String ROOTS_LIST_ID = "com.android.documentsui:id/roots_list";
+    private static final String TAG = "RootsListBot";
+
+    public RootsListBot(UiDevice device, Context context, int timeout) {
+        super(device, context, timeout);
+    }
+
+    private UiObject findRoot(String label) throws UiObjectNotFoundException {
+        final UiSelector rootsList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_roots").childSelector(
+                new UiSelector().resourceId(ROOTS_LIST_ID));
+
+        // We might need to expand drawer if not visible
+        if (!new UiObject(rootsList).waitForExists(mTimeout)) {
+            Log.d(TAG, "Failed to find roots list; trying to expand");
+            final UiSelector hamburger = new UiSelector().resourceId(
+                    "com.android.documentsui:id/toolbar").childSelector(
+                    new UiSelector().className("android.widget.ImageButton").clickable(true));
+            new UiObject(hamburger).click();
+        }
+
+        // Wait for the first list item to appear
+        new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+        // Now scroll around to find our item
+        new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+    }
+
+    public void openRoot(String label) throws UiObjectNotFoundException {
+        findRoot(label).click();
+        mDevice.waitForIdle();
+    }
+
+    public void assertHasRoots(String... labels) throws UiObjectNotFoundException {
+        List<String> missing = new ArrayList<>();
+        for (String label : labels) {
+            if (!findRoot(label).exists()) {
+                missing.add(label);
+            }
+        }
+        if (!missing.isEmpty()) {
+            Assert.fail(
+                    "Expected roots " + Arrays.asList(labels) + ", but missing " + missing);
+        }
+    }
+
+    public void assertHasFocus() {
+        assertHasFocus(ROOTS_LIST_ID);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
new file mode 100644
index 0000000..fe2a3c3
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.bots;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+import com.android.documentsui.R;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A test helper class that provides support for controlling DocumentsUI activities
+ * programmatically, and making assertions against the state of the UI.
+ *
+ * <p>Support for working directly with Roots and Directory view can be found
+ * in the respective bots.
+ */
+public class UiBot extends BaseBot {
+    public static final String TARGET_PKG = "com.android.documentsui";
+    private static final String LAUNCHER_PKG = "com.android.launcher";
+
+    public UiBot(UiDevice device, Context context, int timeout) {
+        super(device, context, timeout);
+    }
+
+    public 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());
+    }
+
+    public void assertMenuEnabled(int id, boolean enabled) {
+        UiObject2 menu= findMenuWithName(mContext.getString(id));
+        assertNotNull(menu);
+        assertEquals(enabled, menu.isEnabled());
+    }
+
+    public void assertSearchTextField(boolean isFocused, String query)
+            throws UiObjectNotFoundException {
+        UiObject textField = findSearchViewTextField();
+        UiObject searchIcon = findSearchViewIcon();
+
+        assertFalse(searchIcon.exists());
+        assertTrue(textField.exists());
+        assertEquals(isFocused, textField.isFocused());
+        if(query != null) {
+            assertEquals(query, textField.getText());
+        }
+    }
+
+    public void assertSearchTextFiledAndIcon(boolean searchTextFieldExists, boolean searchIconExists) {
+        assertEquals(searchTextFieldExists, findSearchViewTextField().exists());
+        assertEquals(searchIconExists, findSearchViewIcon().exists());
+    }
+
+    public void openSearchView() throws UiObjectNotFoundException {
+        UiObject searchView = findSearchView();
+        searchView.click();
+        assertTrue(searchView.exists());
+    }
+
+    public void setSearchQuery(String query) throws UiObjectNotFoundException {
+        UiObject searchView = findSearchView();
+        assertTrue(searchView.exists());
+        UiObject searchTextField = findSearchViewTextField();
+        searchTextField.setText(query);
+        assertSearchTextField(true, query);
+    }
+
+    public UiObject openOverflowMenu() throws UiObjectNotFoundException {
+        UiObject obj = findMenuMoreOptions();
+        obj.click();
+        mDevice.waitForIdle(mTimeout);
+        return obj;
+    }
+
+    public void setDialogText(String text) throws UiObjectNotFoundException {
+        findDialogEditText().setText(text);
+    }
+
+    void switchViewMode() {
+        UiObject2 mode = menuGridMode();
+        if (mode != null) {
+            mode.click();
+        } else {
+            menuListMode().click();
+        }
+    }
+
+    UiObject2 menuGridMode() {
+        // Note that we're using By.desc rather than By.res, because of b/25285770
+        return find(By.desc("Grid view"));
+    }
+
+        UiObject2 menuListMode() {
+        // Note that we're using By.desc rather than By.res, because of b/25285770
+        return find(By.desc("List view"));
+    }
+
+    public UiObject2 menuDelete() {
+        return find(By.res("com.android.documentsui:id/menu_delete"));
+    }
+
+    public UiObject2 menuShare() {
+        return find(By.res("com.android.documentsui:id/menu_share"));
+    }
+
+    public UiObject2 menuRename() {
+        return findMenuWithName(mContext.getString(R.string.menu_rename));
+    }
+
+    public UiObject2 menuNewFolder() {
+        return findMenuWithName(mContext.getString(R.string.menu_create_dir));
+    }
+
+    UiObject findSearchView() {
+        return findObject("com.android.documentsui:id/menu_search");
+    }
+
+    UiObject findSearchViewTextField() {
+        return findObject("com.android.documentsui:id/menu_search", "android:id/search_src_text");
+    }
+
+    UiObject findSearchViewIcon() {
+        return findObject("com.android.documentsui:id/menu_search", "android:id/search_button");
+    }
+
+    UiObject findActionModeBar() {
+        return findObject("android:id/action_mode_bar");
+    }
+
+    public UiObject findDialogEditText() {
+        return findObject("android:id/content", "android:id/text1");
+    }
+
+    public UiObject findRenameDialogOkButton() {
+        return findObject("android:id/content", "android:id/button1");
+    }
+
+    public UiObject findRenameDialogCancelButton() {
+        return findObject("android:id/content", "android:id/button2");
+    }
+
+    UiObject findMenuLabelWithName(String label) {
+        UiSelector selector = new UiSelector().text(label);
+        return mDevice.findObject(selector);
+    }
+
+    UiObject2 findMenuWithName(String label) {
+        List<UiObject2> menuItems = mDevice.findObjects(By.clazz("android.widget.LinearLayout"));
+        Iterator<UiObject2> it = menuItems.iterator();
+
+        UiObject2 menuItem = null;
+        while(it.hasNext()) {
+            menuItem = it.next();
+            UiObject2 text = menuItem.findObject(By.text(label));
+            if(text != null) {
+                break;
+            }
+        }
+        return menuItem;
+    }
+
+    UiObject findMenuMoreOptions() {
+        UiSelector selector = new UiSelector().className("android.widget.ImageButton")
+                .descriptionContains("More options");
+        //TODO: use the system string ? android.R.string.action_menu_overflow_description
+        return mDevice.findObject(selector);
+    }
+
+    public void revealLauncher() {
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), mTimeout);
+    }
+
+    public void revealApp() {
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), mTimeout);
+        mDevice.waitForIdle();
+    }
+
+    public void pressKey(int keyCode) {
+        mDevice.pressKeyCode(keyCode);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
index a1c6dab..9147a57 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
@@ -21,7 +21,6 @@
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.content.Context;
-import android.os.RemoteException;
 
 import com.android.documentsui.R;
 import com.android.documentsui.model.DocumentInfo;
@@ -38,7 +37,7 @@
     }
 
     @Override
-    void start() throws RemoteException {
+    void start() {
         mStarted = true;
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index ec2a173..434631e 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -413,7 +413,7 @@
      * @return true if the menu key should be enabled
      */
     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
-    private boolean shouldEnableMenuKey() {
+    public boolean shouldEnableMenuKey() {
         final Resources res = getResources();
         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
@@ -421,15 +421,6 @@
         return !configDisabled || isTestHarness || fileOverride;
     }
 
-    public boolean handleMenuKey() {
-        // The following enables the MENU key to work for testing automation
-        if (shouldEnableMenuKey()) {
-            dismiss();
-            return true;
-        }
-        return false;
-    }
-
     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
         mViewMediatorCallback = viewMediatorCallback;
         // Update ViewMediator with the current input method requirements
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
index cedd88d..fe98cb8 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -89,7 +89,12 @@
             return true;
         }
         if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
-            int number = keyCode - KeyEvent.KEYCODE_0 ;
+            int number = keyCode - KeyEvent.KEYCODE_0;
+            performNumberClick(number);
+            return true;
+        }
+        if (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9) {
+            int number = keyCode - KeyEvent.KEYCODE_NUMPAD_0;
             performNumberClick(number);
             return true;
         }
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 1bce7f9..525d6f4 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -344,28 +344,6 @@
         <item>midi</item>
     </string-array>
 
-    <!-- Possible values for user theme in Display Settings.
-         Do not translate. -->
-    <string-array name="night_mode_entries" translatable="false">
-        <!-- Do not translate. -->
-        <item>@string/night_mode_no</item>
-        <!-- Do not translate. -->
-        <item>@string/night_mode_yes</item>
-        <!-- Do not translate. -->
-        <item>@string/night_mode_auto</item>
-    </string-array>
-
-    <!-- These values should match up with the MODE_NIGHT constants in UiModeManager.
-         Do not translate. -->
-    <string-array name="night_mode_values" translatable="false">
-        <!-- Do not translate. -->
-        <item>1</item>
-        <!-- Do not translate. -->
-        <item>2</item>
-        <!-- Do not translate. -->
-        <item>0</item>
-    </string-array>
-
     <!-- Display color space adjustment modes for developers -->
     <string-array name="simulate_color_space_entries" translatable="false">
         <item>@string/daltonizer_mode_disabled</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 57c5684..676bf5f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -236,10 +236,14 @@
     <string name="tts_settings">Text-to-speech settings</string>
     <!-- TTS option item name in the main settings screen -->
     <string name="tts_settings_title">Text-to-speech output</string>
-    <!-- On main TTS Settings screen, in default settings section, setting default speech rate for synthesized voice -->
+  <!-- On main TTS Settings screen, in default settings section, setting default speech rate for synthesized voice -->
     <string name="tts_default_rate_title">Speech rate</string>
     <!-- On main TTS Settings screen, summary for default speech rate for synthesized voice -->
     <string name="tts_default_rate_summary">Speed at which the text is spoken</string>
+    <!-- On main TTS Settings screen, in default settings section, setting default pitch for synthesized voice -->
+    <string name="tts_default_pitch_title">Pitch</string>
+    <!-- On main TTS Settings screen, summary for default pitch for synthesized voice -->
+    <string name="tts_default_pitch_summary">Affects the tone of the synthesized speech</string>
     <!-- On main TTS Settings screen, in default settings section, setting default language for synthesized voice -->
     <string name="tts_default_lang_title">Language</string>
     <!-- Entry in the TTS engine language/locale picker, when selected will try to default to the system language [CHAR LIMIT=50] -->
@@ -673,17 +677,6 @@
     <!-- Services settings screen, setting option summary for the user to go to the screen to view running services  -->
     <string name="runningservices_settings_summary">View and control currently running services</string>
 
-    <!-- Sound & display settings screen, setting option name to change the user interface theme [CHAR LIMIT=30] -->
-    <string name="night_mode_title">Night mode</string>
-    <!-- Sound & display settings screen, setting option summary to change the user interface theme [CHAR LIMIT=100] -->
-    <string name="night_mode_summary">%s</string>
-    <!-- Sound & display settings screen, theme setting value to prefer a light-colored user interface [CHAR LIMIT=30] -->
-    <string name="night_mode_no">Disabled</string>
-    <!-- Sound & display settings screen, theme setting value to prefer a dark-colored user interface [CHAR LIMIT=30] -->
-    <string name="night_mode_yes">Always on</string>
-    <!-- Sound & display settings screen, theme setting value to automatically switch between a light- or dark-colored user interface [CHAR LIMIT=30] -->
-    <string name="night_mode_auto">Automatic</string>
-
     <!-- Developer settings: enable WebView multiprocess name [CHAR LIMIT=30] -->
     <string name="enable_webview_multiprocess">Enable multiprocess WebView</string>
     <!-- Developer settings: enable WebView multiprocess summary [CHAR LIMIT=60] -->
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 9926ae5..20aca0e 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -41,6 +41,8 @@
 import libcore.io.Streams;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.google.android.collect.Lists;
 
 import android.accounts.Account;
@@ -506,6 +508,7 @@
      * Cancels a bugreport upon user's request.
      */
     private void cancel(int id) {
+        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
         Log.v(TAG, "cancel: ID=" + id);
         final BugreportInfo info = getInfo(id);
         if (info != null && !info.finished) {
@@ -582,6 +585,7 @@
      * change its values.
      */
     private void launchBugreportInfoDialog(int id) {
+        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
         // Copy values so it doesn't lock mProcesses while UI is being updated
         final String name, title, description;
         final BugreportInfo info = getInfo(id);
@@ -610,6 +614,7 @@
      * upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
      */
     private void takeScreenshot(int id, boolean delayed) {
+        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
         if (getInfo(id) == null) {
             // Most likely am killed Shell before user tapped the notification. Since system might
             // be too busy anwyays, it's better to ignore the notification and switch back to the
@@ -764,6 +769,12 @@
         info.renameScreenshots(mScreenshotsDir);
         info.bugreportFile = bugreportFile;
 
+        final int max = intent.getIntExtra(EXTRA_MAX, -1);
+        if (max != -1) {
+            MetricsLogger.histogram(this, "dumpstate_duration", max);
+            info.max = max;
+        }
+
         final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT);
         if (screenshot != null) {
             info.addScreenshot(screenshot);
@@ -859,6 +870,7 @@
      * intent, but issuing a warning dialog the first time.
      */
     private void shareBugreport(int id, BugreportInfo sharedInfo) {
+        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
         BugreportInfo info = getInfo(id);
         if (info == null) {
             // Service was terminated but notification persisted
@@ -1139,9 +1151,16 @@
         if (info == null) {
             return;
         }
+        if (title != null && !title.equals(info.title)) {
+            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
+        }
         info.title = title;
+        if (description != null && !description.equals(info.description)) {
+            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
+        }
         info.description = description;
         if (name != null && !name.equals(info.name)) {
+            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
             info.name = name;
             updateProgress(info);
         }
@@ -1229,7 +1248,7 @@
         /**
          * Sets its internal state and displays the dialog.
          */
-        private void initialize(Context context, BugreportInfo info) {
+        private void initialize(final Context context, BugreportInfo info) {
             // First initializes singleton.
             if (mDialog == null) {
                 @SuppressLint("InflateParams")
@@ -1263,6 +1282,8 @@
                                     @Override
                                     public void onClick(DialogInterface dialog, int id)
                                     {
+                                        MetricsLogger.action(context,
+                                                MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
                                         if (!mTempName.equals(mSavedName)) {
                                             // Must restore dumpstate's name since it was changed
                                             // before user clicked OK.
@@ -1307,6 +1328,7 @@
 
                     @Override
                     public void onClick(View view) {
+                        MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
                         sanitizeName();
                         final String name = mInfoName.getText().toString();
                         final String title = mInfoTitle.getText().toString();
@@ -1525,7 +1547,7 @@
             return "id: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished
                     + "\n\ttitle: " + title + "\n\tdescription: " + description
                     + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
-                    + "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
+                    + "\n\tprogress: " + progress + "/" + max + " (" + percent + ")"
                     + "\n\tlast_update: " + getFormattedLastUpdate()
                     + "\naddingDetailsToZip: " + addingDetailsToZip
                     + " addedDetailsToZip: " + addedDetailsToZip;
diff --git a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
index 712347a..57809ac 100644
--- a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
+++ b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
@@ -106,6 +106,10 @@
                 try {
                     ArrayList<String> sourceAssets = new ArrayList<String>();
                     for (String host : hostList) {
+                        // "*.example.tld" is validated via https://example.tld
+                        if (host.startsWith("*.")) {
+                            host = host.substring(2);
+                        }
                         sourceAssets.add(createWebAssetString(scheme, host));
                     }
                     extras.putStringArrayList(DirectStatementService.EXTRA_SOURCE_ASSET_DESCRIPTORS,
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index f699fce..7df6bc6 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -22,6 +22,7 @@
     android:focusable="true"
     android:clickable="true"
     >
+
     <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -65,4 +66,9 @@
             />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
 
+    <com.android.systemui.statusbar.notification.FakeShadowView
+        android:id="@+id/fake_shadow"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index c4c45bb..e456984 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -76,4 +76,9 @@
         android:layout_height="wrap_content"
         />
 
+    <com.android.systemui.statusbar.notification.FakeShadowView
+        android:id="@+id/fake_shadow"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </com.android.systemui.statusbar.ExpandableNotificationRow>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index e4effd4..baec8ef 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,25 +13,15 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/volume_dialog"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
     android:background="@drawable/volume_dialog_background"
-    android:translationZ="4dp" >
-
-    <com.android.keyguard.AlphaOptimizedImageButton
-        android:id="@+id/volume_expand_button"
-        style="@style/VolumeButtons"
-        android:layout_width="@dimen/volume_button_size"
-        android:layout_height="@dimen/volume_button_size"
-        android:layout_alignParentLeft="true"
-        android:clickable="true"
-        android:soundEffectsEnabled="false"
-        android:src="@drawable/ic_volume_collapse_animation"
-        tools:ignore="RtlHardcoded" />
+    android:translationZ="4dp"
+    android:paddingTop="8dp">
 
     <LinearLayout
         android:id="@+id/volume_dialog_content"
@@ -39,9 +29,15 @@
         android:layout_height="wrap_content"
         android:orientation="vertical"
         android:paddingBottom="8dp"
-        android:paddingTop="8dp" >
+        android:paddingStart="8dp">
 
         <!-- volume rows added and removed here! :-) -->
+        <LinearLayout
+                android:id="@+id/volume_row_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="@dimen/volume_button_size"
+                android:orientation="vertical"/>
 
         <include layout="@layout/volume_zen_footer" />
 
@@ -49,4 +45,18 @@
         <include layout="@layout/tuner_zen_mode_panel" />
     </LinearLayout>
 
+    <com.android.keyguard.AlphaOptimizedImageButton
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:tools="http://schemas.android.com/tools"
+            android:id="@+id/volume_expand_button"
+            style="@style/VolumeButtons"
+            android:layout_width="@dimen/volume_button_size"
+            android:layout_height="@dimen/volume_button_size"
+            android:clickable="true"
+            android:soundEffectsEnabled="false"
+            android:src="@drawable/ic_volume_collapse_animation"
+            tools:ignore="RtlHardcoded"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"/>
+
 </RelativeLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 91e931d..57bac41 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -13,13 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout 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="wrap_content"
     android:clipChildren="false"
-    android:id="@+id/volume_dialog_row"
-    android:paddingEnd="8dp"
-    android:paddingStart="8dp" >
+    android:id="@+id/volume_dialog_row" >
 
     <TextView
         android:id="@+id/volume_row_header"
@@ -31,7 +30,8 @@
         android:paddingBottom="0dp"
         android:paddingEnd="12dp"
         android:paddingStart="12dp"
-        android:paddingTop="4dp" />
+        android:paddingTop="4dp"
+        android:visibility="gone" />
 
     <com.android.keyguard.AlphaOptimizedImageButton
         android:id="@+id/volume_row_icon"
@@ -55,12 +55,4 @@
         android:paddingEnd="8dp"
         android:paddingStart="8dp" />
 
-    <com.android.keyguard.AlphaOptimizedImageButton
-        android:id="@+id/volume_settings_button"
-        style="@style/VolumeButtons"
-        android:layout_width="@dimen/volume_button_size"
-        android:layout_height="@dimen/volume_button_size"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@id/volume_row_header" />
-
 </RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_zen_footer.xml b/packages/SystemUI/res/layout/volume_zen_footer.xml
index 28447d7..f30023d 100644
--- a/packages/SystemUI/res/layout/volume_zen_footer.xml
+++ b/packages/SystemUI/res/layout/volume_zen_footer.xml
@@ -32,9 +32,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:paddingEnd="8dp"
-        android:paddingStart="8dp" >
+        android:orientation="horizontal" >
 
         <ImageView
             android:id="@+id/volume_zen_icon"
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 681b39e..41cce74 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -18,11 +18,14 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 
@@ -66,4 +69,9 @@
             ViewGroup container) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, windowManager, container);
     }
+
+    public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+            View headsUpScrim, boolean scrimSrcEnabled) {
+        return new ScrimController(scrimBehind, scrimInFront, headsUpScrim, scrimSrcEnabled);
+    }
 }
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 81e1581..d3536a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -17,6 +17,7 @@
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.support.v7.widget.DefaultItemAnimator;
 import android.support.v7.widget.GridLayoutManager;
@@ -42,7 +43,7 @@
  * This adds itself to the status bar window, so it can appear on top of quick settings and
  * *someday* do fancy animations to get into/out of it.
  */
-public class QSCustomizer extends LinearLayout implements AnimatorListener, OnClickListener {
+public class QSCustomizer extends LinearLayout implements OnClickListener {
 
     private final QSDetailClipper mClipper;
 
@@ -94,7 +95,7 @@
             isShown = true;
             mPhoneStatusBar.getStatusBarWindow().addView(this);
             setTileSpecs();
-            mClipper.animateCircularClip(x, y, true, this);
+            mClipper.animateCircularClip(x, y, true, null);
             new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
         }
     }
@@ -102,7 +103,7 @@
     public void hide(int x, int y) {
         if (isShown) {
             isShown = false;
-            mClipper.animateCircularClip(x, y, false, this);
+            mClipper.animateCircularClip(x, y, false, mCollapseAnimationListener);
         }
     }
 
@@ -144,27 +145,19 @@
         }
     }
 
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        if (!isShown) {
-            mPhoneStatusBar.getStatusBarWindow().removeView(this);
+    private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!isShown) {
+                mPhoneStatusBar.getStatusBarWindow().removeView(QSCustomizer.this);
+            }
         }
-    }
 
-    @Override
-    public void onAnimationCancel(Animator animation) {
-        if (!isShown) {
-            mPhoneStatusBar.getStatusBarWindow().removeView(this);
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (!isShown) {
+                mPhoneStatusBar.getStatusBarWindow().removeView(QSCustomizer.this);
+            }
         }
-    }
-
-    @Override
-    public void onAnimationStart(Animator animation) {
-        // Don't care.
-    }
-
-    @Override
-    public void onAnimationRepeat(Animator animation) {
-        // Don't care.
-    }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 315c509..effe581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -35,6 +35,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 /**
@@ -155,6 +156,7 @@
         }
     };
     private float mShadowAlpha = 1.0f;
+    private FakeShadowView mFakeShadow;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -180,6 +182,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
+        mFakeShadow = (FakeShadowView) findViewById(R.id.fake_shadow);
         mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
         mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
         mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
@@ -852,6 +855,14 @@
         }
     }
 
+    @Override
+    public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
+            int outlineTranslation) {
+        mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
+                + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
+                outlineTranslation);
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ea8b75e..a0c63be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2139,15 +2139,21 @@
     protected void updatePublicContentView(Entry entry,
             StatusBarNotification sbn) {
         final RemoteViews publicContentView = entry.cachedPublicContentView;
-        if (publicContentView != null && entry.getPublicContentView() != null) {
+        View inflatedView = entry.getPublicContentView();
+        if (entry.autoRedacted && publicContentView != null && inflatedView != null) {
             final boolean disabledByPolicy =
                     !adminAllowsUnredactedNotifications(entry.notification.getUserId());
-            publicContentView.setTextViewText(android.R.id.title,
-                    mContext.getString(disabledByPolicy
-                            ? com.android.internal.R.string.notification_hidden_by_policy_text
-                            : com.android.internal.R.string.notification_hidden_text));
-            publicContentView.reapply(sbn.getPackageContext(mContext),
-                    entry.getPublicContentView(), mOnClickHandler);
+            String notificationHiddenText = mContext.getString(disabledByPolicy
+                    ? com.android.internal.R.string.notification_hidden_by_policy_text
+                    : com.android.internal.R.string.notification_hidden_text);
+            TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
+            if (titleView != null
+                    && !titleView.getText().toString().equals(notificationHiddenText)) {
+                publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
+                publicContentView.reapply(sbn.getPackageContext(mContext),
+                        inflatedView, mOnClickHandler);
+                entry.row.onNotificationUpdated(entry);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c73e115..c72cec3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -80,6 +80,7 @@
     /** Are we showing the "public" version */
     private boolean mShowingPublic;
     private boolean mSensitive;
+    private boolean mSensitiveHiddenInGeneral;
     private boolean mShowingPublicInitialized;
     private boolean mHideSensitiveForIntrinsicHeight;
 
@@ -1041,8 +1042,9 @@
         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
     }
 
-    public void setSensitive(boolean sensitive) {
+    public void setSensitive(boolean sensitive, boolean hideSensitive) {
         mSensitive = sensitive;
+        mSensitiveHiddenInGeneral = hideSensitive;
     }
 
     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
@@ -1114,7 +1116,8 @@
 
     private void updateClearability() {
         // public versions cannot be dismissed
-        mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
+        mVetoButton.setVisibility(isClearable() && (!mShowingPublic
+                || !mSensitiveHiddenInGeneral) ? View.VISIBLE : View.GONE);
     }
 
     public void setChildrenExpanded(boolean expanded, boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 782a38c..f98e87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -70,6 +70,11 @@
         }
     }
 
+    @Override
+    public float getOutlineAlpha() {
+        return mOutlineAlpha;
+    }
+
     protected void setOutlineRect(RectF rect) {
         if (rect != null) {
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
@@ -80,6 +85,11 @@
         }
     }
 
+    @Override
+    public int getOutlineTranslation() {
+        return mCustomOutline ? mOutlineRect.left : 0;
+    }
+
     protected void setOutlineRect(float left, float top, float right, float bottom) {
         setOutlineRect(true, left, top, right, bottom);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 3176e2e..c9e1cff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -399,6 +399,18 @@
         return false;
     }
 
+    public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
+            int outlineTranslation) {
+    }
+
+    public float getOutlineAlpha() {
+        return 0.0f;
+    }
+
+    public int getOutlineTranslation() {
+        return 0;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 647f0bf..bc85922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -523,7 +523,13 @@
     }
 
     public void setDark(boolean dark, boolean fade, long delay) {
-        if (mDark == dark || mContractedChild == null) return;
+        setDark(dark, fade, delay, false /* force */);
+    }
+
+    public void setDark(boolean dark, boolean fade, long delay, boolean force) {
+        if ((!force && mDark == dark) || mContractedChild == null) {
+            return;
+        }
         mDark = dark;
         dark = dark && !mShowingLegacyBackground;
         if (mVisibleType == VISIBLE_TYPE_CONTRACTED || !dark) {
@@ -571,7 +577,6 @@
         selectLayout(false /* animate */, true /* force */);
         if (mContractedChild != null) {
             mContractedWrapper.notifyContentUpdated(entry.notification);
-            mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
         }
         if (mExpandedChild != null) {
             mExpandedWrapper.notifyContentUpdated(entry.notification);
@@ -579,6 +584,7 @@
         if (mHeadsUpChild != null) {
             mHeadsUpWrapper.notifyContentUpdated(entry.notification);
         }
+        setDark(mDark, false /* animate */, 0 /* delay */, true /* force */);
     }
 
     private void updateSingleLineView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/FakeShadowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/FakeShadowView.java
new file mode 100644
index 0000000..32c26ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/FakeShadowView.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.notification;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.LinearLayout;
+
+import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
+
+/**
+ * A view used to cast a shadow of a certain size on another view
+ */
+public class FakeShadowView extends AlphaOptimizedFrameLayout {
+    public static final float SHADOW_SIBLING_TRESHOLD = 0.1f;
+
+    private View mFakeShadow;
+    private float mOutlineAlpha;
+
+    public FakeShadowView(Context context) {
+        this(context, null);
+    }
+
+    public FakeShadowView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FakeShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FakeShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mFakeShadow = new View(context);
+        mFakeShadow.setVisibility(INVISIBLE);
+        mFakeShadow.setLayoutParams(new LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                (int) (48 * getResources().getDisplayMetrics().density)));
+        mFakeShadow.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRect(0, 0, getWidth(), mFakeShadow.getHeight());
+                outline.setAlpha(mOutlineAlpha);
+            }
+        });
+        addView(mFakeShadow);
+    }
+
+    public void setFakeShadowTranslationZ(float fakeShadowTranslationZ, float outlineAlpha,
+            int shadowYEnd, int outlineTranslation) {
+        if (fakeShadowTranslationZ == 0.0f) {
+            mFakeShadow.setVisibility(INVISIBLE);
+        } else {
+            mFakeShadow.setVisibility(VISIBLE);
+            mFakeShadow.setTranslationZ(fakeShadowTranslationZ);
+            mFakeShadow.setTranslationX(outlineTranslation);
+            mFakeShadow.setTranslationY(shadowYEnd - mFakeShadow.getHeight());
+            if (outlineAlpha != mOutlineAlpha) {
+                mOutlineAlpha = outlineAlpha;
+                mFakeShadow.invalidateOutline();
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b9e1ad2..ee88b00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,7 +16,11 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.ActivityManager;
 import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -42,6 +46,8 @@
  */
 public class KeyguardBouncer {
 
+    final static private String TAG = "KeyguardBouncer";
+
     protected Context mContext;
     protected ViewMediatorCallback mCallback;
     protected LockPatternUtils mLockPatternUtils;
@@ -73,6 +79,11 @@
     }
 
     public void show(boolean resetSecuritySelection) {
+        final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
+        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
+            // In split system user mode, we never unlock system user.
+            return;
+        }
         mFalsingManager.onBouncerShown();
         ensureView();
         if (resetSecuritySelection) {
@@ -84,14 +95,25 @@
             return;
         }
 
-        // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole
-        // Keyguard. If we need to authenticate, show the bouncer.
-        if (!mKeyguardView.dismiss()) {
-            mShowingSoon = true;
-
-            // Split up the work over multiple frames.
-            DejankUtils.postAfterTraversal(mShowRunnable);
+        final int activeUserId = ActivityManager.getCurrentUser();
+        final boolean allowDismissKeyguard =
+                !(UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM)
+                && activeUserId == keyguardUserId;
+        // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) is
+        // set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
+        if (allowDismissKeyguard && mKeyguardView.dismiss()) {
+            return;
         }
+
+        // This condition may indicate an error on Android, so log it.
+        if (!allowDismissKeyguard) {
+            Slog.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " + keyguardUserId);
+        }
+
+        mShowingSoon = true;
+
+        // Split up the work over multiple frames.
+        DejankUtils.postAfterTraversal(mShowRunnable);
     }
 
     private final Runnable mShowRunnable = new Runnable() {
@@ -258,19 +280,8 @@
         return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
     }
 
-    public boolean onMenuPressed() {
-        ensureView();
-        if (mKeyguardView.handleMenuKey()) {
-
-            // We need to show it in case it is secure. If not, it will get dismissed in any case.
-            mRoot.setVisibility(View.VISIBLE);
-            mFalsingManager.onBouncerShown();
-            mKeyguardView.requestFocus();
-            mKeyguardView.onResume();
-            return true;
-        } else {
-            return false;
-        }
+    public boolean shouldDismissOnMenuPressed() {
+        return mKeyguardView.shouldEnableMenuKey();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 1a0acbe..05ae41b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -94,7 +94,7 @@
     private TextView mClockView;
     private View mReserveNotificationSpace;
     private View mQsNavbarScrim;
-    private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    protected NotificationsQuickSettingsContainer mNotificationContainerParent;
     protected NotificationStackScrollLayout mNotificationStackScroller;
     private boolean mAnimateNextTopPaddingChange;
 
@@ -879,7 +879,11 @@
                 mQsTracking = false;
                 mTrackingPointer = -1;
                 trackMovement(event);
-                flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                float fraction = getQsExpansionFraction();
+                if (fraction != 0f || y >= mInitialTouchY) {
+                    flingQsWithCurrentVelocity(y,
+                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                }
                 if (mVelocityTracker != null) {
                     mVelocityTracker.recycle();
                     mVelocityTracker = null;
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 d60ea20..e344df2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -111,6 +111,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.doze.DozeHost;
@@ -771,8 +772,8 @@
         ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
         ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
         View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
-        mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim,
-                mScrimSrcModeEnabled);
+        mScrimController = SystemUIFactory.getInstance().createScrimController(
+                scrimBehind, scrimInFront, headsUpScrim, mScrimSrcModeEnabled);
         mHeadsUpManager.addListener(mScrimController);
         mStackScroller.setScrimController(mScrimController);
         mScrimController.setBackDropView(mBackdrop);
@@ -1415,7 +1416,7 @@
             if (showingPublic) {
                 updatePublicContentView(ent, ent.notification);
             }
-            ent.row.setSensitive(sensitive);
+            ent.row.setSensitive(sensitive, hideSensitive);
             if (ent.autoRedacted && ent.legacy) {
                 // TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
                 // for legacy auto redacted notifications.
@@ -3878,7 +3879,13 @@
     }
 
     public boolean onMenuPressed() {
-        return mState == StatusBarState.KEYGUARD && mStatusBarKeyguardViewManager.onMenuPressed();
+        if (mDeviceInteractive && mState != StatusBarState.SHADE
+                && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed()) {
+            animateCollapsePanels(
+                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            return true;
+        }
+        return false;
     }
 
     public void endAffordanceLaunch() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f310c2c2..fe76ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -56,16 +56,16 @@
     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
 
-    private final ScrimView mScrimBehind;
+    protected final ScrimView mScrimBehind;
     private final ScrimView mScrimInFront;
     private final UnlockMethodCache mUnlockMethodCache;
     private final View mHeadsUpScrim;
 
-    private boolean mKeyguardShowing;
+    protected boolean mKeyguardShowing;
     private float mFraction;
 
     private boolean mDarkenWhileDragging;
-    private boolean mBouncerShowing;
+    protected boolean mBouncerShowing;
     private boolean mWakeAndUnlocking;
     private boolean mAnimateChange;
     private boolean mUpdatePending;
@@ -203,7 +203,7 @@
         mUpdatePending = true;
     }
 
-    private void updateScrims() {
+    protected void updateScrims() {
         if (mAnimateKeyguardFadingOut || mForceHideScrims) {
             setScrimInFrontColor(0f);
             setScrimBehindColor(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7a05b8f..0e84f733 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -509,8 +509,8 @@
         return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
     }
 
-    public boolean onMenuPressed() {
-        return mBouncer.onMenuPressed();
+    public boolean shouldDismissOnMenuPressed() {
+        return mBouncer.shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index f078b53..fe06c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -70,6 +71,8 @@
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 
 /**
@@ -93,7 +96,7 @@
     private static final int INVALID_POINTER = -1;
 
     private ExpandHelper mExpandHelper;
-    private SwipeHelper mSwipeHelper;
+    private NotificationSwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
@@ -277,6 +280,7 @@
     private int mBgColor;
     private float mDimAmount;
     private ValueAnimator mDimAnimator;
+    private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
     private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
@@ -292,6 +296,31 @@
         }
     };
     private ViewGroup mQsContainer;
+    private boolean mContinuousShadowUpdate;
+    private ViewTreeObserver.OnPreDrawListener mShadowUpdater
+            = new ViewTreeObserver.OnPreDrawListener() {
+
+        @Override
+        public boolean onPreDraw() {
+            updateViewShadows();
+            return true;
+        }
+    };
+    private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
+        @Override
+        public int compare(ExpandableView view, ExpandableView otherView) {
+            float endY = view.getTranslationY() + view.getActualHeight();
+            float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
+            if (endY < otherEndY) {
+                return -1;
+            } else if (endY > otherEndY) {
+                return 1;
+            } else {
+                // The two notifications end at the same location
+                return 0;
+            }
+        }
+    };
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -667,6 +696,7 @@
         }
         mSwipedOutViews.add(v);
         mAmbientState.onDragFinished(v);
+        updateContinuousShadowDrawing();
         if (v instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
             if (row.isHeadsUp()) {
@@ -689,6 +719,7 @@
     @Override
     public void onChildSnappedBack(View animView, float targetLeft) {
         mAmbientState.onDragFinished(animView);
+        updateContinuousShadowDrawing();
         if (!mDragAnimPendingChildren.contains(animView)) {
             if (mAnimationsEnabled) {
                 mSnappedBackChildren.add(animView);
@@ -721,6 +752,7 @@
         mFalsingManager.onNotificatonStartDismissing();
         setSwipingInProgress(true);
         mAmbientState.onBeginDrag(v);
+        updateContinuousShadowDrawing();
         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
             mDragAnimPendingChildren.add(v);
             mNeedsAnimation = true;
@@ -2242,6 +2274,7 @@
             setAnimationRunning(true);
             mAnimationEvents.clear();
             updateBackground();
+            updateViewShadows();
         } else {
             applyCurrentState();
         }
@@ -2824,6 +2857,43 @@
         }
         runAnimationFinishedRunnables();
         updateBackground();
+        updateViewShadows();
+    }
+
+    private void updateViewShadows() {
+        // we need to work around an issue where the shadow would not cast between siblings when
+        // their z difference is between 0 and 0.1
+
+        // Lefts first sort by Z difference
+        for (int i = 0; i < getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                mTmpSortedChildren.add(child);
+            }
+        }
+        Collections.sort(mTmpSortedChildren, mViewPositionComparator);
+
+        // Now lets update the shadow for the views
+        ExpandableView previous = null;
+        for (int i = 0; i < mTmpSortedChildren.size(); i++) {
+            ExpandableView expandableView = mTmpSortedChildren.get(i);
+            float translationZ = expandableView.getTranslationZ();
+            float otherZ = previous == null ? translationZ : previous.getTranslationZ();
+            float diff = otherZ - translationZ;
+            if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
+                // There is no fake shadow to be drawn
+                expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
+            } else {
+                float yLocation = previous.getTranslationY() + previous.getActualHeight() -
+                        expandableView.getTranslationY();
+                expandableView.setFakeShadowIntensity(diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
+                        previous.getOutlineAlpha(), (int) yLocation,
+                        previous.getOutlineTranslation());
+            }
+            previous = expandableView;
+        }
+
+        mTmpSortedChildren.clear();
     }
 
     public void goToFullShade(long delay) {
@@ -3045,7 +3115,7 @@
             disableClipOptimization();
         }
         handleDismissAllClipping();
-        if (mCurrIconRow != null & mCurrIconRow.isVisible()) {
+        if (mCurrIconRow != null && mCurrIconRow.isVisible()) {
             mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
         }
     }
@@ -3262,6 +3332,7 @@
                 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
             }
             mAnimationRunning = animationRunning;
+            updateContinuousShadowDrawing();
         }
     }
 
@@ -3549,6 +3620,18 @@
         }
     }
 
+    private void updateContinuousShadowDrawing() {
+        boolean continuousShadowUpdate = mAnimationRunning
+                || !mAmbientState.getDraggedViews().isEmpty();
+        if (continuousShadowUpdate != mContinuousShadowUpdate) {
+            if (continuousShadowUpdate) {
+                getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
+            } else {
+                getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
+            }
+        }
+    }
+
     static class AnimationEvent {
 
         static AnimationFilter[] FILTERS = new AnimationFilter[] {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index e87b363..d78d626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -24,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.notification.FakeShadowView;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -605,20 +606,40 @@
     private void updateZValuesForState(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
-        int childrenOnTop = 0;
+        float childrenOnTop = 0.0f;
         for (int i = childCount - 1; i >= 0; i--) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
             if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
                 // We are in the bottom stack
                 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
-                childViewState.zTranslation = mZBasicHeight
-                        - numItemsAbove * mZDistanceBetweenElements;
+                float zSubtraction;
+                if (numItemsAbove <= 1.0f) {
+                    float factor = 0.2f;
+                    // Lets fade in slower to the threshold to make the shadow fade in look nicer
+                    if (numItemsAbove <= factor) {
+                        zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
+                                * numItemsAbove * (1.0f / factor);
+                    } else {
+                        zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
+                                + (numItemsAbove - factor) * (1.0f / (1.0f - factor))
+                                        * (mZDistanceBetweenElements
+                                                - FakeShadowView.SHADOW_SIBLING_TRESHOLD);
+                    }
+                } else {
+                    zSubtraction = numItemsAbove * mZDistanceBetweenElements;
+                }
+                childViewState.zTranslation = mZBasicHeight - zSubtraction;
             } else if (child.mustStayOnScreen()
                     && childViewState.yTranslation < ambientState.getTopPadding()
                     + ambientState.getStackTranslation()) {
-                // TODO; do this more cleanly
-                childrenOnTop++;
+                if (childrenOnTop != 0.0f) {
+                    childrenOnTop++;
+                } else {
+                    float overlap = ambientState.getTopPadding()
+                            + ambientState.getStackTranslation() - childViewState.yTranslation;
+                    childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
+                }
                 childViewState.zTranslation = mZBasicHeight
                         + childrenOnTop * mZDistanceBetweenElements;
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index fac6338..0089fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -101,7 +101,26 @@
         @Override
         public void run() {
             if (mState != STATE_NO_PIP) {
-                // TODO: check whether PIP task is closed.
+                StackInfo stackInfo = null;
+                try {
+                    stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                    if (stackInfo == null) {
+                        Log.w(TAG, "There is no pinned stack");
+                        closePipInternal(false);
+                        return;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "getStackInfo failed", e);
+                    return;
+                }
+                for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
+                    if (stackInfo.taskIds[i] == mPipTaskId) {
+                        // PIP task is still alive.
+                        return;
+                    }
+                }
+                // PIP task doesn't exist anymore in PINNED_STACK.
+                closePipInternal(true);
             }
         }
     };
@@ -203,12 +222,18 @@
      * Closes PIP (PIPed activity and PIP system UI).
      */
     public void closePip() {
+        closePipInternal(true);
+    }
+
+    private void closePipInternal(boolean removePipStack) {
         mState = STATE_NO_PIP;
         mPipTaskId = TASK_ID_NO_PIP;
-        try {
-            mActivityManager.removeStack(PINNED_STACK_ID);
-        } catch (RemoteException e) {
-            Log.e(TAG, "removeStack failed", e);
+        if (removePipStack) {
+            try {
+                mActivityManager.removeStack(PINNED_STACK_ID);
+            } catch (RemoteException e) {
+                Log.e(TAG, "removeStack failed", e);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 1810c1c..1d5ca04 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
@@ -42,6 +43,7 @@
 import android.provider.Settings.Global;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -49,7 +51,6 @@
 import android.view.View.AccessibilityDelegate;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
@@ -91,39 +92,40 @@
     public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
 
     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
-    private static final int WAIT_FOR_RIPPLE = 200;
 
     private final Context mContext;
     private final H mHandler = new H();
     private final VolumeDialogController mController;
 
-    private final CustomDialog mDialog;
-    private final ViewGroup mDialogView;
-    private final ViewGroup mDialogContentView;
-    private final ImageButton mExpandButton;
-    private final View mSettingsButton;
-    private final List<VolumeRow> mRows = new ArrayList<VolumeRow>();
+    private CustomDialog mDialog;
+    private ViewGroup mDialogView;
+    private ViewGroup mDialogContentView;
+    private ViewGroup mVolumeRowContainer;
+    private ImageButton mExpandButton;
+    private final List<VolumeRow> mRows = new ArrayList<>();
     private final SpTexts mSpTexts;
     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
     private final KeyguardManager mKeyguard;
     private final AudioManager mAudioManager;
-    private final int mExpandButtonAnimationDuration;
-    private final ZenFooter mZenFooter;
+    private int mExpandButtonAnimationDuration;
+    private ZenFooter mZenFooter;
     private final LayoutTransition mLayoutTransition;
     private final Object mSafetyWarningLock = new Object();
     private final Accessibility mAccessibility = new Accessibility();
     private final ColorStateList mActiveSliderTint;
     private final ColorStateList mInactiveSliderTint;
-    private final VolumeDialogMotion mMotion;
+    private VolumeDialogMotion mMotion;
+    private final int mWindowType;
+    private final ZenModeController mZenModeController;
 
     private boolean mShowing;
     private boolean mExpanded;
+
     private int mActiveStream;
     private boolean mShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
     private State mState;
-    private int mExpandButtonRes;
     private boolean mExpandButtonAnimationRunning;
     private SafetyWarningDialog mSafetyWarning;
     private Callback mCallback;
@@ -131,22 +133,43 @@
     private boolean mPendingRecheckAll;
     private long mCollapseTime;
     private boolean mHovering = false;
-    private int mLastActiveStream;
+    private int mDensity;
 
     private boolean mShowFullZen;
-    private final TunerZenModePanel mZenPanel;
+    private TunerZenModePanel mZenPanel;
 
     public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
             ZenModeController zenModeController, Callback callback) {
         mContext = context;
         mController = controller;
         mCallback = callback;
+        mWindowType = windowType;
+        mZenModeController = zenModeController;
         mSpTexts = new SpTexts(mContext);
         mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mActiveSliderTint = loadColorStateList(R.color.system_accent_color);
+        mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+        mLayoutTransition = new LayoutTransition();
+        mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
 
+        initDialog();
+
+        mAccessibility.init();
+
+        controller.addCallback(mControllerCallbackH, mHandler);
+        controller.getState();
+        TunerService.get(mContext).addTunable(this, SHOW_FULL_ZEN);
+
+        final Configuration currentConfig = mContext.getResources().getConfiguration();
+        mDensity = currentConfig.densityDpi;
+    }
+
+    private void initDialog() {
         mDialog = new CustomDialog(mContext);
 
+        mHovering = false;
+        mShowing = false;
         final Window window = mDialog.getWindow();
         window.requestFeature(Window.FEATURE_NO_TITLE);
         window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
@@ -160,7 +183,7 @@
         mDialog.setCanceledOnTouchOutside(true);
         final Resources res = mContext.getResources();
         final WindowManager.LayoutParams lp = window.getAttributes();
-        lp.type = windowType;
+        lp.type = mWindowType;
         lp.format = PixelFormat.TRANSLUCENT;
         lp.setTitle(VolumeDialog.class.getSimpleName());
         lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
@@ -170,8 +193,7 @@
         window.setAttributes(lp);
         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
 
-        mActiveSliderTint = loadColorStateList(R.color.system_accent_color);
-        mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setOnHoverListener(new View.OnHoverListener() {
@@ -185,56 +207,53 @@
             }
         });
         mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
+        mVolumeRowContainer =
+                (ViewGroup) mDialogContentView.findViewById(R.id.volume_row_container);
+        mExpanded = false;
         mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
         mExpandButton.setOnClickListener(mClickExpand);
         updateWindowWidthH();
         updateExpandButtonH();
-        mLayoutTransition = new LayoutTransition();
-        mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
+
         mDialogContentView.setLayoutTransition(mLayoutTransition);
         mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
                 new VolumeDialogMotion.Callback() {
-            @Override
-            public void onAnimatingChanged(boolean animating) {
-                if (animating) return;
-                if (mPendingStateChanged) {
-                    mHandler.sendEmptyMessage(H.STATE_CHANGED);
-                    mPendingStateChanged = false;
-                }
-                if (mPendingRecheckAll) {
-                    mHandler.sendEmptyMessage(H.RECHECK_ALL);
-                    mPendingRecheckAll = false;
-                }
-            }
-        });
+                    @Override
+                    public void onAnimatingChanged(boolean animating) {
+                        if (animating) return;
+                        if (mPendingStateChanged) {
+                            mHandler.sendEmptyMessage(H.STATE_CHANGED);
+                            mPendingStateChanged = false;
+                        }
+                        if (mPendingRecheckAll) {
+                            mHandler.sendEmptyMessage(H.RECHECK_ALL);
+                            mPendingRecheckAll = false;
+                        }
+                    }
+                });
 
-        addRow(AudioManager.STREAM_RING,
-                R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
-        addRow(AudioManager.STREAM_MUSIC,
-                R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
-        addRow(AudioManager.STREAM_ALARM,
-                R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
-        addRow(AudioManager.STREAM_VOICE_CALL,
-                R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
-        addRow(AudioManager.STREAM_BLUETOOTH_SCO,
-                R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
-        addRow(AudioManager.STREAM_SYSTEM,
-                R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
-
-        mSettingsButton = mDialog.findViewById(R.id.volume_settings_button);
-        mSettingsButton.setOnClickListener(mClickSettings);
+        if (mRows.isEmpty()) {
+            addRow(AudioManager.STREAM_RING,
+                    R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
+            addRow(AudioManager.STREAM_MUSIC,
+                    R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true);
+            addRow(AudioManager.STREAM_ALARM,
+                    R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
+            addRow(AudioManager.STREAM_VOICE_CALL,
+                    R.drawable.ic_volume_voice, R.drawable.ic_volume_voice, false);
+            addRow(AudioManager.STREAM_BLUETOOTH_SCO,
+                    R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
+            addRow(AudioManager.STREAM_SYSTEM,
+                    R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+        } else {
+            addExistingRows();
+        }
         mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
         mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
-        mZenFooter.init(zenModeController);
+        mZenFooter.init(mZenModeController);
         mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
-        mZenPanel.init(zenModeController);
+        mZenPanel.init(mZenModeController);
         mZenPanel.setCallback(mZenPanelCallback);
-
-        mAccessibility.init();
-
-        controller.addCallback(mControllerCallbackH, mHandler);
-        controller.getState();
-        TunerService.get(mContext).addTunable(this, SHOW_FULL_ZEN);
     }
 
     @Override
@@ -285,46 +304,38 @@
     }
 
     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
-        final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);
+        VolumeRow row = new VolumeRow();
+        initRow(row, stream, iconRes, iconMuteRes, important);
         if (!mRows.isEmpty()) {
-            final View v = new View(mContext);
-            v.setId(android.R.id.background);
-            final int h = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
-            final LinearLayout.LayoutParams lp =
-                    new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
-            mDialogContentView.addView(v, mDialogContentView.getChildCount() - 2, lp);
-            row.space = v;
+            addSpacer(row);
         }
-        row.settingsButton.addOnLayoutChangeListener(new OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                final boolean moved = mLastActiveStream != mActiveStream ||
-                        oldLeft != left || oldTop != top;
-                if (D.BUG) Log.d(TAG, "onLayoutChange moved=" + moved
-                        + " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
-                        + "," + mLastActiveStream
-                        + " new=" + new Rect(left,top,right,bottom).toShortString()
-                        + "," + mActiveStream);
-                mLastActiveStream = mActiveStream;
-                if (moved) {
-                    for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
-                        final View c = mDialogContentView.getChildAt(i);
-                        if (!c.isShown()) continue;
-                        if (c == row.view) {
-                            repositionExpandAnim(row);
-                        }
-                        return;
-                    }
-                }
-            }
-        });
-        // add new row just before the footer
-        mDialogContentView.addView(row.view, mDialogContentView.getChildCount() - 2);
+        mVolumeRowContainer.addView(row.view);
         mRows.add(row);
     }
 
+    private void addExistingRows() {
+        int N = mRows.size();
+        for (int i = 0; i < N; i++) {
+            final VolumeRow row = mRows.get(i);
+            initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
+            if (i > 0) {
+                addSpacer(row);
+            }
+            mVolumeRowContainer.addView(row.view);
+        }
+    }
+
+    private void addSpacer(VolumeRow row) {
+        final View v = new View(mContext);
+        v.setId(android.R.id.background);
+        final int h = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.volume_slider_interspacing);
+        final LinearLayout.LayoutParams lp =
+                new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, h);
+        mVolumeRowContainer.addView(v, lp);
+        row.space = v;
+    }
+
     private boolean isAttached() {
         return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
     }
@@ -345,18 +356,6 @@
         return null;
     }
 
-    private void repositionExpandAnim(VolumeRow row) {
-        final int[] loc = new int[2];
-        row.settingsButton.getLocationInWindow(loc);
-        final MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
-        final int x = loc[0] - mlp.leftMargin;
-        final int y = loc[1] - mlp.topMargin;
-        if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y);
-        mExpandButton.setTranslationX(x);
-        mExpandButton.setTranslationY(y);
-        mExpandButton.setTag((Integer) y);
-    }
-
     public void dump(PrintWriter writer) {
         writer.println(VolumeDialog.class.getSimpleName() + " state:");
         writer.print("  mShowing: "); writer.println(mShowing);
@@ -374,8 +373,8 @@
     }
 
     @SuppressLint("InflateParams")
-    private VolumeRow initRow(final int stream, int iconRes, int iconMuteRes, boolean important) {
-        final VolumeRow row = new VolumeRow();
+    private void initRow(final VolumeRow row, final int stream, int iconRes, int iconMuteRes,
+            boolean important) {
         row.stream = stream;
         row.iconRes = iconRes;
         row.iconMuteRes = iconMuteRes;
@@ -442,9 +441,6 @@
                 row.userAttempt = 0;  // reset the grace period, slider should update immediately
             }
         });
-        row.settingsButton = (ImageButton) row.view.findViewById(R.id.volume_settings_button);
-        row.settingsButton.setOnClickListener(mClickSettings);
-        return row;
     }
 
     public void destroy() {
@@ -573,8 +569,6 @@
         if (mExpandButtonAnimationRunning && isAttached()) return;
         final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
                 : R.drawable.ic_volume_expand_animation;
-        if (res == mExpandButtonRes) return;
-        mExpandButtonRes = res;
         if (hasTouchFeature()) {
             mExpandButton.setImageResource(res);
         } else {
@@ -606,16 +600,6 @@
             final boolean visible = isVisibleH(row, isActive);
             Util.setVisOrGone(row.view, visible);
             Util.setVisOrGone(row.space, visible && mExpanded);
-            final int expandButtonRes = mExpanded ? R.drawable.ic_volume_settings : 0;
-            if (expandButtonRes != row.cachedExpandButtonRes) {
-                row.cachedExpandButtonRes = expandButtonRes;
-                if (expandButtonRes == 0) {
-                    row.settingsButton.setImageDrawable(null);
-                } else {
-                    row.settingsButton.setImageResource(expandButtonRes);
-                }
-            }
-            Util.setVisOrInvis(row.settingsButton, false);
             updateVolumeRowHeaderVisibleH(row);
             row.header.setAlpha(mExpanded && isActive ? 1 : 0.5f);
             updateVolumeRowSliderTintH(row, isActive);
@@ -629,8 +613,8 @@
             if (row.ss == null || !row.ss.dynamic) continue;
             if (!mDynamic.get(row.stream)) {
                 mRows.remove(i);
-                mDialogContentView.removeView(row.view);
-                mDialogContentView.removeView(row.space);
+                mVolumeRowContainer.removeView(row.view);
+                mVolumeRowContainer.removeView(row.space);
             }
         }
     }
@@ -911,6 +895,12 @@
 
         @Override
         public void onConfigurationChanged() {
+            Configuration newConfig = mContext.getResources().getConfiguration();
+            final int density = newConfig.densityDpi;
+            if (density != mDensity) {
+                mDialog.dismiss();
+                initDialog();
+            }
             updateWindowWidthH();
             mSpTexts.update();
             mZenFooter.onConfigurationChanged();
@@ -963,21 +953,6 @@
         }
     };
 
-    private final OnClickListener mClickSettings = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            mSettingsButton.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    Events.writeEvent(mContext, Events.EVENT_SETTINGS_CLICK);
-                    if (mCallback != null) {
-                        mCallback.onSettingsClicked();
-                    }
-                }
-            }, WAIT_FOR_RIPPLE);
-        }
-    };
-
     private final class H extends Handler {
         private static final int SHOW = 1;
         private static final int DISMISS = 2;
@@ -1155,7 +1130,6 @@
         private TextView header;
         private ImageButton icon;
         private SeekBar slider;
-        private ImageButton settingsButton;
         private int stream;
         private StreamState ss;
         private long userAttempt;  // last user-driven slider change
@@ -1168,12 +1142,10 @@
         private ColorStateList cachedSliderTint;
         private int iconState;  // from Events
         private boolean cachedShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS;
-        private int cachedExpandButtonRes;
         private int lastAudibleLevel = 1;
     }
 
     public interface Callback {
-        void onSettingsClicked();
         void onZenSettingsClicked();
         void onZenPrioritySettingsClicked();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index d7635ad..3d33809 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -168,11 +168,6 @@
 
     private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() {
         @Override
-        public void onSettingsClicked() {
-            startSettings(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
-        }
-
-        @Override
         public void onZenSettingsClicked() {
             startSettings(ZenModePanel.ZEN_SETTINGS);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
index 04339eb..bbb70ed 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
@@ -43,7 +43,7 @@
     public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
     public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
 
-    public static final boolean DEFAULT_SHOW_HEADERS = true;
+    public static final boolean DEFAULT_SHOW_HEADERS = false;
     public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
     public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index a03e7f7..c06b63b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -139,9 +139,8 @@
     }
 
     public void onConfigurationChanged() {
-        mEndNowButton.setText(mContext.getString(R.string.volume_zen_end_now));
-        mSpTexts.update();
         Util.setText(mEndNowButton, mContext.getString(R.string.volume_zen_end_now));
+        mSpTexts.update();
     }
 
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 3f3f851..cd31b17 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -369,5 +369,44 @@
     // Logged when the user saves a modification to notification importance. Negative numbers
     // indicate the user lowered the importance; positive means they increased it.
     ACTION_SAVE_IMPORTANCE = 291;
+
+    // Interactive bug report initiated from power menu.
+    ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE = 292;
+
+    // Full bug report initiated from power menu.
+    ACTION_BUGREPORT_FROM_POWER_MENU_FULL = 293;
+
+    // Interactive bug report initiated from Settings.
+    ACTION_BUGREPORT_FROM_SETTINGS_INTERACTIVE = 294;
+
+    // Full bug report initiated from Settings.
+    ACTION_BUGREPORT_FROM_SETTINGS_FULL = 295;
+
+    // Bug report canceled using system notification.
+    ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL = 296;
+
+    // Bug report details screen open using system notification.
+    ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS = 297;
+
+    // Additional Bug report screen shot taken using system notification.
+    ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT = 298;
+
+    // Bug report shared by user using system notification.
+    ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE = 299;
+
+    // User changed bug report name using the details screen.
+    ACTION_BUGREPORT_DETAILS_NAME_CHANGED = 300;
+
+    // User changed bug report title using the details screen.
+    ACTION_BUGREPORT_DETAILS_TITLE_CHANGED = 301;
+
+    // User changed bug report description using the details screen.
+    ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED = 302;
+
+    // Changes made on bug report details screen were saved by user.
+    ACTION_BUGREPORT_DETAILS_SAVED = 303;
+
+    // Changes made on bug report details screen were canceled by user.
+    ACTION_BUGREPORT_DETAILS_CANCELED = 304;
   }
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2b52799..f537d18 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -353,7 +353,10 @@
     }
 
     private void onPackageBroadcastReceived(Intent intent, int userId) {
-        if (!mUserManager.isUserUnlocked(userId)) return;
+        if (!mUserManager.isUserUnlocked(userId) ||
+                isProfileWithLockedParent(userId)) {
+            return;
+        }
 
         final String action = intent.getAction();
         boolean added = false;
@@ -435,7 +438,10 @@
      * due to user not being available and package suspension.
      */
     private void reloadWidgetsMaskedStateForUser(int userId) {
-        if (!mUserManager.isUserUnlocked(userId)) return;
+        if (!mUserManager.isUserUnlocked(userId) ||
+                isProfileWithLockedParent(userId)) {
+            return;
+        }
         synchronized (mLock) {
             reloadWidgetPackageSuspensionMaskedStateLocked(userId);
             List<UserInfo> profiles = mUserManager.getEnabledProfiles(userId);
@@ -606,7 +612,10 @@
             throw new IllegalStateException(
                     "User " + userId + " must be unlocked for widgets to be available");
         }
-
+        if (isProfileWithLockedParent(userId)) {
+            throw new IllegalStateException(
+                    "Profile " + userId + " must have unlocked parent");
+        }
         final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
 
         // Careful lad, we may have already loaded the state for some
@@ -2458,6 +2467,9 @@
     }
 
     private void onUserUnlocked(int userId) {
+        if (isProfileWithLockedParent(userId)) {
+            return;
+        }
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(userId);
             reloadWidgetsMaskedStateForUser(userId);
@@ -3306,6 +3318,23 @@
         }
     }
 
+    private boolean isProfileWithLockedParent(int userId) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            UserInfo userInfo = mUserManager.getUserInfo(userId);
+            if (userInfo != null && userInfo.isManagedProfile()) {
+                UserInfo parentInfo = mUserManager.getProfileParent(userId);
+                if (parentInfo != null
+                        && !mUserManager.isUserUnlocked(parentInfo.getUserHandle())) {
+                    return true;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return false;
+    }
+
     private boolean isProfileWithUnlockedParent(int userId) {
         UserInfo userInfo = mUserManager.getUserInfo(userId);
         if (userInfo != null && userInfo.isManagedProfile()) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 6c19c38..62fa7d5 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -132,7 +132,8 @@
     private Intent mLightIdleIntent;
     private Display mCurDisplay;
     private AnyMotionDetector mAnyMotionDetector;
-    private boolean mEnabled;
+    private boolean mLightEnabled;
+    private boolean mDeepEnabled;
     private boolean mForceIdle;
     private boolean mScreenOn;
     private boolean mCharging;
@@ -179,7 +180,7 @@
     private static final int LIGHT_STATE_IDLE = 2;
     /** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */
     private static final int LIGHT_STATE_IDLE_MAINTENANCE = 3;
-    /** Device light idle state is overriden, now applying full doze state. */
+    /** Device light idle state is overriden, now applying deep doze state. */
     private static final int LIGHT_STATE_OVERRIDE = 4;
     private static String lightStateToString(int state) {
         switch (state) {
@@ -288,8 +289,8 @@
     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 static final int EVENT_DEEP_IDLE = 4;
+    private static final int EVENT_DEEP_MAINTENANCE = 5;
 
     private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
     private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
@@ -835,13 +836,13 @@
                 case MSG_REPORT_IDLE_ON:
                 case MSG_REPORT_IDLE_ON_LIGHT: {
                     EventLogTags.writeDeviceIdleOnStart();
-                    final boolean fullChanged;
+                    final boolean deepChanged;
                     final boolean lightChanged;
                     if (msg.what == MSG_REPORT_IDLE_ON) {
-                        fullChanged = mLocalPowerManager.setDeviceIdleMode(true);
+                        deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
                         lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                     } else {
-                        fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                        deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                         lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
                     }
                     try {
@@ -851,7 +852,7 @@
                                 : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
                     } catch (RemoteException e) {
                     }
-                    if (fullChanged) {
+                    if (deepChanged) {
                         getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                     }
                     if (lightChanged) {
@@ -861,7 +862,7 @@
                 } break;
                 case MSG_REPORT_IDLE_OFF: {
                     EventLogTags.writeDeviceIdleOffStart("unknown");
-                    final boolean fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                     final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                     try {
                         mNetworkPolicyManager.setDeviceIdleMode(false);
@@ -869,7 +870,7 @@
                                 null, Process.myUid());
                     } catch (RemoteException e) {
                     }
-                    if (fullChanged) {
+                    if (deepChanged) {
                         incActiveIdleOps();
                         getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
                                 null, mIdleStartedDoneReceiver, null, 0, null, null);
@@ -889,7 +890,7 @@
                     int activeUid = msg.arg1;
                     EventLogTags.writeDeviceIdleOffStart(
                             activeReason != null ? activeReason : "unknown");
-                    final boolean fullChanged = mLocalPowerManager.setDeviceIdleMode(false);
+                    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                     final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                     try {
                         mNetworkPolicyManager.setDeviceIdleMode(false);
@@ -897,7 +898,7 @@
                                 activeReason, activeUid);
                     } catch (RemoteException e) {
                     }
-                    if (fullChanged) {
+                    if (deepChanged) {
                         getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                     }
                     if (lightChanged) {
@@ -1102,7 +1103,7 @@
         final PackageManager pm = getContext().getPackageManager();
 
         synchronized (this) {
-            mEnabled = getContext().getResources().getBoolean(
+            mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                     com.android.internal.R.bool.config_enableAutoPowerModes);
             SystemConfig sysConfig = SystemConfig.getInstance();
             ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
@@ -1550,17 +1551,17 @@
 
     void becomeInactiveIfAppropriateLocked() {
         if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
-        if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled) {
+        if ((!mScreenOn && !mCharging) || mForceIdle) {
             // Screen has turned off; we are now going to become inactive and start
             // waiting to see if we will ultimately go idle.
-            if (mState == STATE_ACTIVE) {
+            if (mState == STATE_ACTIVE && mDeepEnabled) {
                 mState = STATE_INACTIVE;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                 resetIdleManagementLocked();
                 scheduleAlarmLocked(mInactiveTimeout, false);
                 EventLogTags.writeDeviceIdle(mState, "no activity");
             }
-            if (mLightState == LIGHT_STATE_ACTIVE) {
+            if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
                 mLightState = LIGHT_STATE_INACTIVE;
                 if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
                 resetLightIdleManagementLocked();
@@ -1594,7 +1595,7 @@
 
     void stepLightIdleStateLocked(String reason) {
         if (mLightState == LIGHT_STATE_OVERRIDE) {
-            // If we are already in full device idle mode, then
+            // If we are already in deep device idle mode, then
             // there is nothing left to do for light mode.
             return;
         }
@@ -1731,7 +1732,7 @@
                     cancelLightAlarmLocked();
                 }
                 EventLogTags.writeDeviceIdle(mState, reason);
-                addEvent(EVENT_FULL_IDLE);
+                addEvent(EVENT_DEEP_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
             case STATE_IDLE:
@@ -1744,7 +1745,7 @@
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                 mState = STATE_IDLE_MAINTENANCE;
                 EventLogTags.writeDeviceIdle(mState, reason);
-                addEvent(EVENT_FULL_MAINTENANCE);
+                addEvent(EVENT_DEEP_MAINTENANCE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
@@ -1877,7 +1878,7 @@
             becomeInactive = true;
         }
         if (mLightState == LIGHT_STATE_OVERRIDE) {
-            // We went out of light idle mode because we had started full idle mode...  let's
+            // We went out of light idle mode because we had started deep idle mode...  let's
             // now go back and reset things so we resume light idling if appropriate.
             mLightState = STATE_ACTIVE;
             EventLogTags.writeDeviceIdleLight(mLightState, type);
@@ -2183,11 +2184,11 @@
         pw.println("  force-idle");
         pw.println("    Force directly into idle mode, regardless of other device state.");
         pw.println("    Use \"step\" to get out.");
-        pw.println("  disable");
+        pw.println("  disable [light|deep|all]");
         pw.println("    Completely disable device idle mode.");
-        pw.println("  enable");
+        pw.println("  enable [light|deep|all]");
         pw.println("    Re-enable device idle mode after it had previously been disabled.");
-        pw.println("  enabled");
+        pw.println("  enabled [light|deep|all]");
         pw.println("    Print 1 if device idle mode is currently enabled, else 0.");
         pw.println("  whitelist");
         pw.println("    Print currently whitelisted apps.");
@@ -2247,7 +2248,7 @@
             synchronized (this) {
                 long token = Binder.clearCallingIdentity();
                 try {
-                    if (!mEnabled) {
+                    if (!mDeepEnabled) {
                         pw.println("Unable to go idle; not enabled");
                         return -1;
                     }
@@ -2274,11 +2275,32 @@
                     null);
             synchronized (this) {
                 long token = Binder.clearCallingIdentity();
+                String arg = shell.getNextArg();
                 try {
-                    if (mEnabled) {
-                        mEnabled = false;
-                        becomeActiveLocked("disabled", Process.myUid());
-                        pw.println("Idle mode disabled");
+                    boolean becomeActive = false;
+                    boolean valid = false;
+                    if (arg == null || "deep".equals(arg) || "all".equals(arg)) {
+                        valid = true;
+                        if (mDeepEnabled) {
+                            mDeepEnabled = false;
+                            becomeActive = true;
+                            pw.println("Deep idle mode disabled");
+                        }
+                    }
+                    if (arg == null || "light".equals(arg) || "all".equals(arg)) {
+                        valid = true;
+                        if (mLightEnabled) {
+                            mLightEnabled = false;
+                            becomeActive = true;
+                            pw.println("Light idle mode disabled");
+                        }
+                    }
+                    if (becomeActive) {
+                        becomeActiveLocked((arg == null ? "all" : arg) + "-disabled",
+                                Process.myUid());
+                    }
+                    if (!valid) {
+                        pw.println("Unknown idle mode: " + arg);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -2289,12 +2311,31 @@
                     null);
             synchronized (this) {
                 long token = Binder.clearCallingIdentity();
+                String arg = shell.getNextArg();
                 try {
-                    exitForceIdleLocked();
-                    if (!mEnabled) {
-                        mEnabled = true;
+                    boolean becomeInactive = false;
+                    boolean valid = false;
+                    if (arg == null || "deep".equals(arg) || "all".equals(arg)) {
+                        valid = true;
+                        if (!mDeepEnabled) {
+                            mDeepEnabled = true;
+                            becomeInactive = true;
+                            pw.println("Deep idle mode enabled");
+                        }
+                    }
+                    if (arg == null || "light".equals(arg) || "all".equals(arg)) {
+                        valid = true;
+                        if (!mLightEnabled) {
+                            mLightEnabled = true;
+                            becomeInactive = true;
+                            pw.println("Light idle mode enable");
+                        }
+                    }
+                    if (becomeInactive) {
                         becomeInactiveIfAppropriateLocked();
-                        pw.println("Idle mode enabled");
+                    }
+                    if (!valid) {
+                        pw.println("Unknown idle mode: " + arg);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -2302,7 +2343,16 @@
             }
         } else if ("enabled".equals(cmd)) {
             synchronized (this) {
-                pw.println(mEnabled ? "1" : " 0");
+                String arg = shell.getNextArg();
+                if (arg == null || "all".equals(arg)) {
+                    pw.println(mDeepEnabled && mLightEnabled ? "1" : 0);
+                } else if ("deep".equals(arg)) {
+                    pw.println(mDeepEnabled ? "1" : 0);
+                } else if ("light".equals(arg)) {
+                    pw.println(mLightEnabled ? "1" : 0);
+                } else {
+                    pw.println("Unknown idle mode: " + arg);
+                }
             }
         } else if ("whitelist".equals(cmd)) {
             long token = Binder.clearCallingIdentity();
@@ -2441,8 +2491,8 @@
                         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;
+                        case EVENT_DEEP_IDLE:           label = "  deep-idle"; break;
+                        case EVENT_DEEP_MAINTENANCE:    label = " deep-maint"; break;
                         default:                        label = "         ??"; break;
                     }
                     pw.print("    ");
@@ -2519,7 +2569,8 @@
                 }
             }
 
-            pw.print("  mEnabled="); pw.println(mEnabled);
+            pw.print("  mLightEnabled="); pw.print(mLightEnabled);
+            pw.print(" mDeepEnabled="); pw.println(mDeepEnabled);
             pw.print("  mForceIdle="); pw.println(mForceIdle);
             pw.print("  mMotionSensor="); pw.println(mMotionSensor);
             pw.print("  mCurDisplay="); pw.println(mCurDisplay);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index d1de7e5..5ba8bd5 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1458,8 +1458,7 @@
         return null;
     }
 
-    @Override
-    public InputBindResult startInput(
+    private InputBindResult startInput(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
             IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
             int controlFlags) {
@@ -2197,7 +2196,19 @@
     }
 
     @Override
-    public InputBindResult windowGainedFocus(
+    public InputBindResult startInputOrWindowGainedFocus(
+            /* @InputMethodClient.StartInputReason */ final int startInputReason,
+            IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+            int windowFlags, EditorInfo attribute, IInputContext inputContext) {
+        if (windowToken != null) {
+            return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
+                    softInputMode, windowFlags, attribute, inputContext);
+        } else {
+            return startInput(startInputReason, client, inputContext, attribute, controlFlags);
+        }
+    }
+
+    private InputBindResult windowGainedFocus(
             /* @InputMethodClient.StartInputReason */ final int startInputReason,
             IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
             int windowFlags, EditorInfo attribute, IInputContext inputContext) {
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
new file mode 100644
index 0000000..d237fe7
--- /dev/null
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import android.content.Context;
+import android.os.IRecoverySystem;
+import android.os.IRecoverySystemProgressListener;
+import android.os.RecoverySystem;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * The recovery system service is responsible for coordinating recovery related
+ * functions on the device. It sets up (or clears) the bootloader control block
+ * (BCB), which will be read by the bootloader and the recovery image. It also
+ * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
+ * /data partition so that it can be accessed under the recovery image.
+ */
+public final class RecoverySystemService extends SystemService {
+    private static final String TAG = "RecoverySystemService";
+    private static final boolean DEBUG = false;
+
+    // A pipe file to monitor the uncrypt progress.
+    private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
+    // Temporary command file to communicate between the system server and uncrypt.
+    private static final String COMMAND_FILE = "/cache/recovery/command";
+
+    private Context mContext;
+
+    public RecoverySystemService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.RECOVERY_SERVICE, new BinderService());
+    }
+
+    private final class BinderService extends IRecoverySystem.Stub {
+        @Override // Binder call
+        public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
+            if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
+
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+            // Write the filename into UNCRYPT_PACKAGE_FILE to be read by
+            // uncrypt.
+            RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
+
+            try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) {
+                uncryptFile.write(filename + "\n");
+            } catch (IOException e) {
+                Slog.e(TAG, "IOException when writing \"" + RecoverySystem.UNCRYPT_PACKAGE_FILE +
+                        "\": " + e.getMessage());
+                return false;
+            }
+
+            // Create the status pipe file to communicate with uncrypt.
+            new File(UNCRYPT_STATUS_FILE).delete();
+            try {
+                Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
+            } catch (ErrnoException e) {
+                Slog.e(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE +
+                        "\": " + e.getMessage());
+                return false;
+            }
+
+            // Trigger uncrypt via init.
+            SystemProperties.set("ctl.start", "uncrypt");
+
+            // Read the status from the pipe.
+            try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
+                int lastStatus = Integer.MIN_VALUE;
+                while (true) {
+                    String str = reader.readLine();
+                    try {
+                        int status = Integer.parseInt(str);
+
+                        // Avoid flooding the log with the same message.
+                        if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
+                            continue;
+                        }
+                        lastStatus = status;
+
+                        if (status >= 0 && status <= 100) {
+                            // Update status
+                            Slog.i(TAG, "uncrypt read status: " + status);
+                            if (listener != null) {
+                                try {
+                                    listener.onProgress(status);
+                                } catch (RemoteException unused) {
+                                    Slog.w(TAG, "RemoteException when posting progress");
+                                }
+                            }
+                            if (status == 100) {
+                                Slog.i(TAG, "uncrypt successfully finished.");
+                                break;
+                            }
+                        } else {
+                            // Error in /system/bin/uncrypt.
+                            Slog.e(TAG, "uncrypt failed with status: " + status);
+                            return false;
+                        }
+                    } catch (NumberFormatException unused) {
+                        Slog.e(TAG, "uncrypt invalid status received: " + str);
+                        return false;
+                    }
+                }
+            } catch (IOException unused) {
+                Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override // Binder call
+        public boolean clearBcb() {
+            if (DEBUG) Slog.d(TAG, "clearBcb");
+            return setupOrClearBcb(false, null);
+        }
+
+        @Override // Binder call
+        public boolean setupBcb(String command) {
+            if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
+            return setupOrClearBcb(true, command);
+        }
+
+        private boolean setupOrClearBcb(boolean isSetup, String command) {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+            if (isSetup) {
+                // Set up the command file to be read by uncrypt.
+                try (FileWriter commandFile = new FileWriter(COMMAND_FILE)) {
+                    commandFile.write(command + "\n");
+                } catch (IOException e) {
+                    Slog.e(TAG, "IOException when writing \"" + COMMAND_FILE +
+                            "\": " + e.getMessage());
+                    return false;
+                }
+            }
+
+            // Create the status pipe file to communicate with uncrypt.
+            new File(UNCRYPT_STATUS_FILE).delete();
+            try {
+                Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
+            } catch (ErrnoException e) {
+                Slog.e(TAG, "ErrnoException when creating named pipe \"" +
+                        UNCRYPT_STATUS_FILE + "\": " + e.getMessage());
+                return false;
+            }
+
+            if (isSetup) {
+                SystemProperties.set("ctl.start", "setup-bcb");
+            } else {
+                SystemProperties.set("ctl.start", "clear-bcb");
+            }
+
+            // Read the status from the pipe.
+            try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
+                while (true) {
+                    String str = reader.readLine();
+                    try {
+                        int status = Integer.parseInt(str);
+
+                        if (status == 100) {
+                            Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
+                                    " bcb successfully finished.");
+                            break;
+                        } else {
+                            // Error in /system/bin/uncrypt.
+                            Slog.e(TAG, "uncrypt failed with status: " + status);
+                            return false;
+                        }
+                    } catch (NumberFormatException unused) {
+                        Slog.e(TAG, "uncrypt invalid status received: " + str);
+                        return false;
+                    }
+                }
+            } catch (IOException unused) {
+                Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
+                return false;
+            }
+
+            // Delete the command file as we don't need it anymore.
+            new File(COMMAND_FILE).delete();
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8c04fbc..29608dd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1894,7 +1894,7 @@
             case SYSTEM_USER_UNLOCK_MSG: {
                 final int userId = msg.arg1;
                 mSystemServiceManager.unlockUser(userId);
-                mRecentTasks.cleanupLocked(userId);
+                mRecentTasks.loadUserRecentsLocked(userId);
                 installEncryptionUnawareProviders(userId);
                 break;
             }
@@ -2541,7 +2541,7 @@
     }
 
     void onUserStoppedLocked(int userId) {
-        mRecentTasks.unloadUserRecentsLocked(userId);
+        mRecentTasks.unloadUserDataFromMemoryLocked(userId);
     }
 
     public void initPowerManagement() {
@@ -8739,6 +8739,10 @@
                     android.Manifest.permission.GET_DETAILED_TASKS)
                     == PackageManager.PERMISSION_GRANTED;
 
+            if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
+                Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
+                return Collections.emptyList();
+            }
             mRecentTasks.loadUserRecentsLocked(userId);
 
             final int recentsCount = mRecentTasks.size();
@@ -12560,7 +12564,7 @@
             // Make sure we have the current profile info, since it is needed for security checks.
             mUserController.onSystemReady();
 
-            mRecentTasks.onSystemReady();
+            mRecentTasks.onSystemReadyLocked();
             // Check to see if there are any update receivers to run.
             if (!mDidUpdate) {
                 if (mWaitingUpdate) {
@@ -17212,6 +17216,15 @@
                     ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
                     mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
                     break;
+                case android.hardware.Camera.ACTION_NEW_PICTURE:
+                case android.hardware.Camera.ACTION_NEW_VIDEO:
+                    // These broadcasts are no longer allowed by the system, since they can
+                    // cause significant thrashing at a crictical point (using the camera).
+                    // Apps should use JobScehduler to monitor for media provider changes.
+                    Slog.w(TAG, action + " no longer allowed; dropping from "
+                            + UserHandle.formatUid(callingUid));
+                    // Lie; we don't want to crash the app.
+                    return ActivityManager.BROADCAST_SUCCESS;
             }
         }
 
@@ -17807,36 +17820,42 @@
             final long origId = Binder.clearCallingIdentity();
             final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
             if (stack != null) {
-                if (fromStackId == DOCKED_STACK_ID) {
+                mWindowManager.deferSurfaceLayout();
+                try {
+                    if (fromStackId == DOCKED_STACK_ID) {
 
-                    // We are moving all tasks from the docked stack to the fullscreen stack, which
-                    // is dismissing the docked stack, so resize all other stacks to fullscreen here
-                    // already so we don't end up with resize trashing.
-                    for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                        if (StackId.isResizeableByDockedStack(i)) {
-                            ActivityStack otherStack = mStackSupervisor.getStack(i);
-                            if (otherStack != null) {
-                                mStackSupervisor.resizeStackLocked(i,
-                                        null, null, null, PRESERVE_WINDOWS,
-                                        true /* allowResizeInDockedMode */);
+                        // We are moving all tasks from the docked stack to the fullscreen stack,
+                        // which is dismissing the docked stack, so resize all other stacks to
+                        // fullscreen here already so we don't end up with resize trashing.
+                        for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+                            if (StackId.isResizeableByDockedStack(i)) {
+                                ActivityStack otherStack = mStackSupervisor.getStack(i);
+                                if (otherStack != null) {
+                                    mStackSupervisor.resizeStackLocked(i,
+                                            null, null, null, PRESERVE_WINDOWS,
+                                            true /* allowResizeInDockedMode */);
+                                }
                             }
                         }
                     }
-                }
-                final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-                final int size = tasks.size();
-                if (onTop) {
-                    for (int i = 0; i < size; i++) {
-                        mStackSupervisor.moveTaskToStackLocked(tasks.get(i).taskId,
-                                FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, !FORCE_FOCUS,
-                                "moveTasksToFullscreenStack", ANIMATE);
+                    final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+                    final int size = tasks.size();
+                    if (onTop) {
+                        for (int i = 0; i < size; i++) {
+                            mStackSupervisor.moveTaskToStackLocked(tasks.get(i).taskId,
+                                    FULLSCREEN_WORKSPACE_STACK_ID, onTop, !FORCE_FOCUS,
+                                    "moveTasksToFullscreenStack", ANIMATE);
+                        }
+                    } else {
+                        for (int i = size - 1; i >= 0; i--) {
+                            mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
+                                    FULLSCREEN_WORKSPACE_STACK_ID, 0);
+                        }
                     }
-                } else {
-                    for (int i = size - 1; i >= 0; i--) {
-                        mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
-                                FULLSCREEN_WORKSPACE_STACK_ID, 0);
-                    }
+                } finally {
+                    mWindowManager.continueSurfaceLayout();
                 }
+
             }
             Binder.restoreCallingIdentity(origId);
         }
@@ -18629,8 +18648,15 @@
                             }
                         }
                         if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
-                            if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
-                                schedGroup = Process.THREAD_GROUP_DEFAULT;
+                            // This will treat important bound services identically to
+                            // the top app, which may behave differently than generic
+                            // foreground work.
+                            if (client.curSchedGroup > schedGroup) {
+                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+                                    schedGroup = client.curSchedGroup;
+                                } else {
+                                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                                }
                             }
                             if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
                                 if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
@@ -18694,11 +18720,15 @@
                     final ActivityRecord a = cr.activity;
                     if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
                         if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ &&
-                                (a.visible || a.state == ActivityState.RESUMED
-                                 || a.state == ActivityState.PAUSING)) {
+                            (a.visible || a.state == ActivityState.RESUMED ||
+                             a.state == ActivityState.PAUSING)) {
                             adj = ProcessList.FOREGROUND_APP_ADJ;
                             if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
-                                schedGroup = Process.THREAD_GROUP_DEFAULT;
+                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+                                    schedGroup = Process.THREAD_GROUP_TOP_APP;
+                                } else {
+                                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                                }
                             }
                             app.cached = false;
                             app.adjType = "service";
@@ -18778,7 +18808,7 @@
                 if (procState > clientProcState) {
                     procState = clientProcState;
                 }
-                if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                if (client.curSchedGroup > schedGroup) {
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9562f94..2394842 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -719,14 +719,14 @@
     }
 
     int getNextTaskIdForUserLocked(int userId) {
-        mRecentTasks.loadUserRecentsLocked(userId);
         final int currentTaskId = mCurTaskIdForUser.get(userId, userId * MAX_TASK_IDS_PER_USER);
         // for a userId u, a taskId can only be in the range
         // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
         // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
         int candidateTaskId = currentTaskId;
-        while (anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
-                INVALID_STACK_ID) != null) {
+        while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
+                || anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
+                        INVALID_STACK_ID) != null) {
             candidateTaskId++;
             if (candidateTaskId == (userId + 1) * MAX_TASK_IDS_PER_USER) {
                 // Wrap around as there will be smaller task ids that are available now.
@@ -2362,6 +2362,8 @@
         stack.positionTask(task, position);
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
+        stack.ensureActivityConfigurationLocked(task.topRunningActivityLocked(), 0,
+                !PRESERVE_WINDOWS);
         stack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
     }
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1166ae1..6614e63 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1037,6 +1037,9 @@
                 // make sure it becomes visible as it starts (this will also trigger entry
                 // animation). An example of this are PIP activities.
                 mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                // Go ahead and tell window manager to execute app transition for this activity
+                // since the app transition will not be triggered through the resume channel.
+                mWindowManager.executeAppTransition();
             }
         } else {
             mTargetStack.addRecentActivityLocked(mStartActivity);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 9c139d5..7209814 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -37,8 +37,12 @@
 import android.graphics.Bitmap;
 import android.os.Environment;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings.System;
+import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import java.io.File;
@@ -59,12 +63,22 @@
 
     // Maximum number recent bitmaps to keep in memory.
     private static final int MAX_RECENT_BITMAPS = 3;
+    private static final int DEFAULT_INITIAL_CAPACITY = 5;
 
     /**
      * Save recent tasks information across reboots.
      */
     private final TaskPersister mTaskPersister;
-    private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(5);
+    private final ActivityManagerService mService;
+    private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
+            DEFAULT_INITIAL_CAPACITY);
+
+    /**
+     * Stores for each user task ids that are taken by tasks residing in persistent storage. These
+     * tasks may or may not currently be in memory.
+     */
+    final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
+            DEFAULT_INITIAL_CAPACITY);
 
     // Mainly to avoid object recreation on multiple calls.
     private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
@@ -75,6 +89,7 @@
 
     RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
         File systemDir = Environment.getDataSystemDirectory();
+        mService = service;
         mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
         mStackSupervisor.setRecentTasks(this);
     }
@@ -87,6 +102,8 @@
      */
     void loadUserRecentsLocked(int userId) {
         if (!mUsersWithRecentsLoaded.get(userId)) {
+            // Load the task ids if not loaded.
+            loadPersistedTaskIdsForUserLocked(userId);
             Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
             addAll(mTaskPersister.restoreTasksForUserLocked(userId));
             cleanupLocked(userId);
@@ -94,21 +111,49 @@
         }
     }
 
+    private void loadPersistedTaskIdsForUserLocked(int userId) {
+        // An empty instead of a null set here means that no persistent taskIds were present
+        // on file when we loaded them.
+        if (mPersistedTaskIds.get(userId) == null) {
+            mPersistedTaskIds.put(userId, mTaskPersister.loadPersistedTaskIdsForUser(userId));
+        }
+    }
+
+    boolean taskIdTakenForUserLocked(int taskId, int userId) {
+        loadPersistedTaskIdsForUserLocked(userId);
+        return mPersistedTaskIds.get(userId).get(taskId);
+    }
+
     void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
         if (task != null && task.stack != null && task.stack.isHomeStack()) {
             // Never persist the home stack.
             return;
         }
+        syncPersistentTaskIdsLocked();
         mTaskPersister.wakeup(task, flush);
     }
 
-    void onSystemReady() {
-        clear();
-        loadUserRecentsLocked(UserHandle.USER_SYSTEM);
-        startPersisting();
+    private void syncPersistentTaskIdsLocked() {
+        for (int i = mPersistedTaskIds.size() - 1; i >= 0; i--) {
+            int userId = mPersistedTaskIds.keyAt(i);
+            if (mUsersWithRecentsLoaded.get(userId)) {
+                // Recents are loaded only after task ids are loaded. Therefore, the set of taskids
+                // referenced here should not be null.
+                mPersistedTaskIds.valueAt(i).clear();
+            }
+        }
+        for (int i = size() - 1; i >= 0; i--) {
+            TaskRecord task = get(i);
+            if (task.isPersistable && (task.stack == null || !task.stack.isHomeStack())) {
+                // Set of persisted taskIds for task.userId should not be null here
+                mPersistedTaskIds.get(task.userId).put(task.taskId, true);
+            }
+        }
     }
 
-    void startPersisting() {
+
+    void onSystemReadyLocked() {
+        clear();
         mTaskPersister.startPersisting();
     }
 
@@ -125,11 +170,14 @@
     }
 
     void flush() {
+        synchronized (mService) {
+            syncPersistentTaskIdsLocked();
+        }
         mTaskPersister.flush();
     }
 
     /**
-     * Returns all userIds for which recents from storage are loaded
+     * Returns all userIds for which recents from persistent storage are loaded into this list.
      *
      * @return an array of userIds.
      */
@@ -149,12 +197,7 @@
         return usersWithRecentsLoaded;
     }
 
-    /**
-     * Removes recent tasks for this user if they are loaded, does not do anything otherwise.
-     *
-     * @param userId the user id.
-     */
-    void unloadUserRecentsLocked(int userId) {
+    private void unloadUserRecentsLocked(int userId) {
         if (mUsersWithRecentsLoaded.get(userId)) {
             Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
             mUsersWithRecentsLoaded.delete(userId);
@@ -162,6 +205,18 @@
         }
     }
 
+    /**
+     * Removes recent tasks and any other state kept in memory for the passed in user. Does not
+     * touch the information present on persistent storage.
+     *
+     * @param userId the id of the user
+     */
+    void unloadUserDataFromMemoryLocked(int userId) {
+        unloadUserRecentsLocked(userId);
+        mPersistedTaskIds.delete(userId);
+        mTaskPersister.unloadUserDataFromMemory(userId);
+    }
+
     TaskRecord taskForIdLocked(int id) {
         final int recentsCount = size();
         for (int i = 0; i < recentsCount; i++) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 283939e..11fd3bc 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
@@ -26,11 +29,13 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
-
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -38,9 +43,12 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -71,6 +79,7 @@
     private static final String TASKS_DIRNAME = "recent_tasks";
     private static final String TASK_EXTENSION = ".xml";
     private static final String IMAGES_DIRNAME = "recent_images";
+    private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt";
     static final String IMAGE_EXTENSION = ".png";
 
     private static final String TAG_TASK = "task";
@@ -78,6 +87,7 @@
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final RecentTasks mRecentTasks;
+    private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>();
 
     /**
      * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
@@ -170,6 +180,64 @@
         }
     }
 
+    @NonNull
+    SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
+        if (mTaskIdsInFile.get(userId) != null) {
+            return mTaskIdsInFile.get(userId).clone();
+        }
+        final SparseBooleanArray persistedTaskIds = new SparseBooleanArray();
+        BufferedReader reader = null;
+        String line;
+        try {
+            reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId)));
+            while ((line = reader.readLine()) != null) {
+                for (String taskIdString : line.split("\\s+")) {
+                    int id = Integer.parseInt(taskIdString);
+                    persistedTaskIds.put(id, true);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // File doesn't exist. Ignore.
+        } catch (Exception e) {
+            Slog.e(TAG, "Error while reading taskIds file for user " + userId, e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+        mTaskIdsInFile.put(userId, persistedTaskIds);
+        return persistedTaskIds.clone();
+    }
+
+    private void maybeWritePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds,
+            int userId) {
+        if (userId < 0) {
+            return;
+        }
+        SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
+        if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIds)) {
+            return;
+        }
+        final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId);
+        BufferedWriter writer = null;
+        try {
+            writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile));
+            for (int i = 0; i < taskIds.size(); i++) {
+                if (taskIds.valueAt(i)) {
+                    writer.write(String.valueOf(taskIds.keyAt(i)));
+                    writer.newLine();
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error while writing taskIds file for user " + userId, e);
+        } finally {
+            IoUtils.closeQuietly(writer);
+        }
+        mTaskIdsInFile.put(userId, taskIds.clone());
+    }
+
+    void unloadUserDataFromMemory(int userId) {
+        mTaskIdsInFile.delete(userId);
+    }
+
     void wakeup(TaskRecord task, boolean flush) {
         synchronized (this) {
             if (task != null) {
@@ -336,14 +404,16 @@
 
         File[] recentFiles = userTasksDir.listFiles();
         if (recentFiles == null) {
-            Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
+            Slog.e(TAG, "restoreTasksForUserLocked: 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, "restoreTasksForUser: userId=" + userId
-                    + ", taskFile=" + taskFile.getName());
+            if (DEBUG) {
+                Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId
+                        + ", taskFile=" + taskFile.getName());
+            }
             BufferedReader reader = null;
             boolean deleteFile = false;
             try {
@@ -366,20 +436,29 @@
                                 // out the stuff we just read, if we don't write it we will
                                 // read the same thing again.
                                 // mWriteQueue.add(new TaskWriteQueueItem(task));
+
                                 final int taskId = task.taskId;
-                                mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
-                                // Check if it's a valid user id. Don't add tasks for removed users.
-                                if (userId == task.userId) {
+                                if (mStackSupervisor.anyTaskForIdLocked(taskId,
+                                        /* restoreFromRecents= */ false, 0) != null) {
+                                    // Should not happen.
+                                    Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
+                                } else if (userId != task.userId) {
+                                    // Should not happen.
+                                    Slog.wtf(TAG, "Task with userId " + task.userId + " found in "
+                                            + userTasksDir.getAbsolutePath());
+                                } else {
+                                    // Looks fine.
+                                    mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
                                     task.isPersistable = true;
                                     tasks.add(task);
                                     recoveredTaskIds.add(taskId);
                                 }
                             } else {
-                                Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
+                                Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
                                         + taskFile + ": " + fileToString(taskFile));
                             }
                         } else {
-                            Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
+                            Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event
                                     + " name=" + name);
                         }
                     }
@@ -454,6 +533,20 @@
         }
     }
 
+    private void writeTaskIdsFiles() {
+        int candidateUserIds[];
+        synchronized (mService) {
+            candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
+        }
+        SparseBooleanArray taskIdsToSave;
+        for (int userId : candidateUserIds) {
+            synchronized (mService) {
+                taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId).clone();
+            }
+            maybeWritePersistedTaskIdsForUser(taskIdsToSave, userId);
+        }
+    }
+
     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
         int[] candidateUserIds;
         synchronized (mService) {
@@ -472,8 +565,12 @@
         return BitmapFactory.decodeFile(filename);
     }
 
+    static File getUserPersistedTaskIdsFile(int userId) {
+        return new File(Environment.getDataSystemDeDirectory(userId), PERSISTED_TASK_IDS_FILENAME);
+    }
+
     static File getUserTasksDir(int userId) {
-        File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
+        File userTasksDir = new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME);
 
         if (!userTasksDir.exists()) {
             if (!userTasksDir.mkdir()) {
@@ -485,7 +582,7 @@
     }
 
     static File getUserImagesDir(int userId) {
-        File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
+        File userImagesDir = new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME);
 
         if (!userImagesDir.exists()) {
             if (!userImagesDir.mkdir()) {
@@ -535,6 +632,7 @@
                     }
                     removeObsoleteFiles(persistentTaskIds);
                 }
+                writeTaskIdsFiles();
 
                 // If mNextWriteTime, then don't delay between each call to saveToXml().
                 final WriteQueueItem item;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 0d97434..f7db1f7 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -453,48 +453,10 @@
                         }
                     }
                 }
-                ensureDefaultPeriodicSyncsH();
             }
         });
     }
 
-    private void ensureDefaultPeriodicSyncsH() {
-
-        long defaultPeriod = SyncStorageEngine.DEFAULT_POLL_FREQUENCY_SECONDS;
-        long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(defaultPeriod);
-
-        List<AuthorityInfo> authorities = mSyncStorageEngine.getAllAuthorities();
-        List<SyncOperation> syncs = getAllPendingSyncsFromCache();
-        for (AuthorityInfo authority: authorities) {
-            boolean foundPeriodicSync = false;
-            for (SyncOperation op: syncs) {
-                if (op.isPeriodic && authority.target.matchesSpec(op.target)) {
-                    foundPeriodicSync = true;
-                    break;
-                }
-            }
-            if (!foundPeriodicSync) {
-                EndPoint target = authority.target;
-                final RegisteredServicesCache.ServiceInfo<SyncAdapterType>
-                        syncAdapterInfo = mSyncAdapters.getServiceInfo(
-                        SyncAdapterType.newKey(
-                                target.provider, target.account.type),
-                        target.userId);
-                if (syncAdapterInfo == null) {
-                    continue;
-                }
-                scheduleSyncOperationH(
-                    new SyncOperation(target, syncAdapterInfo.uid,
-                            syncAdapterInfo.componentName.getPackageName(),
-                            SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC,
-                            new Bundle(), syncAdapterInfo.type.allowParallelSyncs(),
-                            true /* periodic */, SyncOperation.NO_JOB_ID, defaultPeriod * 1000L,
-                            defaultFlex * 1000L)
-                );
-            }
-        }
-    }
-
     private synchronized void verifyJobScheduler() {
         if (mJobScheduler != null) {
             return;
@@ -2271,6 +2233,7 @@
         void checkIfDeviceReady() {
             if (mProvisioned && mBootCompleted) {
                 synchronized(this) {
+                    mSyncStorageEngine.restoreAllPeriodicSyncs();
                     // Dispatch any stashed messages.
                     obtainMessage(MESSAGE_RELEASE_MESSAGES_FROM_QUEUE).sendToTarget();
                 }
@@ -2622,7 +2585,6 @@
             }
             if (mBootCompleted) {
                 doDatabaseCleanup();
-                mSyncStorageEngine.restoreAllPeriodicSyncs();
             }
 
             AccountAndUser[] accounts = mRunningAccounts;
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 05aabf1..bc3fc6a 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -80,7 +80,7 @@
     private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
 
     /** Default time for a periodic sync. */
-    static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+    private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
 
     /** Percentage of period that is flex by default, if no flexMillis is set. */
     private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04;
@@ -857,16 +857,6 @@
         }
     }
 
-    List<AuthorityInfo> getAllAuthorities() {
-        List<AuthorityInfo> authorities = new ArrayList<AuthorityInfo>();
-        synchronized (mAuthorities) {
-            for (int i = 0; i < mAuthorities.size(); i++) {
-                authorities.add(mAuthorities.valueAt(i));
-            }
-        }
-        return authorities;
-    }
-
     /**
      * Returns true if there is currently a sync operation being actively processed for the given
      * target.
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 811c947..42ecc06 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -49,14 +49,13 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.Process;
-import android.util.ArraySet;
 import android.util.Slog;
 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.JobStore.JobStatusFunctor;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
@@ -80,11 +79,15 @@
  */
 public final class JobSchedulerService extends com.android.server.SystemService
         implements StateChangedListener, JobCompletedListener {
+    static final String TAG = "JobSchedulerService";
     public static final boolean DEBUG = false;
+
     /** The number of concurrent jobs we run at one time. */
     private static final int MAX_JOB_CONTEXTS_COUNT
             = ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
-    static final String TAG = "JobSchedulerService";
+    /** The maximum number of jobs that we allow an unprivileged app to schedule */
+    private static final int MAX_JOBS_PER_APP = 100;
+
     /** Global local for all job scheduler state. */
     final Object mLock = new Object();
     /** Master list of jobs. */
@@ -250,6 +253,15 @@
         if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
         JobStatus toCancel;
         synchronized (mLock) {
+            // Jobs on behalf of others don't apply to the per-app job cap
+            if (packageName == null) {
+                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
+                    Slog.w(TAG, "Too many jobs for uid " + uId);
+                    throw new IllegalStateException("Apps may not schedule more than "
+                                + MAX_JOBS_PER_APP + " distinct jobs");
+                }
+            }
+
             toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
         }
         startTrackingJob(jobStatus, toCancel);
@@ -261,17 +273,15 @@
     }
 
     public List<JobInfo> getPendingJobs(int uid) {
-        ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
         synchronized (mLock) {
-            ArraySet<JobStatus> jobs = mJobs.getJobs();
-            for (int i=0; i<jobs.size(); i++) {
-                JobStatus job = jobs.valueAt(i);
-                if (job.getUid() == uid) {
-                    outList.add(job.getJob());
-                }
+            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+            for (int i = jobs.size() - 1; i >= 0; i--) {
+                JobStatus job = jobs.get(i);
+                outList.add(job.getJob());
             }
+            return outList;
         }
-        return outList;
     }
 
     void cancelJobsForUser(int userHandle) {
@@ -471,14 +481,16 @@
                                     getContext().getMainLooper()));
                 }
                 // Attach jobs to their controllers.
-                ArraySet<JobStatus> jobs = mJobs.getJobs();
-                for (int i=0; i<jobs.size(); i++) {
-                    JobStatus job = jobs.valueAt(i);
-                    for (int controller=0; controller<mControllers.size(); controller++) {
-                        mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
-                        mControllers.get(controller).maybeStartTrackingJobLocked(job, null);
+                mJobs.forEachJob(new JobStatusFunctor() {
+                    @Override
+                    public void process(JobStatus job) {
+                        for (int controller = 0; controller < mControllers.size(); controller++) {
+                            final StateController sc = mControllers.get(controller);
+                            sc.deviceIdleModeChanged(mDeviceIdleMode);
+                            sc.maybeStartTrackingJobLocked(job, null);
+                        }
                     }
-                }
+                });
                 // GO GO GO!
                 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
             }
@@ -741,23 +753,13 @@
          * as many as we can.
          */
         private void queueReadyJobsForExecutionLockedH() {
-            ArraySet<JobStatus> jobs = mJobs.getJobs();
-            mPendingJobs.clear();
             if (DEBUG) {
                 Slog.d(TAG, "queuing all ready jobs for execution:");
             }
-            for (int i=0; i<jobs.size(); i++) {
-                JobStatus job = jobs.valueAt(i);
-                if (isReadyToBeExecutedLocked(job)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "    queued " + job.toShortString());
-                    }
-                    mPendingJobs.add(job);
-                } else if (areJobConstraintsNotSatisfiedLocked(job)) {
-                    stopJobOnServiceContextLocked(job,
-                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
-                }
-            }
+            mPendingJobs.clear();
+            mJobs.forEachJob(mReadyQueueFunctor);
+            mReadyQueueFunctor.postProcess();
+
             if (DEBUG) {
                 final int queuedJobs = mPendingJobs.size();
                 if (queuedJobs == 0) {
@@ -768,6 +770,34 @@
             }
         }
 
+        class ReadyJobQueueFunctor implements JobStatusFunctor {
+            ArrayList<JobStatus> newReadyJobs;
+
+            @Override
+            public void process(JobStatus job) {
+                if (isReadyToBeExecutedLocked(job)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "    queued " + job.toShortString());
+                    }
+                    if (newReadyJobs == null) {
+                        newReadyJobs = new ArrayList<JobStatus>();
+                    }
+                    newReadyJobs.add(job);
+                } else if (areJobConstraintsNotSatisfiedLocked(job)) {
+                    stopJobOnServiceContextLocked(job,
+                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
+                }
+            }
+
+            public void postProcess() {
+                if (newReadyJobs != null) {
+                    mPendingJobs.addAll(newReadyJobs);
+                }
+                newReadyJobs = null;
+            }
+        }
+        private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
+
         /**
          * The state of at least one job has changed. Here is where we could enforce various
          * policies on when we want to execute jobs.
@@ -777,18 +807,21 @@
          * If more than 4 jobs total are ready we send them all off.
          * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
          */
-        private void maybeQueueReadyJobsForExecutionLockedH() {
-            mPendingJobs.clear();
-            int chargingCount = 0;
-            int idleCount =  0;
-            int backoffCount = 0;
-            int connectivityCount = 0;
-            int contentCount = 0;
-            List<JobStatus> runnableJobs = null;
-            ArraySet<JobStatus> jobs = mJobs.getJobs();
-            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
-            for (int i=0; i<jobs.size(); i++) {
-                JobStatus job = jobs.valueAt(i);
+        class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
+            int chargingCount;
+            int idleCount;
+            int backoffCount;
+            int connectivityCount;
+            int contentCount;
+            List<JobStatus> runnableJobs;
+
+            public MaybeReadyJobQueueFunctor() {
+                reset();
+            }
+
+            // Functor method invoked for each job via JobStore.forEachJob()
+            @Override
+            public void process(JobStatus job) {
                 if (isReadyToBeExecutedLocked(job)) {
                     try {
                         if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
@@ -797,7 +830,7 @@
                             Slog.w(TAG, "Aborting job " + job.getUid() + ":"
                                     + job.getJob().toString() + " -- package not allowed to start");
                             mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
-                            continue;
+                            return;
                         }
                     } catch (RemoteException e) {
                     }
@@ -825,23 +858,45 @@
                             JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                 }
             }
-            if (backoffCount > 0 ||
-                    idleCount >= MIN_IDLE_COUNT ||
-                    connectivityCount >= MIN_CONNECTIVITY_COUNT ||
-                    chargingCount >= MIN_CHARGING_COUNT ||
-                    contentCount  >= MIN_CONTENT_COUNT ||
-                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
+
+            public void postProcess() {
+                if (backoffCount > 0 ||
+                        idleCount >= MIN_IDLE_COUNT ||
+                        connectivityCount >= MIN_CONNECTIVITY_COUNT ||
+                        chargingCount >= MIN_CHARGING_COUNT ||
+                        contentCount  >= MIN_CONTENT_COUNT ||
+                        (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
+                    }
+                    mPendingJobs.addAll(runnableJobs);
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
+                    }
                 }
-                for (int i=0; i<runnableJobs.size(); i++) {
-                    mPendingJobs.add(runnableJobs.get(i));
-                }
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
-                }
+
+                // Be ready for next time
+                reset();
             }
+
+            private void reset() {
+                chargingCount = 0;
+                idleCount =  0;
+                backoffCount = 0;
+                connectivityCount = 0;
+                contentCount = 0;
+                runnableJobs = null;
+            }
+        }
+        private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
+
+        private void maybeQueueReadyJobsForExecutionLockedH() {
+            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
+
+            mPendingJobs.clear();
+            mJobs.forEachJob(mMaybeQueueFunctor);
+            mMaybeQueueFunctor.postProcess();
         }
 
         /**
@@ -1090,17 +1145,27 @@
         @Override
         public int scheduleAsPackage(JobInfo job, String packageName, int userId)
                 throws RemoteException {
+            final int callerUid = Binder.getCallingUid();
             if (DEBUG) {
-                Slog.d(TAG, "Scheduling job: " + job.toString() + " on behalf of " + packageName);
+                Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
+                        + " on behalf of " + packageName);
             }
-            final int uid = Binder.getCallingUid();
-            if (uid != Process.SYSTEM_UID) {
-                throw new IllegalArgumentException("Only system process is allowed"
-                        + "to set packageName");
+
+            if (packageName == null) {
+                throw new NullPointerException("Must specify a package for scheduleAsPackage()");
             }
+
+            int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.UPDATE_DEVICE_STATS);
+            if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Caller uid " + callerUid
+                        + " not permitted to schedule jobs for other apps");
+            }
+
             long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.scheduleAsPackage(job, uid, packageName, userId);
+                return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
+                        packageName, userId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1183,7 +1248,7 @@
         return s.toString();
     }
 
-    void dumpInternal(PrintWriter pw) {
+    void dumpInternal(final PrintWriter pw) {
         final long now = SystemClock.elapsedRealtime();
         synchronized (mLock) {
             pw.print("Started users: ");
@@ -1193,24 +1258,27 @@
             pw.println();
             pw.println("Registered jobs:");
             if (mJobs.size() > 0) {
-                ArraySet<JobStatus> jobs = mJobs.getJobs();
-                for (int i=0; i<jobs.size(); i++) {
-                    JobStatus job = jobs.valueAt(i);
-                    pw.print("  Job #"); pw.print(i); pw.print(": ");
-                    pw.println(job.toShortString());
-                    job.dump(pw, "    ");
-                    pw.print("    Ready: ");
-                    pw.print(mHandler.isReadyToBeExecutedLocked(job));
-                    pw.print(" (job=");
-                    pw.print(job.isReady());
-                    pw.print(" pending=");
-                    pw.print(mPendingJobs.contains(job));
-                    pw.print(" active=");
-                    pw.print(isCurrentlyActiveLocked(job));
-                    pw.print(" user=");
-                    pw.print(mStartedUsers.contains(job.getUserId()));
-                    pw.println(")");
-                }
+                mJobs.forEachJob(new JobStatusFunctor() {
+                    private int index = 0;
+
+                    @Override
+                    public void process(JobStatus job) {
+                        pw.print("  Job #"); pw.print(index++); pw.print(": ");
+                        pw.println(job.toShortString());
+                        job.dump(pw, "    ");
+                        pw.print("    Ready: ");
+                        pw.print(mHandler.isReadyToBeExecutedLocked(job));
+                        pw.print(" (job=");
+                        pw.print(job.isReady());
+                        pw.print(" pending=");
+                        pw.print(mPendingJobs.contains(job));
+                        pw.print(" active=");
+                        pw.print(isCurrentlyActiveLocked(job));
+                        pw.print(" user=");
+                        pw.print(mStartedUsers.contains(job.getUserId()));
+                        pw.println(")");
+                    }
+                });
             } else {
                 pw.println("  None.");
             }
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 6020247..f8753d6 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -29,6 +29,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -68,8 +69,8 @@
 
     /** Threshold to adjust how often we want to write to the db. */
     private static final int MAX_OPS_BEFORE_WRITE = 1;
-    final ArraySet<JobStatus> mJobSet;
     final Object mLock;
+    final JobSet mJobSet; // per-caller-uid tracking
     final Context mContext;
 
     private int mDirtyOperations;
@@ -114,7 +115,7 @@
         jobDir.mkdirs();
         mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
 
-        mJobSet = new ArraySet<JobStatus>();
+        mJobSet = new JobSet();
 
         readJobMapFromDisk(mJobSet);
     }
@@ -137,19 +138,6 @@
         return replaced;
     }
 
-    /**
-     * Whether this jobStatus object already exists in the JobStore.
-     */
-    public boolean containsJobIdForUid(int jobId, int uId) {
-        for (int i=mJobSet.size()-1; i>=0; i--) {
-            JobStatus ts = mJobSet.valueAt(i);
-            if (ts.getUid() == uId && ts.getJobId() == jobId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean containsJob(JobStatus jobStatus) {
         return mJobSet.contains(jobStatus);
     }
@@ -158,6 +146,10 @@
         return mJobSet.size();
     }
 
+    public int countJobsForUid(int uid) {
+        return mJobSet.countJobsForUid(uid);
+    }
+
     /**
      * Remove the provided job. Will also delete the job if it was persisted.
      * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
@@ -188,14 +180,7 @@
      * @return A list of all the jobs scheduled by the provided user. Never null.
      */
     public List<JobStatus> getJobsByUser(int userHandle) {
-        List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
-        for (int i=mJobSet.size()-1; i>=0; i--) {
-            JobStatus ts = mJobSet.valueAt(i);
-            if (UserHandle.getUserId(ts.getUid()) == userHandle) {
-                matchingJobs.add(ts);
-            }
-        }
-        return matchingJobs;
+        return mJobSet.getJobsByUser(userHandle);
     }
 
     /**
@@ -203,14 +188,7 @@
      * @return All JobStatus objects for a given uid from the master list. Never null.
      */
     public List<JobStatus> getJobsByUid(int uid) {
-        List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
-        for (int i=mJobSet.size()-1; i>=0; i--) {
-            JobStatus ts = mJobSet.valueAt(i);
-            if (ts.getUid() == uid) {
-                matchingJobs.add(ts);
-            }
-        }
-        return matchingJobs;
+        return mJobSet.getJobsByUid(uid);
     }
 
     /**
@@ -219,20 +197,21 @@
      * @return the JobStatus that matches the provided uId and jobId, or null if none found.
      */
     public JobStatus getJobByUidAndJobId(int uid, int jobId) {
-        for (int i=mJobSet.size()-1; i>=0; i--) {
-            JobStatus ts = mJobSet.valueAt(i);
-            if (ts.getUid() == uid && ts.getJobId() == jobId) {
-                return ts;
-            }
-        }
-        return null;
+        return mJobSet.get(uid, jobId);
     }
 
     /**
-     * @return The live array of JobStatus objects.
+     * Iterate over the set of all jobs, invoking the supplied functor on each.  This is for
+     * customers who need to examine each job; we'd much rather not have to generate
+     * transient unified collections for them to iterate over and then discard, or creating
+     * iterators every time a client needs to perform a sweep.
      */
-    public ArraySet<JobStatus> getJobs() {
-        return mJobSet;
+    public void forEachJob(JobStatusFunctor functor) {
+        mJobSet.forEachJob(functor);
+    }
+
+    public interface JobStatusFunctor {
+        public void process(JobStatus jobStatus);
     }
 
     /** Version of the db schema. */
@@ -261,7 +240,7 @@
     }
 
     @VisibleForTesting
-    public void readJobMapFromDisk(ArraySet<JobStatus> jobSet) {
+    public void readJobMapFromDisk(JobSet jobSet) {
         new ReadJobMapFromDiskRunnable(jobSet).run();
     }
 
@@ -273,21 +252,19 @@
         @Override
         public void run() {
             final long startElapsed = SystemClock.elapsedRealtime();
-            List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
+            final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
             synchronized (mLock) {
-                // Copy over the jobs so we can release the lock before writing.
-                for (int i=0; i<mJobSet.size(); i++) {
-                    JobStatus jobStatus = mJobSet.valueAt(i);
-
-                    if (!jobStatus.isPersisted()){
-                        continue;
+                // Clone the jobs so we can release the lock before writing.
+                mJobSet.forEachJob(new JobStatusFunctor() {
+                    @Override
+                    public void process(JobStatus job) {
+                        if (job.isPersisted()) {
+                            storeCopy.add(new JobStatus(job));
+                        }
                     }
-
-                    JobStatus copy = new JobStatus(jobStatus);
-                    mStoreCopy.add(copy);
-                }
+                });
             }
-            writeJobsMapImpl(mStoreCopy);
+            writeJobsMapImpl(storeCopy);
             if (JobSchedulerService.DEBUG) {
                 Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
                         - startElapsed) + "ms");
@@ -440,13 +417,13 @@
      * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
      */
     private class ReadJobMapFromDiskRunnable implements Runnable {
-        private final ArraySet<JobStatus> jobSet;
+        private final JobSet jobSet;
 
         /**
          * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
          *               so that after disk read we can populate it directly.
          */
-        ReadJobMapFromDiskRunnable(ArraySet<JobStatus> jobSet) {
+        ReadJobMapFromDiskRunnable(JobSet jobSet) {
             this.jobSet = jobSet;
         }
 
@@ -759,4 +736,122 @@
             return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
         }
     }
+
+    static class JobSet {
+        // Key is the getUid() originator of the jobs in each sheaf
+        private SparseArray<ArraySet<JobStatus>> mJobs;
+
+        public JobSet() {
+            mJobs = new SparseArray<ArraySet<JobStatus>>();
+        }
+
+        public List<JobStatus> getJobsByUid(int uid) {
+            ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            if (jobs != null) {
+                matchingJobs.addAll(jobs);
+            }
+            return matchingJobs;
+        }
+
+        // By user, not by uid, so we need to traverse by key and check
+        public List<JobStatus> getJobsByUser(int userId) {
+            ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+            for (int i = mJobs.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
+                    ArraySet<JobStatus> jobs = mJobs.get(i);
+                    if (jobs != null) {
+                        result.addAll(jobs);
+                    }
+                }
+            }
+            return result;
+        }
+
+        public boolean add(JobStatus job) {
+            final int uid = job.getUid();
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            if (jobs == null) {
+                jobs = new ArraySet<JobStatus>();
+                mJobs.put(uid, jobs);
+            }
+            return jobs.add(job);
+        }
+
+        public boolean remove(JobStatus job) {
+            final int uid = job.getUid();
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
+            if (didRemove && jobs.size() == 0) {
+                // no more jobs for this uid; let the now-empty set object be GC'd.
+                mJobs.remove(uid);
+            }
+            return didRemove;
+        }
+
+        public boolean contains(JobStatus job) {
+            final int uid = job.getUid();
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            return jobs != null && jobs.contains(job);
+        }
+
+        public JobStatus get(int uid, int jobId) {
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            if (jobs != null) {
+                for (int i = jobs.size() - 1; i >= 0; i--) {
+                    JobStatus job = jobs.valueAt(i);
+                    if (job.getJobId() == jobId) {
+                        return job;
+                    }
+                }
+            }
+            return null;
+        }
+
+        // Inefficient; use only for testing
+        public List<JobStatus> getAllJobs() {
+            ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
+            for (int i = mJobs.size(); i >= 0; i--) {
+                allJobs.addAll(mJobs.valueAt(i));
+            }
+            return allJobs;
+        }
+
+        public void clear() {
+            mJobs.clear();
+        }
+
+        public int size() {
+            int total = 0;
+            for (int i = mJobs.size() - 1; i >= 0; i--) {
+                total += mJobs.valueAt(i).size();
+            }
+            return total;
+        }
+
+        // We only want to count the jobs that this uid has scheduled on its own
+        // behalf, not those that the app has scheduled on someone else's behalf.
+        public int countJobsForUid(int uid) {
+            int total = 0;
+            ArraySet<JobStatus> jobs = mJobs.get(uid);
+            if (jobs != null) {
+                for (int i = jobs.size() - 1; i >= 0; i--) {
+                    JobStatus job = jobs.valueAt(i);
+                    if (job.getUid() == job.getSourceUid()) {
+                        total++;
+                    }
+                }
+            }
+            return total;
+        }
+
+        public void forEachJob(JobStatusFunctor functor) {
+            for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
+                ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
+                for (int i = jobs.size() - 1; i >= 0; i--) {
+                    functor.process(jobs.valueAt(i));
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index 90e26d8..9f1435a 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -59,8 +59,12 @@
 
     protected static final int IPCONFIG_FILE_VERSION = 2;
 
+    public IpConfigStore(DelayedDiskWrite writer) {
+        mWriter = writer;
+    }
+
     public IpConfigStore() {
-        mWriter = new DelayedDiskWrite();
+        this(new DelayedDiskWrite());
     }
 
     private boolean writeConfig(DataOutputStream out, int configKey,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index abbad21..41077d0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6829,17 +6829,37 @@
 
         // Extract pacakges only if profile-guided compilation is enabled because
         // otherwise BackgroundDexOptService will not dexopt them later.
-        if (mUseJitProfiles) {
-            List<PackageParser.Package> pkgs;
-            synchronized (mPackages) {
-                pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
+        if (!mUseJitProfiles || !isUpgrade()) {
+            return;
+        }
+
+        List<PackageParser.Package> pkgs;
+        synchronized (mPackages) {
+            pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
+        }
+
+        int curr = 0;
+        int total = pkgs.size();
+        for (PackageParser.Package pkg : pkgs) {
+            curr++;
+
+            if (DEBUG_DEXOPT) {
+                Log.i(TAG, "Extracting app " + curr + " of " + total + ": " + pkg.packageName);
             }
-            for (PackageParser.Package pkg : pkgs) {
-                if (PackageDexOptimizer.canOptimizePackage(pkg)) {
-                    performDexOpt(pkg.packageName, null /* instructionSet */,
-                             false /* useProfiles */, true /* extractOnly */, false /* force */);
+
+            if (!isFirstBoot()) {
+                try {
+                    ActivityManagerNative.getDefault().showBootMessage(
+                            mContext.getResources().getString(R.string.android_upgrading_apk,
+                                    curr, total), true);
+                } catch (RemoteException e) {
                 }
             }
+
+            if (PackageDexOptimizer.canOptimizePackage(pkg)) {
+                performDexOpt(pkg.packageName, null /* instructionSet */,
+                         false /* useProfiles */, true /* extractOnly */, false /* force */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 690cb98..dd13809 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4434,7 +4434,7 @@
                 if ((perm.info.flags&PermissionInfo.FLAG_COSTS_MONEY) != 0) {
                     pw.print(", COSTS_MONEY");
                 }
-                if ((perm.info.flags&PermissionInfo.FLAG_HIDDEN) != 0) {
+                if ((perm.info.flags&PermissionInfo.FLAG_REMOVED) != 0) {
                     pw.print(", HIDDEN");
                 }
                 if ((perm.info.flags&PermissionInfo.FLAG_INSTALLED) != 0) {
@@ -4611,7 +4611,7 @@
             if (p.perm != null) {
                 pw.print("    perm="); pw.println(p.perm);
                 if ((p.perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
-                        || (p.perm.info.flags & PermissionInfo.FLAG_HIDDEN) != 0) {
+                        || (p.perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
                     pw.print("    flags=0x"); pw.println(Integer.toHexString(p.perm.info.flags));
                 }
             }
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index a0f20aa..5615998 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -388,6 +388,8 @@
                 public void run() {
                     try {
                         // Take an "interactive" bugreport.
+                        MetricsLogger.visible(this,
+                                MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
                         ActivityManagerNative.getDefault().requestBugReport(
                                 ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
                     } catch (RemoteException e) {
@@ -405,6 +407,8 @@
             }
             try {
                 // Take a "full" bugreport.
+                MetricsLogger.visible(this,
+                        MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
                 ActivityManagerNative.getDefault().requestBugReport(
                         ActivityManager.BUGREPORT_OPTION_FULL);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 716b96f..26a1941 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -25,6 +25,9 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.LayoutParams.*;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
@@ -6099,6 +6102,17 @@
         }
     }
 
+    @Override
+    public boolean isDockSideAllowed(int dockSide) {
+
+        // We do not allow all dock sides at which the navigation bar touches the docked stack.
+        if (!mNavigationBarCanMove) {
+            return dockSide == DOCKED_TOP || dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT;
+        } else {
+            return dockSide == DOCKED_TOP || dockSide == DOCKED_LEFT;
+        }
+    }
+
     void sendCloseSystemWindows() {
         PhoneWindow.sendCloseSystemWindows(mContext, null);
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbaa598..f901f95 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2703,12 +2703,9 @@
         if (reason == null) {
             reason = "";
         }
-        if (reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            // If we are rebooting to go into recovery, instead of
-            // setting sys.powerctl directly we'll start the
-            // pre-recovery service which will do some preparation for
-            // recovery and then reboot for us.
-            SystemProperties.set("ctl.start", "pre-recovery");
+        if (reason.equals(PowerManager.REBOOT_RECOVERY)
+                || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            SystemProperties.set("sys.powerctl", "reboot,recovery");
         } else {
             SystemProperties.set("sys.powerctl", "reboot," + reason);
         }
@@ -3421,7 +3418,8 @@
         @Override // Binder call
         public void reboot(boolean confirm, String reason, boolean wait) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
-            if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
+            if (PowerManager.REBOOT_RECOVERY.equals(reason)
+                    || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
                 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
             }
 
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index ac6a28e..bcafddc 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -32,8 +32,10 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.os.RecoverySystem;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -81,13 +83,9 @@
     private static Object sIsStartedGuard = new Object();
     private static boolean sIsStarted = false;
 
-    // uncrypt status files
-    private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
-    private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
-
     private static boolean mReboot;
     private static boolean mRebootSafeMode;
-    private static boolean mRebootUpdate;
+    private static boolean mRebootHasProgressBar;
     private static String mReason;
 
     // Provides shutdown assurance in case the system_server is killed
@@ -96,6 +94,9 @@
     // Indicates whether we are rebooting into safe mode
     public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
 
+    // Indicates whether we should stay in safe mode until ro.build.date.utc is newer than this
+    public static final String AUDIT_SAFEMODE_PROPERTY = "persist.sys.audit_safemode";
+
     // static instance of this thread
     private static final ShutdownThread sInstance = new ShutdownThread();
 
@@ -213,7 +214,7 @@
     public static void reboot(final Context context, String reason, boolean confirm) {
         mReboot = true;
         mRebootSafeMode = false;
-        mRebootUpdate = false;
+        mRebootHasProgressBar = false;
         mReason = reason;
         shutdownInner(context, confirm);
     }
@@ -233,7 +234,7 @@
 
         mReboot = true;
         mRebootSafeMode = true;
-        mRebootUpdate = false;
+        mRebootHasProgressBar = false;
         mReason = null;
         shutdownInner(context, confirm);
     }
@@ -250,10 +251,19 @@
         // Throw up a system dialog to indicate the device is rebooting / shutting down.
         ProgressDialog pd = new ProgressDialog(context);
 
-        // Path 1: Reboot to recovery and install the update
-        //   Condition: mReason == REBOOT_RECOVERY and mRebootUpdate == True
-        //   (mRebootUpdate is set by checking if /cache/recovery/uncrypt_file exists.)
-        //   UI: progress bar
+        // Path 1: Reboot to recovery for update
+        //   Condition: mReason == REBOOT_RECOVERY_UPDATE
+        //
+        //  Path 1a: uncrypt needed
+        //   Condition: if /cache/recovery/uncrypt_file exists but
+        //              /cache/recovery/block.map doesn't.
+        //   UI: determinate progress bar (mRebootHasProgressBar == True)
+        //
+        // * Path 1a is expected to be removed once the GmsCore shipped on
+        //   device always calls uncrypt prior to reboot.
+        //
+        //  Path 1b: uncrypt already done
+        //   UI: spinning circle only (no progress bar)
         //
         // Path 2: Reboot to recovery for factory reset
         //   Condition: mReason == REBOOT_RECOVERY
@@ -262,24 +272,31 @@
         // Path 3: Regular reboot / shutdown
         //   Condition: Otherwise
         //   UI: spinning circle only (no progress bar)
-        if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
-            mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
-            if (mRebootUpdate) {
-                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
-                pd.setMessage(context.getText(
-                        com.android.internal.R.string.reboot_to_update_prepare));
+        if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(mReason)) {
+            // We need the progress bar if uncrypt will be invoked during the
+            // reboot, which might be time-consuming.
+            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
+                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
+            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
+            if (mRebootHasProgressBar) {
                 pd.setMax(100);
-                pd.setProgressNumberFormat(null);
-                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                 pd.setProgress(0);
                 pd.setIndeterminate(false);
-            } else {
-                // Factory reset path. Set the dialog message accordingly.
-                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
+                pd.setProgressNumberFormat(null);
+                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                 pd.setMessage(context.getText(
-                        com.android.internal.R.string.reboot_to_reset_message));
+                            com.android.internal.R.string.reboot_to_update_prepare));
+            } else {
                 pd.setIndeterminate(true);
+                pd.setMessage(context.getText(
+                            com.android.internal.R.string.reboot_to_update_reboot));
             }
+        } else if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
+            // Factory reset path. Set the dialog message accordingly.
+            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
+            pd.setMessage(context.getText(
+                        com.android.internal.R.string.reboot_to_reset_message));
+            pd.setIndeterminate(true);
         } else {
             pd.setTitle(context.getText(com.android.internal.R.string.power_off));
             pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
@@ -379,7 +396,7 @@
                 if (delay <= 0) {
                     Log.w(TAG, "Shutdown broadcast timed out");
                     break;
-                } else if (mRebootUpdate) {
+                } else if (mRebootHasProgressBar) {
                     int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
                             BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
                     sInstance.setRebootProgress(status, null);
@@ -390,7 +407,7 @@
                 }
             }
         }
-        if (mRebootUpdate) {
+        if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
         }
 
@@ -404,7 +421,7 @@
             } catch (RemoteException e) {
             }
         }
-        if (mRebootUpdate) {
+        if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
         }
 
@@ -415,13 +432,13 @@
         if (pm != null) {
             pm.shutdown();
         }
-        if (mRebootUpdate) {
+        if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
         }
 
         // Shutdown radios.
         shutdownRadios(MAX_RADIO_WAIT_TIME);
-        if (mRebootUpdate) {
+        if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
         }
 
@@ -455,7 +472,7 @@
                 if (delay <= 0) {
                     Log.w(TAG, "Shutdown wait timed out");
                     break;
-                } else if (mRebootUpdate) {
+                } else if (mRebootHasProgressBar) {
                     int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
                             (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
                             MAX_SHUTDOWN_WAIT_TIME);
@@ -468,10 +485,11 @@
                 }
             }
         }
-        if (mRebootUpdate) {
+        if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
 
-            // If it's to reboot to install update, invoke uncrypt via init service.
+            // If it's to reboot to install an update and uncrypt hasn't been
+            // done yet, trigger it now.
             uncrypt();
         }
 
@@ -549,7 +567,7 @@
 
                 long delay = endTime - SystemClock.elapsedRealtime();
                 while (delay > 0) {
-                    if (mRebootUpdate) {
+                    if (mRebootHasProgressBar) {
                         int status = (int)((timeout - delay) * 1.0 *
                                 (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
                         status += PACKAGE_MANAGER_STOP_PERCENT;
@@ -651,66 +669,40 @@
     private void uncrypt() {
         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
 
+        final RecoverySystem.ProgressListener progressListener =
+                new RecoverySystem.ProgressListener() {
+            @Override
+            public void onProgress(int status) {
+                if (status >= 0 && status < 100) {
+                    // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
+                    status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
+                    status += MOUNT_SERVICE_STOP_PERCENT;
+                    CharSequence msg = mContext.getText(
+                            com.android.internal.R.string.reboot_to_update_package);
+                    sInstance.setRebootProgress(status, msg);
+                } else if (status == 100) {
+                    CharSequence msg = mContext.getText(
+                            com.android.internal.R.string.reboot_to_update_reboot);
+                    sInstance.setRebootProgress(status, msg);
+                } else {
+                    // Ignored
+                }
+            }
+        };
+
         final boolean[] done = new boolean[1];
         done[0] = false;
         Thread t = new Thread() {
             @Override
             public void run() {
-                // Create the status pipe file to communicate with /system/bin/uncrypt.
-                new File(UNCRYPT_STATUS_FILE).delete();
+                RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
+                        Context.RECOVERY_SERVICE);
+                String filename = null;
                 try {
-                    Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
-                } catch (ErrnoException e) {
-                    Log.w(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE +
-                            "\": " + e.getMessage());
-                }
-
-                SystemProperties.set("ctl.start", "uncrypt");
-
-                // Read the status from the pipe.
-                try (BufferedReader reader = new BufferedReader(
-                        new FileReader(UNCRYPT_STATUS_FILE))) {
-
-                    int lastStatus = Integer.MIN_VALUE;
-                    while (true) {
-                        String str = reader.readLine();
-                        try {
-                            int status = Integer.parseInt(str);
-
-                            // Avoid flooding the log with the same message.
-                            if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
-                                continue;
-                            }
-                            lastStatus = status;
-
-                            if (status >= 0 && status < 100) {
-                                // Update status
-                                Log.d(TAG, "uncrypt read status: " + status);
-                                // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
-                                status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
-                                status += MOUNT_SERVICE_STOP_PERCENT;
-                                CharSequence msg = mContext.getText(
-                                        com.android.internal.R.string.reboot_to_update_package);
-                                sInstance.setRebootProgress(status, msg);
-                            } else if (status == 100) {
-                                Log.d(TAG, "uncrypt successfully finished.");
-                                CharSequence msg = mContext.getText(
-                                        com.android.internal.R.string.reboot_to_update_reboot);
-                                sInstance.setRebootProgress(status, msg);
-                                break;
-                            } else {
-                                // Error in /system/bin/uncrypt. Or it's rebooting to recovery
-                                // to perform other operations (e.g. factory reset).
-                                Log.d(TAG, "uncrypt failed with status: " + status);
-                                break;
-                            }
-                        } catch (NumberFormatException unused) {
-                            Log.d(TAG, "uncrypt invalid status received: " + str);
-                            break;
-                        }
-                    }
-                } catch (IOException unused) {
-                    Log.w(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
+                    filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
+                    rs.processPackage(mContext, new File(filename), progressListener);
+                } catch (IOException e) {
+                    Log.e(TAG, "Error uncrypting file", e);
                 }
                 done[0] = true;
             }
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 4e96d71..bf281d6 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiHotplugEvent;
@@ -45,6 +46,7 @@
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvStreamConfig;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -57,9 +59,13 @@
 import android.view.KeyEvent;
 import android.view.Surface;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -549,6 +555,70 @@
         return (float) mCurrentIndex / (float) mCurrentMaxIndex;
     }
 
+    public void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump TvInputHardwareManager from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("TvInputHardwareManager Info:");
+            pw.increaseIndent();
+            pw.println("mConnections: deviceId -> Connection");
+            pw.increaseIndent();
+            for (int i = 0; i < mConnections.size(); i++) {
+                int deviceId = mConnections.keyAt(i);
+                Connection mConnection = mConnections.valueAt(i);
+                pw.println(deviceId + ": " + mConnection);
+
+            }
+            pw.decreaseIndent();
+
+            pw.println("mHardwareList:");
+            pw.increaseIndent();
+            for (TvInputHardwareInfo tvInputHardwareInfo : mHardwareList) {
+                pw.println(tvInputHardwareInfo);
+            }
+            pw.decreaseIndent();
+
+            pw.println("mHdmiDeviceList:");
+            pw.increaseIndent();
+            for (HdmiDeviceInfo hdmiDeviceInfo : mHdmiDeviceList) {
+                pw.println(hdmiDeviceInfo);
+            }
+            pw.decreaseIndent();
+
+            pw.println("mHardwareInputIdMap: deviceId -> inputId");
+            pw.increaseIndent();
+            for (int i = 0 ; i < mHardwareInputIdMap.size(); i++) {
+                int deviceId = mHardwareInputIdMap.keyAt(i);
+                String inputId = mHardwareInputIdMap.valueAt(i);
+                pw.println(deviceId + ": " + inputId);
+            }
+            pw.decreaseIndent();
+
+            pw.println("mHdmiInputIdMap: id -> inputId");
+            pw.increaseIndent();
+            for (int i = 0; i < mHdmiInputIdMap.size(); i++) {
+                int id = mHdmiInputIdMap.keyAt(i);
+                String inputId = mHdmiInputIdMap.valueAt(i);
+                pw.println(id + ": " + inputId);
+            }
+            pw.decreaseIndent();
+
+            pw.println("mInputMap: inputId -> inputInfo");
+            pw.increaseIndent();
+            for(Map.Entry<String, TvInputInfo> entry : mInputMap.entrySet()) {
+                pw.println(entry.getKey() + ": " + entry.getValue());
+            }
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
     private class Connection implements IBinder.DeathRecipient {
         private final TvInputHardwareInfo mHardwareInfo;
         private TvInputInfo mInfo;
@@ -641,6 +711,17 @@
                 resetLocked(null, null, null, null, null);
             }
         }
+
+        public String toString() {
+            return "Connection{"
+                    + " mHardwareInfo: " + mHardwareInfo
+                    + ", mInfo: " + mInfo
+                    + ", mCallback: " + mCallback
+                    + ", mConfigs: " + Arrays.toString(mConfigs)
+                    + ", mCallingUid: " + mCallingUid
+                    + ", mResolvedUserId: " + mResolvedUserId
+                    + " }";
+        }
     }
 
     private class TvInputHardwareImpl extends ITvInputHardware.Stub {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 628c627..8a2729e 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1923,6 +1923,7 @@
                     pw.decreaseIndent();
                 }
             }
+            mTvInputHardwareManager.dump(fd, writer, args);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index fa5ee72..a81fba0 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -153,6 +153,13 @@
         } else {
             mClearProlongedAnimation = true;
         }
+
+        // Since we are finally starting our animation, we don't need the logic anymore to prevent
+        // the app from showing again if we just moved between stacks. See
+        // {@link WindowState#notifyMovedInStack}.
+        for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) {
+            mAppToken.allAppWindows.get(i).resetJustMovedInStack();
+        }
     }
 
     public void setDummyAnimation() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 325005b..00690c4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -247,7 +247,15 @@
             mStack.removeTask(this);
         }
         stack.positionTask(this, position, showForAllUsers());
-        setBounds(bounds, config);
+        resizeLocked(bounds, config, false /* force */);
+
+        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
+            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                final WindowState win = windows.get(winNdx);
+                win.notifyMovedInStack();
+            }
+        }
     }
 
     boolean removeAppToken(AppWindowToken wtoken) {
@@ -272,7 +280,7 @@
     }
 
     /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
-    int setBounds(Rect bounds, Configuration config) {
+    private int setBounds(Rect bounds, Configuration config) {
         if (config == null) {
             config = Configuration.EMPTY;
         }
@@ -598,13 +606,21 @@
     void resizeWindows() {
         final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
         for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
+            final AppWindowToken atoken = mAppTokens.get(activityNdx);
+
+            // Some windows won't go through the resizing process, if they don't have a surface, so
+            // destroy all saved surfaces here.
+            atoken.destroySavedSurfaces();
+            final ArrayList<WindowState> windows = atoken.allAppWindows;
             for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                 final WindowState win = windows.get(winNdx);
                 if (win.mHasSurface && !resizingWindows.contains(win)) {
                     if (DEBUG_RESIZE) Slog.d(TAG, "resizeWindows: Resizing " + win);
                     resizingWindows.add(win);
                 }
+                if (win.isGoneForLayoutLw()) {
+                    win.mResizedWhileGone = true;
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 06e5ac5..07a6514 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -352,6 +352,7 @@
 
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (mStackId == DOCKED_STACK_ID) {
+            repositionDockedStackAfterRotation(mTmpRect2);
             snapDockedStackAfterRotation(mTmpRect2);
         }
 
@@ -363,6 +364,43 @@
     }
 
     /**
+     * Some dock sides are not allowed by the policy. This method queries the policy and moves
+     * the docked stack around if needed.
+     *
+     * @param inOutBounds the bounds of the docked stack to adjust
+     */
+    private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+        int dockSide = getDockSide(inOutBounds);
+        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+            return;
+        }
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        dockSide = DockedDividerUtils.invertDockSide(dockSide);
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                int movement = inOutBounds.left;
+                inOutBounds.left -= movement;
+                inOutBounds.right -= movement;
+                break;
+            case DOCKED_RIGHT:
+                movement = mTmpRect.right - inOutBounds.right;
+                inOutBounds.left += movement;
+                inOutBounds.right += movement;
+                break;
+            case DOCKED_TOP:
+                movement = inOutBounds.top;
+                inOutBounds.top -= movement;
+                inOutBounds.bottom -= movement;
+                break;
+            case DOCKED_BOTTOM:
+                movement = mTmpRect.bottom - inOutBounds.bottom;
+                inOutBounds.top += movement;
+                inOutBounds.bottom += movement;
+                break;
+        }
+    }
+
+    /**
      * Snaps the bounds after rotation to the closest snap target for the docked stack.
      */
     private void snapDockedStackAfterRotation(Rect outBounds) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 38d0711..85bddee 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -530,7 +530,7 @@
         for (int i = windows.size() - 1; i >= 0; i--) {
             final WindowState win = windows.get(i);
             WindowStateAnimator winAnimator = win.mWinAnimator;
-            if (winAnimator.mSurfaceController == null) {
+            if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 142715e..f36585e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -320,6 +320,8 @@
 
     private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
 
+    private static final String PROPERTY_BUILD_DATE_UTC = "ro.build.date.utc";
+
     final private KeyguardDisableHandler mKeyguardDisableHandler;
 
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -2758,6 +2760,9 @@
                 winAnimator.mReportSurfaceResized = false;
                 result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
             }
+            if (!win.isGoneForLayoutLw()) {
+                win.mResizedWhileGone = false;
+            }
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
             outContentInsets.set(win.mContentInsets);
@@ -7458,8 +7463,22 @@
                 || volumeDownState > 0;
         try {
             if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0) {
-                mSafeMode = true;
-                SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
+                int auditSafeMode = SystemProperties.getInt(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, 0);
+
+                if (auditSafeMode == 0) {
+                    mSafeMode = true;
+                    SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
+                } else {
+                    // stay in safe mode until we have updated to a newer build
+                    int buildDate = SystemProperties.getInt(PROPERTY_BUILD_DATE_UTC, 0);
+
+                    if (auditSafeMode >= buildDate) {
+                        mSafeMode = true;
+                    } else {
+                        SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
+                        SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, "");
+                    }
+                }
             }
         } catch (IllegalArgumentException e) {
         }
@@ -10663,6 +10682,7 @@
 
         @Override
         public void waitForAllWindowsDrawn(Runnable callback, long timeout) {
+            boolean allWindowsDrawn = false;
             synchronized (mWindowMap) {
                 mWaitingForDrawnCallback = callback;
                 final WindowList windows = getDefaultWindowListLocked();
@@ -10683,13 +10703,16 @@
                     }
                 }
                 mWindowPlacerLocked.requestTraversal();
+                mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+                if (mWaitingForDrawn.isEmpty()) {
+                    allWindowsDrawn = true;
+                } else {
+                    mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
+                    checkDrawnWindowsLocked();
+                }
             }
-            mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
-            if (mWaitingForDrawn.isEmpty()) {
+            if (allWindowsDrawn) {
                 callback.run();
-            } else {
-                mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
-                checkDrawnWindowsLocked();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f30c8d3..9dcb404 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -448,6 +448,16 @@
 
     final private Rect mTmpRect = new Rect();
 
+    /**
+     * See {@link #notifyMovedInStack}.
+     */
+    private boolean mJustMovedInStack;
+
+    /**
+     * Whether the window was resized by us while it was gone for layout.
+     */
+    boolean mResizedWhileGone = false;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, final DisplayContent displayContent) {
@@ -1235,7 +1245,7 @@
         return mViewVisibility == View.GONE
                 || !mRelayoutCalled
                 || (atoken == null && mRootToken.hidden)
-                || (atoken != null && (atoken.hiddenRequested || atoken.hidden))
+                || (atoken != null && atoken.hiddenRequested)
                 || mAttachedHidden
                 || (mAnimatingExit && !isAnimatingLw())
                 || mDestroying;
@@ -1374,6 +1384,39 @@
         }
     }
 
+    /**
+     * Notifies this window that the corresponding task has just moved in the stack.
+     * <p>
+     * This is used to fix the following: If we moved in the stack, and if the last clip rect was
+     * empty, meaning that our task was completely offscreen, we need to keep it invisible because
+     * the actual app transition that updates the visibility is delayed by a few transactions.
+     * Instead of messing around with the ordering and timing how transitions and transactions are
+     * executed, we introduce this little hack which prevents this window of getting visible again
+     * with the wrong bounds until the app transitions has started.
+     * <p>
+     * This method notifies the window about that we just moved in the stack so we can apply this
+     * logic in {@link WindowStateAnimator#updateSurfaceWindowCrop}
+     */
+    void notifyMovedInStack() {
+        mJustMovedInStack = true;
+    }
+
+    /**
+     * See {@link #notifyMovedInStack}.
+     *
+     * @return Whether we just got moved in the corresponding stack.
+     */
+    boolean hasJustMovedInStack() {
+        return mJustMovedInStack;
+    }
+
+    /**
+     * Resets that we just moved in the corresponding stack. See {@link #notifyMovedInStack}.
+     */
+    void resetJustMovedInStack() {
+        mJustMovedInStack = false;
+    }
+
     private final class DeadWindowEventReceiver extends InputEventReceiver {
         DeadWindowEventReceiver(InputChannel inputChannel) {
             super(inputChannel, mService.mH.getLooper());
@@ -1836,6 +1879,12 @@
             return false;
         }
 
+        if (mResizedWhileGone) {
+            // Somebody resized our window while we were gone for layout, which means that the
+            // client got an old size, so we have an outdated surface here.
+            return false;
+        }
+
         if (DEBUG_DISABLE_SAVING_SURFACES) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0828417..2d8a4c9 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1191,6 +1191,11 @@
 
         w.transformFromScreenToSurfaceSpace(clipRect);
 
+        // See {@link WindowState#notifyMovedInStack} for why this is necessary.
+        if (w.hasJustMovedInStack() && mLastClipRect.isEmpty() && !clipRect.isEmpty()) {
+            clipRect.setEmpty();
+        }
+
         if (!clipRect.equals(mLastClipRect)) {
             mLastClipRect.set(clipRect);
             mSurfaceController.setCropInTransaction(clipRect, recoveringMemory);
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 93164de..0db6f3a 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -62,7 +62,7 @@
     // However, we need to somehow handle the situation where the cropping would completely hide
     // the window. We achieve this by explicitly hiding the surface and not letting it be shown.
     private boolean mHiddenForCrop = false;
-
+    private boolean mHiddenForOtherReasons = false;
     private final String title;
 
     public WindowSurfaceController(SurfaceSession s,
@@ -95,6 +95,11 @@
 
     void hideInTransaction(String reason) {
         if (SHOW_TRANSACTIONS) logSurface("HIDE ( " + reason + " )", null);
+        mHiddenForOtherReasons = true;
+        updateVisibility();
+    }
+
+    private void hideSurface() {
         if (mSurfaceControl != null) {
             mSurfaceShown = false;
             try {
@@ -152,9 +157,10 @@
             if (clipRect.width() > 0 && clipRect.height() > 0) {
                 mSurfaceControl.setWindowCrop(clipRect);
                 mHiddenForCrop = false;
+                updateVisibility();
             } else {
-                hideInTransaction("setCrop");
                 mHiddenForCrop = true;
+                updateVisibility();
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error setting crop surface of " + this
@@ -317,11 +323,26 @@
                 "SHOW (performLayout)", null);
         if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
                 + " during relayout");
+        mHiddenForOtherReasons = false;
+        return updateVisibility();
+    }
 
-        if (mHiddenForCrop) {
+    private boolean updateVisibility() {
+        if (mHiddenForCrop || mHiddenForOtherReasons) {
+            if (mSurfaceShown) {
+                hideSurface();
+            }
             return false;
+        } else {
+            if (!mSurfaceShown) {
+                return showSurface();
+            } else {
+                return true;
+            }
         }
+    }
 
+    private boolean showSurface() {
         try {
             mSurfaceShown = true;
             mSurfaceControl.show();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 33225eb..725f6bf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6743,57 +6743,6 @@
         }
     }
 
-    @Override
-    public UserHandle createUser(ComponentName who, String name) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
-        synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                UserInfo userInfo = mUserManager.createUser(name, 0 /* flags */);
-                if (userInfo != null) {
-                    return userInfo.getUserHandle();
-                }
-                return null;
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
-            }
-        }
-    }
-
-    @Override
-    public UserHandle createAndInitializeUser(ComponentName who, String name,
-            String ownerName, ComponentName profileOwnerComponent, Bundle adminExtras) {
-        UserHandle user = createUser(who, name);
-        if (user == null) {
-            return null;
-        }
-        long id = mInjector.binderClearCallingIdentity();
-        try {
-            String profileOwnerPkg = profileOwnerComponent.getPackageName();
-
-            final int userHandle = user.getIdentifier();
-            try {
-                // Install the profile owner if not present.
-                if (!mIPackageManager.isPackageAvailable(profileOwnerPkg, userHandle)) {
-                    mIPackageManager.installExistingPackageAsUser(profileOwnerPkg, userHandle);
-                }
-
-                // Start user in background.
-                mInjector.getIActivityManager().startUserInBackground(userHandle);
-            } catch (RemoteException e) {
-                Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e);
-            }
-
-            setActiveAdmin(profileOwnerComponent, true, userHandle, adminExtras);
-            setProfileOwner(profileOwnerComponent, ownerName, userHandle);
-            return user;
-        } finally {
-            mInjector.binderRestoreCallingIdentity(id);
-        }
-    }
-
     private void sendAdminEnabledBroadcastLocked(int userHandle) {
         DevicePolicyData policyData = getUserData(userHandle);
         if (policyData.mAdminBroadcastPending) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c854573..c75f98f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -341,8 +341,8 @@
             // always make sure uncrypt gets executed properly when needed.
             // If '/cache/recovery/block.map' hasn't been created, stop the
             // reboot which will fail for sure, and get a chance to capture a
-            // bugreport when that's still feasible. (Bug; 26444951)
-            if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
+            // bugreport when that's still feasible. (Bug: 26444951)
+            if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
                 File packageFile = new File(UNCRYPT_PACKAGE_FILE);
                 if (packageFile.exists()) {
                     String filename = null;
@@ -833,6 +833,10 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
 
+            if (!disableNonCoreServices) {
+                mSystemServiceManager.startService(RecoverySystemService.class);
+            }
+
             /*
              * MountService has a few dependencies: Notification Manager and
              * AppWidget Provider. Make sure MountService is completely started
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index c786036..e64481e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -12,6 +12,7 @@
 import android.util.Log;
 import android.util.ArraySet;
 
+import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
 
 import java.util.Iterator;
@@ -62,11 +63,11 @@
         mTaskStoreUnderTest.add(ts);
         Thread.sleep(IO_WAIT);
         // Manually load tasks from xml file.
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
 
         assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
-        final JobStatus loadedTaskStatus = jobStatusSet.iterator().next();
+        final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
         assertTasksEqual(task, loadedTaskStatus.getJob());
         assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
         assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
@@ -97,10 +98,10 @@
         mTaskStoreUnderTest.add(taskStatus2);
         Thread.sleep(IO_WAIT);
 
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
-        Iterator<JobStatus> it = jobStatusSet.iterator();
+        Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
         JobStatus loaded1 = it.next();
         JobStatus loaded2 = it.next();
 
@@ -145,10 +146,10 @@
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
 
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
-        JobStatus loaded = jobStatusSet.iterator().next();
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
         assertTasksEqual(task, loaded.getJob());
     }
     public void testWritingTaskWithSourcePackage() throws Exception {
@@ -163,10 +164,10 @@
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
 
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
-        JobStatus loaded = jobStatusSet.iterator().next();
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
         assertEquals("Source package not equal.", loaded.getSourcePackageName(),
                 taskStatus.getSourcePackageName());
         assertEquals("Source user not equal.", loaded.getSourceUserId(),
@@ -184,10 +185,10 @@
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
 
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
-        JobStatus loaded = jobStatusSet.iterator().next();
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
         assertEquals("Period not equal.", loaded.getJob().getIntervalMillis(),
                 taskStatus.getJob().getIntervalMillis());
         assertEquals("Flex not equal.", loaded.getJob().getFlexMillis(),
@@ -211,10 +212,10 @@
         mTaskStoreUnderTest.add(js);
         Thread.sleep(IO_WAIT);
 
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
-        JobStatus loaded = jobStatusSet.iterator().next();
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
 
         // Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll
         // call SystemClock.elapsedRealtime after doing the disk i/o.
@@ -234,9 +235,9 @@
         final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1);
         mTaskStoreUnderTest.add(js);
         Thread.sleep(IO_WAIT);
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
-        JobStatus loaded = jobStatusSet.iterator().next();
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
         assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
     }
 
@@ -255,10 +256,10 @@
         JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1);
         mTaskStoreUnderTest.add(jsPersisted);
         Thread.sleep(IO_WAIT);
-        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
         assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
-        JobStatus jobStatus = jobStatusSet.iterator().next();
+        JobStatus jobStatus = jobStatusSet.getAllJobs().iterator().next();
         assertEquals("Wrong job persisted.", 43, jobStatus.getJobId());
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index b4c4bf8..7ee7423 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -199,6 +199,7 @@
                 }
                 modelData.setHandle(handle[0]);
                 modelData.setLoaded();
+                Slog.d(TAG, "Generic sound model loaded with handle:" + handle[0]);
             }
             modelData.setCallback(callback);
             modelData.setRecognitionConfig(recognitionConfig);
@@ -227,7 +228,7 @@
 
         synchronized (mLock) {
             if (DBG) {
-                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
+                Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
                         + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
                         + ", recognitionConfig=" + recognitionConfig);
                 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
@@ -243,13 +244,13 @@
             }
 
             if (mModuleProperties == null) {
-                Slog.w(TAG, "Attempting startRecognition without the capability");
+                Slog.w(TAG, "Attempting startKeyphraseRecognition without the capability");
                 return STATUS_ERROR;
             }
             if (mModule == null) {
                 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
                 if (mModule == null) {
-                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
+                    Slog.w(TAG, "startKeyphraseRecognition cannot attach to sound trigger module");
                     return STATUS_ERROR;
                 }
             }
@@ -348,26 +349,29 @@
             }
 
             if (currentCallback == null || !modelData.isModelStarted()) {
-                // startRecognition hasn't been called or it failed.
-                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+                // startGenericRecognition hasn't been called or it failed.
+                Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
+                        " startGenericRecognition");
                 return STATUS_ERROR;
             }
             if (currentCallback.asBinder() != listener.asBinder()) {
                 // We don't allow a different listener to stop the recognition than the one
                 // that started it.
-                Slog.w(TAG, "Attempting stopRecognition for another recognition");
+                Slog.w(TAG, "Attempting stopGenericRecognition for another recognition");
                 return STATUS_ERROR;
             }
 
-            int status = stopGenericRecognitionLocked(modelData, false /* don't notify for synchronous calls */);
+            int status = stopGenericRecognitionLocked(modelData,
+                    false /* don't notify for synchronous calls */);
             if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "stopGenericRecognition failed: " + status);
                 return status;
             }
 
             // We leave the sound model loaded but not started, this helps us when we start
             // back.
             // Also clear the internal state once the recognition has been stopped.
-            modelData.clearState();
+            modelData.setLoaded();
             modelData.clearCallback();
             if (!computeRecognitionRunning()) {
                 internalClearGlobalStateLocked();
@@ -471,6 +475,66 @@
         return mModuleProperties;
     }
 
+    int unloadKeyphraseSoundModel(int keyphraseId) {
+        if (mModule == null || mCurrentKeyphraseModelHandle == INVALID_VALUE) {
+            return STATUS_ERROR;
+        }
+        if (mKeyphraseId != keyphraseId) {
+            Slog.w(TAG, "Given sound model is not the one loaded.");
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+            // Stop recognition if it's the current one.
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify */);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
+            }
+
+            status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
+            }
+            internalClearKeyphraseSoundModelLocked();
+            return status;
+        }
+    }
+
+    int unloadGenericSoundModel(UUID modelId) {
+        if (modelId == null || mModule == null) {
+            return STATUS_ERROR;
+        }
+        ModelData modelData = mGenericModelDataMap.get(modelId);
+        if (modelData == null) {
+            Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
+            return STATUS_ERROR;
+        }
+        synchronized (mLock) {
+            if (!modelData.isModelLoaded()) {
+                // Nothing to do here.
+                Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
+                return STATUS_OK;
+            }
+            if (modelData.isModelStarted()) {
+                int status = stopGenericRecognitionLocked(modelData,
+                        false /* don't notify for synchronous calls */);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "stopGenericRecognition failed: " + status);
+                }
+            }
+
+            int status = mModule.unloadSoundModel(modelData.getHandle());
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
+                Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
+            }
+            mGenericModelDataMap.remove(modelId);
+            if (DBG) dumpGenericModelState();
+            return status;
+        }
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
@@ -913,6 +977,7 @@
                 }
             }
         } else {
+            Slog.i(TAG, "startRecognition successful.");
             modelData.setStarted();
             // Notify of resume if needed.
             if (notify) {
@@ -923,6 +988,7 @@
                 }
             }
         }
+        if (DBG) dumpGenericModelState();
         return status;
     }
 
@@ -951,9 +1017,17 @@
                 }
             }
         }
+        if (DBG) dumpGenericModelState();
         return status;
     }
 
+    private void dumpGenericModelState() {
+        for (UUID modelId : mGenericModelDataMap.keySet()) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            Slog.i(TAG, "Model :" + modelData.toString());
+        }
+    }
+
     // Computes whether we have any recognition running at all (voice or generic). Sets
     // the mRecognitionRunning variable with the result.
     private boolean computeRecognitionRunning() {
@@ -1069,5 +1143,18 @@
         synchronized RecognitionConfig getRecognitionConfig() {
             return mRecognitionConfig;
         }
+
+        String stateToString() {
+            switch(mModelState) {
+                case MODEL_NOTLOADED: return "NOT_LOADED";
+                case MODEL_LOADED: return "LOADED";
+                case MODEL_STARTED: return "STARTED";
+            }
+            return "Unknown state";
+        }
+
+        public String toString() {
+            return "Handle: " + mModelHandle + "ModelState: " + stateToString();
+        }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index 7722876..113431f 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -75,5 +75,7 @@
 
     public abstract ModuleProperties getModuleProperties();
 
+    public abstract int unloadKeyphraseModel(int keyphaseId);
+
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 251f314..a4c1210 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -122,10 +122,10 @@
         public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
                 RecognitionConfig config) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
             if (DEBUG) {
                 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
             }
-            if (!isInitialized()) return STATUS_ERROR;
 
             GenericSoundModel model = getSoundModel(parcelUuid);
             if (model == null) {
@@ -173,6 +173,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
             }
+            // Unload the model if it is loaded.
+            mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
             mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
         }
     }
@@ -216,6 +218,12 @@
         }
 
         @Override
+        public int unloadKeyphraseModel(int keyphraseId) {
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!isInitialized()) return;
             mSoundTriggerHelper.dump(fd, pw, args);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4a54643..6ab0b99 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -760,6 +760,10 @@
             final long caller = Binder.clearCallingIdentity();
             boolean deleted = false;
             try {
+                int unloadStatus = mSoundTriggerInternal.unloadKeyphraseModel(keyphraseId);
+                if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
+                    Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
+                }
                 deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
                 return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
             } finally {
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index a72b3dd..dc4cdb5 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,10 +2,12 @@
         package="com.android.test.soundtrigger">
 
     <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
+    <uses-permission android:name="android.permission.WAKE_LOCk" />
     <application>
         <activity
             android:name="TestSoundTriggerActivity"
             android:label="SoundTrigger Test Application"
+            android:screenOrientation="portrait"
             android:theme="@android:style/Theme.Material">
             <!--
             <intent-filter>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 3149783..95e2dcf 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -28,6 +28,8 @@
 import android.text.Editable;
 import android.text.method.ScrollingMovementMethod;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
@@ -54,6 +56,8 @@
     private TextView mDebugView = null;
     private int mSelectedModelId = 1;
     private ScrollView mScrollView = null;
+    private PowerManager.WakeLock mScreenWakelock;
+    private Handler mHandler;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,6 +70,7 @@
         mDebugView.setMovementMethod(new ScrollingMovementMethod());
         mSoundTriggerUtil = new SoundTriggerUtil(this);
         mRandom = new Random();
+        mHandler = new Handler();
     }
 
     private void postMessage(String msg) {
@@ -85,24 +90,43 @@
         });
     }
 
-    private UUID getSelectedUuid() {
+    private synchronized UUID getSelectedUuid() {
         if (mSelectedModelId == 2) return mModelUuid2;
         if (mSelectedModelId == 3) return mModelUuid3;
         return mModelUuid1;  // Default.
     }
 
-    private void setDetector(SoundTriggerDetector detector) {
-        if (mSelectedModelId == 2) mDetector2 = detector;
-        if (mSelectedModelId == 3) mDetector3 = detector;
+    private synchronized void setDetector(SoundTriggerDetector detector) {
+        if (mSelectedModelId == 2) {
+            mDetector2 = detector;
+            return;
+        }
+        if (mSelectedModelId == 3) {
+            mDetector3 = detector;
+            return;
+        }
         mDetector1 = detector;
     }
 
-    private SoundTriggerDetector getDetector() {
+    private synchronized SoundTriggerDetector getDetector() {
         if (mSelectedModelId == 2) return mDetector2;
         if (mSelectedModelId == 3) return mDetector3;
         return mDetector1;
     }
 
+    private void screenWakeup() {
+        PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+        if (mScreenWakelock == null) {
+            mScreenWakelock =  pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "TAG");
+        }
+        mScreenWakelock.acquire();
+    }
+
+    private void screenRelease() {
+        PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+        mScreenWakelock.release();
+    }
+
     /**
      * Called when the user clicks the enroll button.
      * Performs a fresh enrollment.
@@ -139,7 +163,7 @@
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
         }
-        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid1);
+        boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
         if (status) {
             Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
                     Toast.LENGTH_SHORT)
@@ -204,22 +228,28 @@
         }
     }
 
-    public void onRadioButtonClicked(View view) {
+    public synchronized void onRadioButtonClicked(View view) {
         // Is the button now checked?
         boolean checked = ((RadioButton) view).isChecked();
         // Check which radio button was clicked
         switch(view.getId()) {
             case R.id.model_one:
-                if (checked) mSelectedModelId = 1;
-                postMessage("Selected model one.");
+                if (checked) {
+                    mSelectedModelId = 1;
+                    postMessage("Selected model one.");
+                }
                 break;
             case R.id.model_two:
-                if (checked) mSelectedModelId = 2;
-                postMessage("Selected model two.");
+                if (checked) {
+                    mSelectedModelId = 2;
+                    postMessage("Selected model two.");
+                }
                 break;
             case R.id.model_three:
-                if (checked) mSelectedModelId = 3;
-                postMessage("Selected model three.");
+                if (checked) {
+                    mSelectedModelId = 3;
+                    postMessage("Selected model three.");
+                }
                 break;
         }
     }
@@ -232,6 +262,13 @@
 
         public void onDetected(SoundTriggerDetector.EventPayload event) {
             postMessage("onDetected(): " + eventPayloadToString(event));
+            screenWakeup();
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                   screenRelease();
+                }
+            }, 1000L);
         }
 
         public void onError() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 01c3c50..5c74caf 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,15 +184,6 @@
     }
 
     @Override
-    public InputBindResult startInput(
-            /* @InputMethodClient.StartInputReason */ int startInputReason,
-            IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
-            int controlFlags) throws RemoteException {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
     public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub
         return false;
@@ -228,7 +219,7 @@
     }
 
     @Override
-    public InputBindResult windowGainedFocus(
+    public InputBindResult startInputOrWindowGainedFocus(
             /* @InputMethodClient.StartInputReason */ int startInputReason,
             IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
             int windowFlags, EditorInfo attribute, IInputContext inputContext)
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2373754..69e179d 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -30,7 +30,9 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.Protocol;
 
 import java.util.List;
@@ -78,6 +80,8 @@
     public static final int REASON_INVALID_REQUEST = -3;
     /** Invalid request */
     public static final int REASON_NOT_AUTHORIZED = -4;
+    /** An outstanding request with the same listener hasn't finished yet. */
+    public static final int REASON_DUPLICATE_REQEUST = -5;
 
     /** @hide */
     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
@@ -460,8 +464,11 @@
      *                 scans should also not share this object.
      */
     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, settings);
     }
     /**
      * stop an ongoing wifi scan
@@ -469,8 +476,11 @@
      *  #startBackgroundScan}
      */
     public void stopBackgroundScan(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
     }
     /**
      * reports currently available scan results on appropriate listeners
@@ -491,8 +501,11 @@
      *                 scans should also not share this object.
      */
     public void startScan(ScanSettings settings, ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, settings);
     }
 
     /**
@@ -501,8 +514,11 @@
      * @param listener
      */
     public void stopScan(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
     }
 
     /** specifies information about an access point of interest */
@@ -634,8 +650,11 @@
      *                 provided on {@link #stopTrackingWifiChange}
      */
     public void startTrackingWifiChange(WifiChangeListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
+        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
     }
 
     /**
@@ -644,8 +663,10 @@
      * #stopTrackingWifiChange}
      */
     public void stopTrackingWifiChange(WifiChangeListener listener) {
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
     }
 
     /** @hide */
@@ -730,11 +751,14 @@
      */
     public void startTrackingBssids(BssidInfo[] bssidInfos,
                                     int apLostThreshold, BssidListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
         HotlistSettings settings = new HotlistSettings();
         settings.bssidInfos = bssidInfos;
         settings.apLostThreshold = apLostThreshold;
-        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
     }
 
     /**
@@ -742,8 +766,11 @@
      * @param listener same object provided in {@link #startTrackingBssids}
      */
     public void stopTrackingBssids(BssidListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
     }
 
 
@@ -812,7 +839,7 @@
 
     private static final Object sThreadRefLock = new Object();
     private static int sThreadRefCount;
-    private static HandlerThread sHandlerThread;
+    private static Handler sInternalHandler;
 
     /**
      * Create a new WifiScanner instance.
@@ -824,12 +851,29 @@
      * @hide
      */
     public WifiScanner(Context context, IWifiScanner service) {
-        mContext = context;
-        mService = service;
-        init();
+        this(context, service, null, true);
     }
 
-    private void init() {
+    /**
+     * Create a new WifiScanner instance.
+     *
+     * @param context The application context.
+     * @param service The IWifiScanner Binder interface
+     * @param looper Looper for running WifiScanner operations. If null, a handler thread will be
+     *          created for running WifiScanner operations.
+     * @param waitForConnection If true, this will not return until a connection to Wifi Scanner
+     *          service is established.
+     * @hide
+     */
+    @VisibleForTesting
+    public WifiScanner(Context context, IWifiScanner service, Looper looper,
+            boolean waitForConnection) {
+        mContext = context;
+        mService = service;
+        init(looper, waitForConnection);
+    }
+
+    private void init(Looper looper, boolean waitForConnection) {
         synchronized (sThreadRefLock) {
             if (++sThreadRefCount == 1) {
                 Messenger messenger = null;
@@ -846,17 +890,23 @@
                     return;
                 }
 
-                sHandlerThread = new HandlerThread("WifiScanner");
                 sAsyncChannel = new AsyncChannel();
                 sConnected = new CountDownLatch(1);
 
-                sHandlerThread.start();
-                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
-                sAsyncChannel.connect(mContext, handler, messenger);
-                try {
-                    sConnected.await();
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "interrupted wait at init");
+                if (looper == null) {
+                    HandlerThread thread = new HandlerThread("WifiScanner");
+                    thread.start();
+                    sInternalHandler = new ServiceHandler(thread.getLooper());
+                } else {
+                    sInternalHandler = new ServiceHandler(looper);
+                }
+                sAsyncChannel.connect(mContext, sInternalHandler, messenger);
+                if (waitForConnection) {
+                    try {
+                        sConnected.await();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "interrupted wait at init");
+                    }
                 }
             }
         }
@@ -867,6 +917,30 @@
                 "No permission to access and change wifi or a bad initialization");
     }
 
+    // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
+    // send an error message to internal handler; Otherwise add the listener to the listener map and
+    // return the key of the listener.
+    private int addListener(ActionListener listener) {
+        synchronized (sListenerMap) {
+            boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
+            // Note we need to put the listener into listener map even if it's a duplicate as the
+            // internal handler will need the key to find the listener. In case of duplicates,
+            // removing duplicate key logic will be handled in internal handler.
+            int key = putListener(listener);
+            if (keyExists) {
+                if (DBG) Log.d(TAG, "listener key already exists");
+                OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
+                        "Outstanding request with same key not stopped yet");
+                Message message = Message.obtain(sInternalHandler, CMD_OP_FAILED, 0, key,
+                        operationResult);
+                message.sendToTarget();
+                return INVALID_KEY;
+            } else {
+                return key;
+            }
+        }
+    }
+
     private static int putListener(Object listener) {
         if (listener == null) return INVALID_KEY;
         int key;
@@ -910,7 +984,10 @@
 
     private static int removeListener(Object listener) {
         int key = getListenerKey(listener);
-        if (key == INVALID_KEY) return key;
+        if (key == INVALID_KEY) {
+            Log.e(TAG, "listener cannot be found");
+            return key;
+        }
         synchronized (sListenerMapLock) {
             sListenerMap.remove(key);
             return key;