merge in oc-release history after reset to master
diff --git a/Android.mk b/Android.mk
index 783fa6b..e324a75 100644
--- a/Android.mk
+++ b/Android.mk
@@ -282,6 +282,7 @@
 	core/java/android/service/notification/IStatusBarNotificationHolder.aidl \
 	core/java/android/service/notification/IConditionListener.aidl \
 	core/java/android/service/notification/IConditionProvider.aidl \
+	core/java/android/service/vr/IPersistentVrStateCallbacks.aidl \
 	core/java/android/service/vr/IVrListener.aidl \
 	core/java/android/service/vr/IVrManager.aidl \
 	core/java/android/service/vr/IVrStateCallbacks.aidl \
@@ -568,6 +569,7 @@
     android.hardware.thermal@1.0-java-constants         \
     android.hardware.health@1.0-java-constants          \
     android.hardware.usb@1.0-java-constants             \
+    android.hardware.vibrator@1.0-java-constants        \
 
 # Loaded with System.loadLibrary by android.view.textclassifier
 LOCAL_REQUIRED_MODULES += libtextclassifier
@@ -991,11 +993,13 @@
 
 framework_docs_LOCAL_DROIDDOC_OPTIONS += \
 		-hdf dac true \
-		-hdf sdk.codename N \
-		-hdf sdk.preview.version 5 \
+		-hdf sdk.codename O \
+		-hdf sdk.preview.version 1 \
 		-hdf sdk.version $(framework_docs_SDK_VERSION) \
 		-hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \
-		-hdf sdk.preview 0
+		-hdf sdk.preview 0 \
+		-resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \
+		-resourcesoutdir reference/android/images/
 
 # ====  the api stubs and current.xml ===========================
 include $(CLEAR_VARS)
@@ -1192,9 +1196,7 @@
 		-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
 		-sdkvalues $(OUT_DOCS) \
 		-hdf android.whichdoc offline \
-		-referenceonly \
-		-resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \
-		-resourcesoutdir reference/android/images/
+		-referenceonly
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=external/doclava/res/assets/templates-sdk
 
diff --git a/api/current.txt b/api/current.txt
index e6bacc0..ee4cada 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1179,6 +1179,7 @@
     field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
     field public static final int shrinkColumns = 16843082; // 0x101014a
     field public static final deprecated int singleLine = 16843101; // 0x101015d
+    field public static final int singleLineTitle = 16844127; // 0x101055f
     field public static final int singleUser = 16843711; // 0x10103bf
     field public static final int slideEdge = 16843824; // 0x1010430
     field public static final int smallIcon = 16843422; // 0x101029e
@@ -4844,6 +4845,7 @@
 
   public class Instrumentation {
     ctor public Instrumentation();
+    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
     method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -6749,6 +6751,7 @@
     method public boolean isRequireBatteryNotLow();
     method public boolean isRequireCharging();
     method public boolean isRequireDeviceIdle();
+    method public boolean isRequireStorageNotLow();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
     field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -6776,6 +6779,7 @@
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
     method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+    method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
     method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
     method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
     method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -7977,6 +7981,7 @@
     method public boolean isAnonymous();
     method public boolean isConnectable();
     method public boolean isLegacy();
+    method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
     field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8004,6 +8009,7 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
@@ -9479,7 +9485,7 @@
     field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
     field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
     field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
-    field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+    field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
     field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
     field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
     field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -10621,6 +10627,7 @@
     field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
     field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
     field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+    field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -31556,6 +31563,16 @@
     method public static long uptimeMillis();
   }
 
+  public class TestLooperManager {
+    method public void execute(android.os.Message);
+    method public android.os.MessageQueue getQueue();
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+    method public android.os.Message next();
+    method public void recycle(android.os.Message);
+    method public void release();
+  }
+
   public abstract class TokenWatcher {
     ctor public TokenWatcher(android.os.Handler, java.lang.String);
     method public void acquire(android.os.IBinder, java.lang.String);
@@ -31655,13 +31672,25 @@
     field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -32001,6 +32030,7 @@
     method public boolean isPersistent();
     method public boolean isRecycleEnabled();
     method public boolean isSelectable();
+    method public boolean isSingleLineTitle();
     method protected void notifyChanged();
     method public void notifyDependencyChange(boolean);
     method protected void notifyHierarchyChanged();
@@ -32042,6 +32072,7 @@
     method public void setRecycleEnabled(boolean);
     method public void setSelectable(boolean);
     method public void setShouldDisableView(boolean);
+    method public void setSingleLineTitle(boolean);
     method public void setSummary(java.lang.CharSequence);
     method public void setSummary(int);
     method public void setTitle(java.lang.CharSequence);
@@ -36838,9 +36869,11 @@
 
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(android.widget.RemoteViews);
+    ctor public Dataset.Builder();
     method public android.service.autofill.Dataset build();
     method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -46308,7 +46341,6 @@
     method public abstract int addChildCount(int);
     method public abstract void asyncCommit();
     method public abstract android.view.ViewStructure asyncNewChild(int);
-    method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
     method public abstract int getChildCount();
     method public abstract android.os.Bundle getExtras();
     method public abstract java.lang.CharSequence getHint();
@@ -46317,11 +46349,11 @@
     method public abstract int getTextSelectionStart();
     method public abstract boolean hasExtras();
     method public abstract android.view.ViewStructure newChild(int);
-    method public abstract android.view.ViewStructure newChild(int, int, int);
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHint(java.lang.String[]);
+    method public abstract void setAutofillId(android.view.ViewStructure, int);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46340,6 +46372,7 @@
     method public abstract void setFocused(boolean);
     method public abstract void setHint(java.lang.CharSequence);
     method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+    method public abstract void setIdEntry(java.lang.String);
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index 173aaf3..bf39eda 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1296,6 +1296,7 @@
     field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
     field public static final int shrinkColumns = 16843082; // 0x101014a
     field public static final deprecated int singleLine = 16843101; // 0x101015d
+    field public static final int singleLineTitle = 16844127; // 0x101055f
     field public static final int singleUser = 16843711; // 0x10103bf
     field public static final int slideEdge = 16843824; // 0x1010430
     field public static final int smallIcon = 16843422; // 0x101029e
@@ -5013,6 +5014,7 @@
 
   public class Instrumentation {
     ctor public Instrumentation();
+    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
     method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -7183,6 +7185,7 @@
     method public boolean isRequireBatteryNotLow();
     method public boolean isRequireCharging();
     method public boolean isRequireDeviceIdle();
+    method public boolean isRequireStorageNotLow();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
     field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -7210,6 +7213,7 @@
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
     method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+    method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
     method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
     method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
     method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -8450,6 +8454,7 @@
     method public boolean isAnonymous();
     method public boolean isConnectable();
     method public boolean isLegacy();
+    method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
     field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8477,6 +8482,7 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
@@ -10014,7 +10020,7 @@
     field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
     field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
     field public static final java.lang.String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
-    field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+    field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
     field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
     field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
     field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -11277,6 +11283,7 @@
     field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
     field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
     field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+    field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -34317,6 +34324,16 @@
     method public static long uptimeMillis();
   }
 
+  public class TestLooperManager {
+    method public void execute(android.os.Message);
+    method public android.os.MessageQueue getQueue();
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+    method public android.os.Message next();
+    method public void recycle(android.os.Message);
+    method public void release();
+  }
+
   public abstract class TokenWatcher {
     ctor public TokenWatcher(android.os.Handler, java.lang.String);
     method public void acquire(android.os.IBinder, java.lang.String);
@@ -34493,13 +34510,25 @@
   public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -34851,6 +34880,7 @@
     method public boolean isPersistent();
     method public boolean isRecycleEnabled();
     method public boolean isSelectable();
+    method public boolean isSingleLineTitle();
     method protected void notifyChanged();
     method public void notifyDependencyChange(boolean);
     method protected void notifyHierarchyChanged();
@@ -34892,6 +34922,7 @@
     method public void setRecycleEnabled(boolean);
     method public void setSelectable(boolean);
     method public void setShouldDisableView(boolean);
+    method public void setSingleLineTitle(boolean);
     method public void setSummary(java.lang.CharSequence);
     method public void setSummary(int);
     method public void setTitle(java.lang.CharSequence);
@@ -39872,9 +39903,11 @@
 
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(android.widget.RemoteViews);
+    ctor public Dataset.Builder();
     method public android.service.autofill.Dataset build();
     method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -49771,7 +49804,6 @@
     method public abstract int addChildCount(int);
     method public abstract void asyncCommit();
     method public abstract android.view.ViewStructure asyncNewChild(int);
-    method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
     method public abstract int getChildCount();
     method public abstract android.os.Bundle getExtras();
     method public abstract java.lang.CharSequence getHint();
@@ -49780,11 +49812,11 @@
     method public abstract int getTextSelectionStart();
     method public abstract boolean hasExtras();
     method public abstract android.view.ViewStructure newChild(int);
-    method public abstract android.view.ViewStructure newChild(int, int, int);
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHint(java.lang.String[]);
+    method public abstract void setAutofillId(android.view.ViewStructure, int);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -49803,6 +49835,7 @@
     method public abstract void setFocused(boolean);
     method public abstract void setHint(java.lang.CharSequence);
     method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+    method public abstract void setIdEntry(java.lang.String);
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 99d960b..40be895 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1179,6 +1179,7 @@
     field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
     field public static final int shrinkColumns = 16843082; // 0x101014a
     field public static final deprecated int singleLine = 16843101; // 0x101015d
+    field public static final int singleLineTitle = 16844127; // 0x101055f
     field public static final int singleUser = 16843711; // 0x10103bf
     field public static final int slideEdge = 16843824; // 0x1010430
     field public static final int smallIcon = 16843422; // 0x101029e
@@ -4854,6 +4855,7 @@
 
   public class Instrumentation {
     ctor public Instrumentation();
+    method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
     method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
     method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -6776,6 +6778,7 @@
     method public boolean isRequireBatteryNotLow();
     method public boolean isRequireCharging();
     method public boolean isRequireDeviceIdle();
+    method public boolean isRequireStorageNotLow();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
     field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -6803,6 +6806,7 @@
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
     method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
     method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+    method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
     method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
     method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
     method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -8004,6 +8008,7 @@
     method public boolean isAnonymous();
     method public boolean isConnectable();
     method public boolean isLegacy();
+    method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
     field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8031,6 +8036,7 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
@@ -9509,7 +9515,7 @@
     field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
     field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
     field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
-    field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+    field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
     field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
     field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
     field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -10657,6 +10663,7 @@
     field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
     field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
     field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+    field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -31679,6 +31686,16 @@
     method public static long uptimeMillis();
   }
 
+  public class TestLooperManager {
+    method public void execute(android.os.Message);
+    method public android.os.MessageQueue getQueue();
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+    method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+    method public android.os.Message next();
+    method public void recycle(android.os.Message);
+    method public void release();
+  }
+
   public abstract class TokenWatcher {
     ctor public TokenWatcher(android.os.Handler, java.lang.String);
     method public void acquire(android.os.IBinder, java.lang.String);
@@ -31780,13 +31797,25 @@
     field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -32126,6 +32155,7 @@
     method public boolean isPersistent();
     method public boolean isRecycleEnabled();
     method public boolean isSelectable();
+    method public boolean isSingleLineTitle();
     method protected void notifyChanged();
     method public void notifyDependencyChange(boolean);
     method protected void notifyHierarchyChanged();
@@ -32167,6 +32197,7 @@
     method public void setRecycleEnabled(boolean);
     method public void setSelectable(boolean);
     method public void setShouldDisableView(boolean);
+    method public void setSingleLineTitle(boolean);
     method public void setSummary(java.lang.CharSequence);
     method public void setSummary(int);
     method public void setTitle(java.lang.CharSequence);
@@ -36984,9 +37015,11 @@
 
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(android.widget.RemoteViews);
+    ctor public Dataset.Builder();
     method public android.service.autofill.Dataset build();
     method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -46677,7 +46710,6 @@
     method public abstract int addChildCount(int);
     method public abstract void asyncCommit();
     method public abstract android.view.ViewStructure asyncNewChild(int);
-    method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
     method public abstract int getChildCount();
     method public abstract android.os.Bundle getExtras();
     method public abstract java.lang.CharSequence getHint();
@@ -46686,11 +46718,11 @@
     method public abstract int getTextSelectionStart();
     method public abstract boolean hasExtras();
     method public abstract android.view.ViewStructure newChild(int);
-    method public abstract android.view.ViewStructure newChild(int, int, int);
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHint(java.lang.String[]);
+    method public abstract void setAutofillId(android.view.ViewStructure, int);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46709,6 +46741,7 @@
     method public abstract void setFocused(boolean);
     method public abstract void setHint(java.lang.CharSequence);
     method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+    method public abstract void setIdEntry(java.lang.String);
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 75d4f32..6d4b812 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -344,6 +344,17 @@
         "android.accounts.LOGIN_ACCOUNTS_CHANGED";
 
     /**
+     * Action sent as a broadcast Intent to specific package by the AccountsService
+     * when account visibility or account's credentials (saved password, etc) are changed.
+     *
+     * @see #addOnAccountsUpdatedListener
+     *
+     * @hide
+     */
+    public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
+        "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
+
+    /**
      * Key to set default visibility for applications targeting API level
      * {@link android.os.Build.VERSION_CODES#O} or above and don't have the same signature as
      * authenticator See {@link #getAccountVisibility}. If the value was not set by authenticator
@@ -1057,8 +1068,8 @@
 
     /**
      * Gets the previous name associated with the account or {@code null}, if
-     * none. This is intended so that clients of {@link
-     * #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
+     * none. This is intended so that clients of
+     * {@link OnAccountsUpdateListener} can determine if an
      * authenticator has renamed an account.
      *
      * <p>It is safe to call this method from the main thread.
@@ -1555,7 +1566,8 @@
      * <p>In that case, you may need to wait until the user responds, which
      * could take hours or days or forever.  When the user does respond and
      * supply a new password, the account manager will broadcast the
-     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
      * use to try again.
      *
      * <p>If notifyAuthFailure is not set, it is the application's
@@ -1631,7 +1643,8 @@
      * <p>In that case, you may need to wait until the user responds, which
      * could take hours or days or forever.  When the user does respond and
      * supply a new password, the account manager will broadcast the
-     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
      * use to try again.
      *
      * <p>If notifyAuthFailure is not set, it is the application's
@@ -2811,7 +2824,7 @@
             Maps.newHashMap();
 
     /**
-     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
+     * BroadcastReceiver that listens for the ACTION_VISIBLE_ACCOUNTS_CHANGED intent
      * so that it can read the updated list of accounts and send them to the listener
      * in mAccountsUpdatedListeners.
      */
@@ -2881,22 +2894,27 @@
             mAccountsUpdatedListeners.put(listener, handler);
             if (accountTypes != null) {
                 mAccountsUpdatedListenersTypes.put(listener,
-                        new HashSet<String>(Arrays.asList(accountTypes)));
+                    new HashSet<String>(Arrays.asList(accountTypes)));
+            } else {
+                mAccountsUpdatedListenersTypes.put(listener, null);
             }
 
             if (wasEmpty) {
                 // Register a broadcast receiver to monitor account changes
                 IntentFilter intentFilter = new IntentFilter();
-                // TODO get rid of the broadcast receiver
-                // create android.os.ResultReceiver
-                // send it to the service via aidl
-                // handle onReceiveResult
-                intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
+                intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
                 // To recover from disk-full.
                 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
-                // Register a broadcast receiver to monitor account changes
                 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
             }
+
+            try {
+                // Notify AccountManagedService about new receiver.
+                // The receiver must be unregistered later exactly one time
+                mService.registerAccountListener(accountTypes, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
         if (updateImmediately) {
             postToHandler(handler, listener, getAccounts());
@@ -2923,11 +2941,23 @@
                 Log.e(TAG, "Listener was not previously added");
                 return;
             }
+            Set<String> accountTypes = mAccountsUpdatedListenersTypes.get(listener);
+            String[] accountsArray;
+            if (accountTypes != null) {
+                accountsArray = accountTypes.toArray(new String[accountTypes.size()]);
+            } else {
+                accountsArray = null;
+            }
             mAccountsUpdatedListeners.remove(listener);
             mAccountsUpdatedListenersTypes.remove(listener);
             if (mAccountsUpdatedListeners.isEmpty()) {
                 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
             }
+            try {
+                mService.unregisterAccountListener(accountsArray, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 49cd2c6..7494cfc 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -117,6 +117,9 @@
     /* Type may be null returns Map <Account, Integer>*/
     Map getAccountsAndVisibilityForPackage(in String packageName, in String accountType);
 
+    void registerAccountListener(in String[] accountTypes, String opPackageName);
+    void unregisterAccountListener(in String[] accountTypes, String opPackageName);
+
     /* Check if the package in a user can access an account */
     boolean hasAccountAccess(in Account account, String packageName, in UserHandle userHandle);
     /* Crate an intent to request account access for package and a given user id */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8a0af9a..9f2f669 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2177,31 +2177,62 @@
         private final GraphicBuffer mSnapshot;
         private final int mOrientation;
         private final Rect mContentInsets;
+        private final boolean mReducedResolution;
+        private final float mScale;
 
-        public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets) {
+        public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
+                boolean reducedResolution, float scale) {
             mSnapshot = snapshot;
             mOrientation = orientation;
             mContentInsets = new Rect(contentInsets);
+            mReducedResolution = reducedResolution;
+            mScale = scale;
         }
 
         private TaskSnapshot(Parcel source) {
             mSnapshot = source.readParcelable(null /* classLoader */);
             mOrientation = source.readInt();
             mContentInsets = source.readParcelable(null /* classLoader */);
+            mReducedResolution = source.readBoolean();
+            mScale = source.readFloat();
         }
 
+        /**
+         * @return The graphic buffer representing the screenshot.
+         */
         public GraphicBuffer getSnapshot() {
             return mSnapshot;
         }
 
+        /**
+         * @return The screen orientation the screenshot was taken in.
+         */
         public int getOrientation() {
             return mOrientation;
         }
 
+        /**
+         * @return The system/content insets on the snapshot. These can be clipped off in order to
+         *         remove any areas behind system bars in the snapshot.
+         */
         public Rect getContentInsets() {
             return mContentInsets;
         }
 
+        /**
+         * @return Whether this snapshot is a down-sampled version of the full resolution.
+         */
+        public boolean isReducedResolution() {
+            return mReducedResolution;
+        }
+
+        /**
+         * @return The scale this snapshot was taken in.
+         */
+        public float getScale() {
+            return mScale;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -2212,12 +2243,15 @@
             dest.writeParcelable(mSnapshot, 0);
             dest.writeInt(mOrientation);
             dest.writeParcelable(mContentInsets, 0);
+            dest.writeBoolean(mReducedResolution);
+            dest.writeFloat(mScale);
         }
 
         @Override
         public String toString() {
             return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
-                    + " mContentInsets=" + mContentInsets.toShortString();
+                    + " mContentInsets=" + mContentInsets.toShortString()
+                    + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
         }
 
         public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f924cd5..e89dc0b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -204,6 +204,22 @@
     // Whether to invoke an activity callback after delivering new configuration.
     private static final boolean REPORT_TO_ACTIVITY = true;
 
+    /**
+     * Denotes an invalid sequence number corresponding to a process state change.
+     */
+    public static final long INVALID_PROC_STATE_SEQ = -1;
+
+    private final Object mNetworkPolicyLock = new Object();
+
+    /**
+     * Denotes the sequence number of the process state change for which the main thread needs
+     * to block until the network rules are updated for it.
+     *
+     * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
+     */
+    @GuardedBy("mNetworkPolicyLock")
+    private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+
     private ContextImpl mSystemContext;
 
     static volatile IPackageManager sPackageManager;
@@ -1324,6 +1340,18 @@
             }
         }
 
+        /**
+         * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
+         * the main thread that it needs to wait for the network rules to get updated before
+         * launching an activity.
+         */
+        @Override
+        public void setNetworkBlockSeq(long procStateSeq) {
+            synchronized (mNetworkPolicyLock) {
+                mNetworkBlockSeq = procStateSeq;
+            }
+        }
+
         @Override
         public void scheduleInstallProvider(ProviderInfo provider) {
             sendMessage(H.INSTALL_PROVIDER, provider);
@@ -2698,6 +2726,7 @@
                     activity.mIntent = customIntent;
                 }
                 r.lastNonConfigurationInstances = null;
+                checkAndBlockForNetworkAccess();
                 activity.mStartedActivity = false;
                 int theme = r.activityInfo.getThemeResource();
                 if (theme != 0) {
@@ -2764,6 +2793,22 @@
         return activity;
     }
 
+    /**
+     * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
+     * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
+     * network rules to get updated.
+     */
+    private void checkAndBlockForNetworkAccess() {
+        synchronized (mNetworkPolicyLock) {
+            if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
+                try {
+                    ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
+                    mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+                } catch (RemoteException ignored) {}
+            }
+        }
+    }
+
     private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
         final int displayId;
         try {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 55407e6..286f8570 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2139,11 +2139,12 @@
     @Override
     public void getPackageSizeInfoAsUser(String packageName, int userHandle,
             IPackageStatsObserver observer) {
+        final String msg = "Shame on you for calling the hidden API "
+                + "getPackageSizeInfoAsUser(). Shame!";
         if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
-            throw new UnsupportedOperationException(
-                    "Shame on you for calling a hidden API. Shame!");
+            throw new UnsupportedOperationException(msg);
         } else if (observer != null) {
-            Log.d(TAG, "Shame on you for calling a hidden API. Shame!");
+            Log.d(TAG, msg);
             try {
                 observer.onGetStatsCompleted(null, false);
             } catch (RemoteException ignored) {
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index c88448a..f564e8d 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -370,7 +370,7 @@
 
     public BackStackRecord(FragmentManagerImpl manager) {
         mManager = manager;
-        mAllowOptimization = getTargetSdk() > Build.VERSION_CODES.N_MR1;
+        mAllowOptimization = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1;
     }
 
     public int getId() {
@@ -423,7 +423,7 @@
     }
 
     private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
-        if (getTargetSdk() > Build.VERSION_CODES.N_MR1) {
+        if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
             final Class fragmentClass = fragment.getClass();
             final int modifiers = fragmentClass.getModifiers();
             if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
@@ -1022,22 +1022,4 @@
     public boolean isEmpty() {
         return mOps.isEmpty();
     }
-
-    /**
-     * @return the target SDK of the FragmentManager's application info. If the
-     * FragmentManager has been torn down, then 0 is returned.
-     */
-    private int getTargetSdk() {
-        FragmentHostCallback host = mManager.mHost;
-        if (host != null) {
-            Context context = host.getContext();
-            if (context != null) {
-                ApplicationInfo info = context.getApplicationInfo();
-                if (info != null) {
-                    return info.targetSdkVersion;
-                }
-            }
-        }
-        return 0;
-    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8a3d9b1..4232617 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -63,6 +63,7 @@
 import android.os.ServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -365,6 +366,13 @@
     @Override
     public SharedPreferences getSharedPreferences(File file, int mode) {
         checkMode(mode);
+        if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
+            if (isCredentialProtectedStorage()
+                    && !getSystemService(UserManager.class).isUserUnlocked()) {
+                throw new IllegalStateException("SharedPreferences in credential encrypted "
+                        + "storage are not available until after user is unlocked");
+            }
+        }
         SharedPreferencesImpl sp;
         synchronized (ContextImpl.class) {
             final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0672e3b..0d859a1 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -23,9 +23,11 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Looper;
@@ -674,6 +676,10 @@
     // Postponed transactions.
     ArrayList<StartEnterTransitionListener> mPostponedTransactions;
 
+    // Prior to O, we allowed executing transactions during fragment manager state changes.
+    // This is dangerous, but we want to keep from breaking old applications.
+    boolean mAllowOldReentrantBehavior;
+
     Runnable mExecCommit = new Runnable() {
         @Override
         public void run() {
@@ -2815,69 +2821,92 @@
         mHost = host;
         mContainer = container;
         mParent = parent;
+        mAllowOldReentrantBehavior = getTargetSdk() <= Build.VERSION_CODES.N_MR1;
     }
-    
+
+    /**
+     * @return the target SDK of the FragmentManager's application info. If the
+     * FragmentManager has been torn down, then 0 is returned.
+     */
+    int getTargetSdk() {
+        if (mHost != null) {
+            Context context = mHost.getContext();
+            if (context != null) {
+                ApplicationInfo info = context.getApplicationInfo();
+                if (info != null) {
+                    return info.targetSdkVersion;
+                }
+            }
+        }
+        return 0;
+    }
+
     public void noteStateNotSaved() {
         mStateSaved = false;
     }
     
     public void dispatchCreate() {
         mStateSaved = false;
-        mExecutingActions = true;
-        moveToState(Fragment.CREATED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.CREATED);
     }
     
     public void dispatchActivityCreated() {
         mStateSaved = false;
-        mExecutingActions = true;
-        moveToState(Fragment.ACTIVITY_CREATED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.ACTIVITY_CREATED);
     }
     
     public void dispatchStart() {
         mStateSaved = false;
-        mExecutingActions = true;
-        moveToState(Fragment.STARTED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.STARTED);
     }
     
     public void dispatchResume() {
         mStateSaved = false;
-        mExecutingActions = true;
-        moveToState(Fragment.RESUMED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.RESUMED);
     }
     
     public void dispatchPause() {
-        mExecutingActions = true;
-        moveToState(Fragment.STARTED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.STARTED);
     }
     
     public void dispatchStop() {
-        mExecutingActions = true;
-        moveToState(Fragment.STOPPED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.STOPPED);
     }
     
     public void dispatchDestroyView() {
-        mExecutingActions = true;
-        moveToState(Fragment.CREATED, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.CREATED);
     }
 
     public void dispatchDestroy() {
         mDestroyed = true;
         execPendingActions();
-        mExecutingActions = true;
-        moveToState(Fragment.INITIALIZING, false);
-        mExecutingActions = false;
+        dispatchMoveToState(Fragment.INITIALIZING);
         mHost = null;
         mContainer = null;
         mParent = null;
     }
 
+    /**
+     * This method is called by dispatch* methods to change the FragmentManager's state.
+     * It calls moveToState directly if the target SDK is older than O. Otherwise, it sets and
+     * clears mExecutingActions to ensure that there is no reentrancy while the
+     * FragmentManager is changing state.
+     *
+     * @param state The new state of the FragmentManager.
+     */
+    private void dispatchMoveToState(int state) {
+        if (mAllowOldReentrantBehavior) {
+            moveToState(state, false);
+        } else {
+            try {
+                mExecutingActions = true;
+                moveToState(state, false);
+            } finally {
+                mExecutingActions = false;
+            }
+        }
+    }
+
     public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
         if (mAdded == null) {
             return;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f5e0c38..d940857 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,13 +600,18 @@
     void cancelTaskThumbnailTransition(int taskId);
 
     /**
+     * @param taskId the id of the task to retrieve the snapshots for
+     * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
+     *                          a reduced resolution of it, which is much faster
      * @return a graphic buffer representing a screenshot of a task
      */
-    ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+    ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution);
 
     void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
     void setPersistentVrThread(int tid);
 
+    void waitForNetworkStateUpdate(long procStateSeq);
+
     // WARNING: when these transactions are updated, check if they are any callers on the native
     // side. If so, make sure they are using the correct transaction ids and arguments.
     // If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index d5b4668..e99691d 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -154,4 +154,5 @@
     void handleTrustStorageUpdate();
     void attachAgent(String path);
     void scheduleApplicationInfoChanged(in ApplicationInfo ai);
+    void setNetworkBlockSeq(long procStateSeq);
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 4db29fb..f9a3ea7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -37,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.TestLooperManager;
 import android.os.UserHandle;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
@@ -110,6 +111,22 @@
     }
 
     /**
+     * Called for methods that shouldn't be called by standard apps and
+     * should only be used in instrumentation environments. This is not
+     * security feature as these classes will still be accessible through
+     * reflection, but it will serve as noticeable discouragement from
+     * doing such a thing.
+     */
+    private void checkInstrumenting(String method) {
+        // Check if we have an instrumentation context, as init should only get called by
+        // the system in startup processes that are being instrumented.
+        if (mInstrContext == null) {
+            throw new RuntimeException(method +
+                    " cannot be called outside of instrumented processes");
+        }
+    }
+
+    /**
      * Called when the instrumentation is starting, before any application code
      * has been loaded.  Usually this will be implemented to simply call
      * {@link #start} to begin the instrumentation thread, which will then
@@ -2024,6 +2041,15 @@
         return null;
     }
 
+    /**
+     * Takes control of the execution of messages on the specified looper until
+     * {@link TestLooperManager#release} is called.
+     */
+    public TestLooperManager acquireLooperManager(Looper looper) {
+        checkInstrumenting("acquireLooperManager");
+        return new TestLooperManager(looper);
+    }
+
     private final class InstrumentationThread extends Thread {
         public InstrumentationThread(String name) {
             super(name);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91b87d7..790a952 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -172,6 +172,25 @@
                 return new SecurityEvent[size];
             }
         };
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SecurityEvent other = (SecurityEvent) o;
+            return mEvent.equals(other.mEvent);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            return mEvent.hashCode();
+        }
     }
     /**
      * Retrieve all security logs and return immediately.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 27bfb51..d5436b7 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,6 @@
 package android.app.assist;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -639,6 +640,7 @@
         static final int FLAGS_HAS_CHILDREN = 0x00100000;
         static final int FLAGS_HAS_URL = 0x00080000;
         static final int FLAGS_HAS_INPUT_TYPE = 0x00040000;
+        static final int FLAGS_HAS_ENTRY_ID = 0x00020000;
         static final int FLAGS_ALL_CONTROL = 0xfff00000;
 
         int mFlags;
@@ -672,7 +674,10 @@
                         mIdPackage = preader.readString();
                     }
                 }
+            } else if ((flags&FLAGS_HAS_ENTRY_ID) != 0) {
+                mIdEntry = preader.readString();
             }
+
             if ((flags&FLAGS_HAS_AUTOFILL_DATA) != 0) {
                 mSanitized = in.readInt() == 1;
                 mAutofillId = in.readParcelable(null);
@@ -745,6 +750,8 @@
             int flags = mFlags & ~FLAGS_ALL_CONTROL;
             if (mId != View.NO_ID) {
                 flags |= FLAGS_HAS_ID;
+            } else if (mIdEntry != null ){
+                flags |= FLAGS_HAS_ENTRY_ID;
             }
             if (mAutofillId != null) {
                 flags |= FLAGS_HAS_AUTOFILL_DATA;
@@ -805,7 +812,10 @@
                         pwriter.writeString(mIdPackage);
                     }
                 }
+            } else if ((flags&FLAGS_HAS_ENTRY_ID) != 0) {
+                pwriter.writeString(mIdEntry);
             }
+
             if ((flags&FLAGS_HAS_AUTOFILL_DATA) != 0) {
                 writeSensitive = mSanitized || !sanitizeOnWrite;
                 out.writeInt(mSanitized ? 1 : 0);
@@ -887,6 +897,10 @@
          * If {@link #getId()} is a resource identifier, this is the entry name of that
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
+         *
+         * <p>If the node represents a virtual view, it could also represent the entry id set by
+         *  {@link android.view.ViewStructure#setIdEntry ViewStructure.setIdEntry}
+         *
          */
         public String getIdEntry() {
             return mIdEntry;
@@ -1361,6 +1375,11 @@
         }
 
         @Override
+        public void setIdEntry(String entryName) {
+            mNode.mIdEntry = entryName;
+        }
+
+        @Override
         public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
             mNode.mX = left;
             mNode.mY = top;
@@ -1583,23 +1602,22 @@
             return mNode.mChildren != null ? mNode.mChildren.length : 0;
         }
 
-        private void setAutofillId(ViewNode child, boolean forAutoFill, int virtualId) {
-            if (forAutoFill) {
-                child.mAutofillId = new AutofillId(mNode.mAutofillId, virtualId);
-            }
+        @Override
+        public void setAutofillId(@NonNull ViewStructure parent, int virtualId) {
+            mNode.mAutofillId = new AutofillId(parent.getAutofillId(), virtualId);
         }
 
-        private ViewStructure newChild(int index, boolean forAutoFill, int virtualId, int flags) {
+        @Override
+        public ViewStructure newChild(int index) {
             ViewNode node = new ViewNode();
-            setAutofillId(node, forAutoFill, virtualId);
             mNode.mChildren[index] = node;
             return new ViewNodeBuilder(mAssist, node, false);
         }
 
-        private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
+        @Override
+        public ViewStructure asyncNewChild(int index) {
             synchronized (mAssist) {
                 ViewNode node = new ViewNode();
-                setAutofillId(node, forAutoFill, virtualId);
                 mNode.mChildren[index] = node;
                 ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
                 mAssist.mPendingAsyncChildren.add(builder);
@@ -1608,26 +1626,6 @@
         }
 
         @Override
-        public ViewStructure newChild(int index) {
-            return newChild(index, false, 0, 0);
-        }
-
-        @Override
-        public ViewStructure newChild(int index, int virtualId, int flags) {
-            return newChild(index, true, virtualId, flags);
-        }
-
-        @Override
-        public ViewStructure asyncNewChild(int index) {
-            return asyncNewChild(index, false, 0);
-        }
-
-        @Override
-        public ViewStructure asyncNewChild(int index, int virtualId, int flags) {
-            return asyncNewChild(index, true, virtualId);
-        }
-
-        @Override
         public void asyncCommit() {
             synchronized (mAssist) {
                 if (!mAsync) {
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 6652eee..78e4c0d 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -189,6 +189,11 @@
      */
     public static final int CONSTRAINT_FLAG_DEVICE_IDLE = 1 << 2;
 
+    /**
+     * @hide
+     */
+    public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3;
+
     private final int jobId;
     private final PersistableBundle extras;
     private final Bundle transientExtras;
@@ -273,6 +278,13 @@
     }
 
     /**
+     * Whether this job needs the device's storage to not be low.
+     */
+    public boolean isRequireStorageNotLow() {
+        return (constraintFlags & CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0;
+    }
+
+    /**
      * @hide
      */
     public int getConstraintFlags() {
@@ -710,15 +722,33 @@
         }
 
         /**
+         * Specify that to run this job, the device's available storage must not be low.
+         * This defaults to false.  If true, the job will only run when the device is not
+         * in a low storage state, which is generally the point where the user is given a
+         * "low storage" warning.
+         * @param storageNotLow Whether or not the device's available storage must not be low.
+         */
+        public Builder setRequiresStorageNotLow(boolean storageNotLow) {
+            mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW)
+                    | (storageNotLow ? CONSTRAINT_FLAG_STORAGE_NOT_LOW : 0);
+            return this;
+        }
+
+        /**
          * Add a new content: URI that will be monitored with a
          * {@link android.database.ContentObserver}, and will cause the job to execute if changed.
          * If you have any trigger content URIs associated with a job, it will not execute until
          * there has been a change report for one or more of them.
+         *
          * <p>Note that trigger URIs can not be used in combination with
          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
          * for content changes, you need to schedule a new JobInfo observing the same URIs
-         * before you finish execution of the JobService handling the most recent changes.</p>
-         * <p>Because because setting this property is not compatible with periodic or
+         * before you finish execution of the JobService handling the most recent changes.
+         * Following this pattern will ensure you do not lost any content changes: while your
+         * job is running, the system will continue monitoring for content changes, and propagate
+         * any it sees over to the next job you schedule.</p>
+         *
+         * <p>Because setting this property is not compatible with periodic or
          * persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
          * {@link android.app.job.JobInfo.Builder#build()} is called.</p>
          *
diff --git a/core/java/android/app/job/JobService.java b/core/java/android/app/job/JobService.java
index 77307b7..f4019ce 100644
--- a/core/java/android/app/job/JobService.java
+++ b/core/java/android/app/job/JobService.java
@@ -250,7 +250,7 @@
     public abstract boolean onStopJob(JobParameters params);
 
     /**
-     * Callback to inform the JobManager you've finished executing. This can be called from any
+     * Call this to inform the JobManager you've finished executing. This can be called from any
      * thread, as it will ultimately be run on your application's main thread. When the system
      * receives this message it will release the wakelock being held.
      * <p>
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index 59fef8d..fe1f425 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -116,14 +116,16 @@
     private final int primaryPhy;
     private final int secondaryPhy;
     private final boolean connectable;
+    private final boolean scannable;
     private final int interval;
     private final int txPowerLevel;
 
-    private AdvertisingSetParameters(boolean connectable, boolean isLegacy,
+    private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
                                      boolean isAnonymous, boolean includeTxPower,
                                      int primaryPhy, int secondaryPhy,
                                      int interval, int txPowerLevel) {
         this.connectable = connectable;
+        this.scannable = scannable;
         this.isLegacy = isLegacy;
         this.isAnonymous = isAnonymous;
         this.includeTxPower = includeTxPower;
@@ -135,6 +137,7 @@
 
     private AdvertisingSetParameters(Parcel in) {
         connectable = in.readInt() != 0 ? true : false;
+        scannable = in.readInt() != 0 ? true : false;
         isLegacy = in.readInt() != 0 ? true : false;
         isAnonymous = in.readInt() != 0 ? true : false;
         includeTxPower = in.readInt() != 0 ? true : false;
@@ -150,6 +153,11 @@
     public boolean isConnectable() { return connectable; }
 
     /**
+     * Returns whether the advertisement will be scannable.
+     */
+    public boolean isScannable() { return scannable; }
+
+    /**
      * Returns whether the legacy advertisement will be used.
      */
     public boolean isLegacy() { return isLegacy; }
@@ -204,6 +212,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(connectable ? 1 : 0);
+        dest.writeInt(scannable ? 1 : 0);
         dest.writeInt(isLegacy ? 1 : 0);
         dest.writeInt(isAnonymous ? 1 : 0);
         dest.writeInt(includeTxPower ? 1 : 0);
@@ -232,6 +241,7 @@
     public static final class Builder {
 
         private boolean connectable = true;
+        private boolean scannable = true;
         private boolean isLegacy = false;
         private boolean isAnonymous = false;
         private boolean includeTxPower = false;
@@ -254,6 +264,18 @@
         }
 
         /**
+         * Set whether the advertisement type should be scannable
+         * Legacy advertisements can be both connectable and scannable. Other
+         * advertisements can be scannable only if not connectable.
+         * @param scannable Controls whether the advertisment type will be
+         * scannable (true) or non-scannable (false).
+         */
+        public Builder setScannable(boolean scannable) {
+            this.scannable = scannable;
+            return this;
+        }
+
+        /**
          * When set to true, advertising set will advertise 4.x Spec compliant
          * advertisements.
          *
@@ -371,7 +393,7 @@
          * Build the {@link AdvertisingSetParameters} object.
          */
         public AdvertisingSetParameters build() {
-            return new AdvertisingSetParameters(connectable, isLegacy, isAnonymous,
+            return new AdvertisingSetParameters(connectable, scannable, isLegacy, isAnonymous,
                                                 includeTxPower, primaryPhy,
                                                 secondaryPhy, interval, txPowerLevel);
         }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 4457bdd..ae012d9 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -130,6 +130,7 @@
             AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
             parameters.setLegacyMode(true);
             parameters.setConnectable(isConnectable);
+            parameters.setScannable(true); // legacy advertisements we support are always scannable
             if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                 parameters.setInterval(1600); // 1s
             } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
@@ -157,7 +158,9 @@
 
     AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
         return new AdvertisingSetCallback() {
-            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {
+            @Override
+            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+                        int status) {
                 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
                     postStartFailure(callback, status);
                     return;
@@ -167,7 +170,9 @@
             }
 
             /* Legacy advertiser is disabled on timeout */
-            public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
+            @Override
+            public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
+                        int status) {
                 if (enabled == true) {
                     Log.e(TAG, "Legacy advertiser should be only disabled on timeout," +
                         " but was enabled!");
@@ -400,6 +405,7 @@
 
     IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
         return new IAdvertisingSetCallback.Stub() {
+            @Override
             public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -418,6 +424,7 @@
                 });
             }
 
+            @Override
             public void onAdvertisingSetStopped(int advertiserId) {
                 handler.post(new Runnable() {
                     @Override
@@ -430,6 +437,7 @@
                 });
             }
 
+            @Override
             public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -440,6 +448,7 @@
                 });
             }
 
+            @Override
             public void onAdvertisingDataSet(int advertiserId, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -450,6 +459,7 @@
                 });
             }
 
+            @Override
             public void onScanResponseDataSet(int advertiserId, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -460,6 +470,7 @@
                 });
             }
 
+            @Override
             public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -470,6 +481,7 @@
                 });
             }
 
+            @Override
             public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -480,6 +492,7 @@
                 });
             }
 
+            @Override
             public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
                 handler.post(new Runnable() {
                     @Override
@@ -490,6 +503,7 @@
                 });
             }
 
+            @Override
             public void onPeriodicAdvertisingEnable(int advertiserId, boolean enable, int status) {
                 handler.post(new Runnable() {
                     @Override
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5710ad1..ecdc0ce 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -22,10 +22,13 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.IntentSender;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.Log;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -40,6 +43,9 @@
  */
 public final class CompanionDeviceManager {
 
+    private static final boolean DEBUG = false; //TODO
+    private static final String LOG_TAG = "CompanionDeviceManager";
+
     /**
      * A device, returned in the activity result of the {@link IntentSender} received in
      * {@link Callback#onDeviceFound}
@@ -81,7 +87,7 @@
 
     /** @hide */
     public CompanionDeviceManager(
-            @NonNull ICompanionDeviceManager service, @NonNull Context context) {
+            @Nullable ICompanionDeviceManager service, @NonNull Context context) {
         mService = service;
         mContext = context;
     }
@@ -120,6 +126,10 @@
             @NonNull AssociationRequest request,
             @NonNull Callback callback,
             @Nullable Handler handler) {
+        if (!checkFeaturePresent()) {
+            return;
+        }
+
         final Handler finalHandler = handler != null
                 ? handler
                 : new Handler(Looper.getMainLooper());
@@ -153,6 +163,9 @@
      */
     @NonNull
     public List<String> getAssociations() {
+        if (!checkFeaturePresent()) {
+            return Collections.emptyList();
+        }
         try {
             return mService.getAssociations(mContext.getPackageName());
         } catch (RemoteException e) {
@@ -172,6 +185,9 @@
      * @param deviceMacAddress the MAC address of device to disassociate from this app
      */
     public void disassociate(@NonNull String deviceMacAddress) {
+        if (!checkFeaturePresent()) {
+            return;
+        }
         try {
             mService.disassociate(deviceMacAddress, mContext.getPackageName());
         } catch (RemoteException e) {
@@ -181,14 +197,28 @@
 
     /** @hide */
     public void requestNotificationAccess() {
+        if (!checkFeaturePresent()) {
+            return;
+        }
         //TODO implement
         throw new UnsupportedOperationException("Not yet implemented");
     }
 
     /** @hide */
     public boolean haveNotificationAccess() {
+        if (!checkFeaturePresent()) {
+            return false;
+        }
         //TODO implement
         throw new UnsupportedOperationException("Not yet implemented");
     }
 
+    private boolean checkFeaturePresent() {
+        boolean featurePresent = mService == null;
+        if (!featurePresent && DEBUG) {
+            Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
+                    + " not available");
+        }
+        return featurePresent;
+    }
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 4ffc6f9..d75c2ee0 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -620,12 +620,17 @@
             return MODE_IGNORED;
         }
 
-        final String failReason = mExported
-                ? " requires " + missingPerm + ", or grantUriPermission()"
-                : " requires the provider be exported, or grantUriPermission()";
+        final String suffix;
+        if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(mReadPermission)) {
+            suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
+        } else if (mExported) {
+            suffix = " requires " + missingPerm + ", or grantUriPermission()";
+        } else {
+            suffix = " requires the provider be exported, or grantUriPermission()";
+        }
         throw new SecurityException("Permission Denial: reading "
                 + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
-                + ", uid=" + uid + failReason);
+                + ", uid=" + uid + suffix);
     }
 
     /** {@hide} */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index bd31b03..fb86791 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import static android.content.ContentProvider.maybeAddUserId;
+
 import android.annotation.AnyRes;
 import android.annotation.BroadcastBehavior;
 import android.annotation.IntDef;
@@ -41,7 +43,6 @@
 import android.os.ShellCommand;
 import android.os.StrictMode;
 import android.os.UserHandle;
-import android.os.storage.StorageManager;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
@@ -49,7 +50,9 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
+
 import com.android.internal.util.XmlUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -67,8 +70,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import static android.content.ContentProvider.maybeAddUserId;
-
 /**
  * An intent is an abstract description of an operation to be performed.  It
  * can be used with {@link Context#startActivity(Intent) startActivity} to
@@ -3854,9 +3855,23 @@
     public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
 
     /**
-     * A content: URI holding a stream of data associated with the Intent,
-     * used with {@link #ACTION_SEND} to supply the data being sent.
+     * A content: URI holding a stream of data associated with the Intent, used
+     * with {@link #ACTION_SEND} to supply the data being sent.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN} this value
+     * will be automatically promoted to {@link Intent#setClipData(ClipData)}
+     * when that value is not already defined.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#O} this value will be
+     * automatically demoted from {@link Intent#getClipData()} when this value
+     * is not already defined.
+     *
+     * @deprecated apps should use {@link Intent#setClipData(ClipData)} and
+     *             {@link Intent#getClipData()} instead of this extra, since
+     *             only those APIs can extend temporary permission grants to the
+     *             underlying resource.
      */
+    @Deprecated
     public static final String EXTRA_STREAM = "android.intent.extra.STREAM";
 
     /**
@@ -5123,6 +5138,8 @@
     private Intent mSelector;
     private ClipData mClipData;
     private int mContentUserHint = UserHandle.USER_CURRENT;
+    /** Token to track instant app launches. Local only; do not copy cross-process. */
+    private String mLaunchToken;
 
     // ---------------------------------------------------------------------
 
@@ -5143,6 +5160,7 @@
         this.mComponent = o.mComponent;
         this.mFlags = o.mFlags;
         this.mContentUserHint = o.mContentUserHint;
+        this.mLaunchToken = o.mLaunchToken;
         if (o.mCategories != null) {
             this.mCategories = new ArraySet<String>(o.mCategories);
         }
@@ -6379,6 +6397,16 @@
         return mContentUserHint;
     }
 
+    /** @hide */
+    public String getLaunchToken() {
+        return mLaunchToken;
+    }
+
+    /** @hide */
+    public void setLaunchToken(String launchToken) {
+        mLaunchToken = launchToken;
+    }
+
     /**
      * Sets the ClassLoader that will be used when unmarshalling
      * any Parcelable values from the extras of this Intent.
@@ -9362,6 +9390,21 @@
                 mContentUserHint = UserHandle.USER_CURRENT;
             }
         }
+
+        // If someone is sending us ClipData, but not EXTRA_STREAM, offer to
+        // downgrade that content for older apps to find
+        if (mClipData != null && mClipData.getItemCount() > 0 && !hasExtra(EXTRA_STREAM)) {
+            final String action = getAction();
+            if (ACTION_SEND.equals(action)) {
+                putExtra(EXTRA_STREAM, mClipData.getItemAt(0).getUri());
+            } else if (ACTION_SEND_MULTIPLE.equals(action)) {
+                final ArrayList<Uri> list = new ArrayList<>();
+                for (int i = 0; i < mClipData.getItemCount(); i++) {
+                    list.add(mClipData.getItemAt(i).getUri());
+                }
+                putExtra(EXTRA_STREAM, list);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 0b3742f..ffc7719 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -920,7 +920,7 @@
 
     /**
      * Category for apps which are primarily social apps, such as messaging,
-     * communication, or social network apps.
+     * communication, email, or social network apps.
      *
      * @see #category
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6dd1833..bb35928 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -46,6 +46,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -58,6 +59,8 @@
 
 import com.android.internal.util.ArrayUtils;
 
+import dalvik.system.VMRuntime;
+
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -2233,6 +2236,15 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device supports {@link android.companion.CompanionDeviceManager#associate associating}
+     * with devices via {@link android.companion.CompanionDeviceManager}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_COMPANION_DEVICE_SETUP
+            = "android.software.companion_device_setup";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device can perform backup and restore operations on installed applications.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -4252,8 +4264,14 @@
     @Deprecated
     public List<ResolveInfo> queryBroadcastReceivers(Intent intent,
             @ResolveInfoFlags int flags, @UserIdInt int userId) {
-        Log.w(TAG, "STAHP USING HIDDEN APIS KTHX");
-        return queryBroadcastReceiversAsUser(intent, flags, userId);
+        final String msg = "Shame on you for calling the hidden API "
+                + "queryBroadcastReceivers(). Shame!";
+        if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) {
+            throw new UnsupportedOperationException(msg);
+        } else {
+            Log.d(TAG, msg);
+            return queryBroadcastReceiversAsUser(intent, flags, userId);
+        }
     }
 
     /**
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 949d644..ccf30ac 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -74,8 +74,6 @@
     private static final boolean TRACE_FOR_PRELOAD = false;
     private static final boolean TRACE_FOR_MISS_PRELOAD = false;
 
-    private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
-            ActivityInfo.CONFIG_LAYOUT_DIRECTION);
 
     private static final int ID_OTHER = 0x01000004;
 
@@ -636,8 +634,8 @@
                 }
             } else {
                 if (verifyPreloadConfig(
-                        changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
-                    if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
+                        changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
+                    if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
                         // If this resource does not vary based on layout direction,
                         // we can put it in all of the preload maps.
                         sPreloadedDrawables[0].put(key, cs);
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
index fbd039d..5f42f30 100644
--- a/core/java/android/database/PageViewCursor.java
+++ b/core/java/android/database/PageViewCursor.java
@@ -19,6 +19,7 @@
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.util.MathUtils;
@@ -33,22 +34,24 @@
  *
  * @hide
  */
-public final class PageViewCursor extends CrossProcessCursorWrapper {
+public final class PageViewCursor extends CursorWrapper implements CrossProcessCursor {
 
     /**
-     * An extra added to results that are auto-paged using the wrapper.
+     * An in internal extra added to results that are auto-paged using the wrapper.
      */
     public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
 
     private static final String TAG = "PageViewCursor";
-    private static final boolean DEBUG = false;
-    private static final boolean VERBOSE = false;
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+    private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
 
     private final int mOffset;  // aka first index
     private final int mCount;
     private final Bundle mExtras;
 
+    private @Nullable CursorWindow mWindow;
     private int mPos = -1;
+    private int mWindowFillCount = 0;
 
     /**
      * @see PageViewCursor#wrap(Cursor, Bundle)
@@ -195,6 +198,33 @@
         return mCount;
     }
 
+    @Override
+    public boolean getWantsAllOnMoveCalls() {
+        return false;  // we want bulk cursor adapter to lift data into a CursorWindow.
+    }
+
+    @Override
+    public CursorWindow getWindow() {
+        assert(mPos == -1 || mPos == 0);
+        if (mWindow == null) {
+           mWindow = new CursorWindow("PageViewCursorWindow");
+           fillWindow(0, mWindow);
+        }
+
+        return mWindow;
+    }
+
+    @Override
+    public void fillWindow(int position, CursorWindow window) {
+        assert(window == mWindow);
+
+        if (mWindowFillCount++ > 0) {
+            Log.w(TAG, "Re-filling window on paged cursor! Reduce ContentResolver.QUERY_ARG_LIMIT");
+        }
+
+        DatabaseUtils.cursorFillWindow(this, position, window);
+    }
+
     /**
      * Wraps the cursor such that it will honor paging args (if present), AND if the cursor
      * does not report paging size.
@@ -209,12 +239,19 @@
                 || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
 
         if (!hasPagingArgs) {
-            if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request.");
+            if (VERBOSE) Log.v(TAG, "No-wrap: No paging args in request.");
             return cursor;
         }
 
         if (hasPagedResponseDetails(cursor.getExtras())) {
-            if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details.");
+            if (VERBOSE) Log.v(TAG, "No-wrap. Cursor has paging details.");
+            return cursor;
+        }
+
+        // Cursors that want all calls aren't compatible with our way
+        // of doing business. TODO: Cover this case in CTS.
+        if (cursor.getWantsAllOnMoveCalls()) {
+            Log.w(TAG, "Unable to wrap cursor that wants to hear about move calls.");
             return cursor;
         }
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b0..631b77d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@
             return true;
         }
 
-        /**
-         * @hide
-         */
         @Override
-        public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
-            vibrate(new long[] { 0, milliseconds}, -1);
+        public boolean hasAmplitudeControl() {
+            return false;
         }
 
         /**
          * @hide
          */
         @Override
-        public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-                AudioAttributes attributes) {
-            if (repeat >= pattern.length) {
-                throw new ArrayIndexOutOfBoundsException();
+        public void vibrate(int uid, String opPkg,
+                VibrationEffect effect, AudioAttributes attributes) {
+            long[] pattern;
+            int repeat;
+            if (effect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+                pattern = new long[] { 0, oneShot.getTiming() };
+                repeat = -1;
+            } else if (effect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+                pattern = waveform.getTimings();
+                repeat = waveform.getRepeatIndex();
+            } else {
+                // TODO: Add support for prebaked effects
+                Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+                return;
             }
+
             try {
                 mIm.vibrate(mDeviceId, pattern, repeat, mToken);
             } catch (RemoteException ex) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d..e59c3ae 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
 
 package android.os;
 
+import android.os.VibrationEffect;
+
 /** {@hide} */
 interface IVibratorService
 {
     boolean hasVibrator();
-    void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
-    void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+    boolean hasAmplitudeControl();
+    void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
     void cancelVibrate(IBinder token);
 }
 
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f..b8bdc89 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@
         return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+    public boolean hasAmplitudeControl() {
+        return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes) {
-        if (repeat >= pattern.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
+    public void vibrate(int uid, String opPkg,
+            VibrationEffect effect, AudioAttributes attributes) {
     }
 
     @Override
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d6688e3..f69c996 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -140,6 +140,12 @@
     public static final int WEBVIEW_ZYGOTE_UID = 1051;
 
     /**
+     * Defines the UID used for resource tracking for OTA updates.
+     * @hide
+     */
+    public static final int OTA_UPDATE_UID = 1061;
+
+    /**
      * Defines the start of a range of UIDs (and GIDs), going from this
      * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
      * to applications.
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c488811..f776c17 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@
     private final Binder mToken = new Binder();
 
     public SystemVibrator() {
-        mService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
     public SystemVibrator(Context context) {
         super(context);
-        mService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
     @Override
@@ -55,47 +53,33 @@
         return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+    public boolean hasAmplitudeControl() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.hasAmplitudeControl();
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg,
+            VibrationEffect effect, AudioAttributes attributes) {
         if (mService == null) {
             Log.w(TAG, "Failed to vibrate; no vibrator service.");
             return;
         }
         try {
-            mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
     }
 
-    /**
-     * @hide
-     */
-    @Override
-    public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes) {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
-            return;
-        }
-        // catch this here because the server will do nothing.  pattern may
-        // not be null, let that be checked, because the server will drop it
-        // anyway
-        if (repeat < pattern.length) {
-            try {
-                mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
-                        mToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to vibrate.", e);
-            }
-        } else {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-    }
-
     private static int usageForAttributes(AudioAttributes attributes) {
         return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
     }
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
new file mode 100644
index 0000000..745642e
--- /dev/null
+++ b/core/java/android/os/TestLooperManager.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArraySet;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Blocks a looper from executing any messages, and allows the holder of this object
+ * to control when and which messages get executed until it is released.
+ * <p>
+ * A TestLooperManager should be acquired using
+ * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
+ * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
+ * The test code may use {@link #next()} to acquire messages that have been queued to this
+ * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
+ */
+public class TestLooperManager {
+
+    private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
+
+    private final MessageQueue mQueue;
+    private final Looper mLooper;
+    private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
+
+    private boolean mReleased;
+    private boolean mLooperBlocked;
+
+    /**
+     * @hide
+     */
+    public TestLooperManager(Looper looper) {
+        synchronized (sHeldLoopers) {
+            if (sHeldLoopers.contains(looper)) {
+                throw new RuntimeException("TestLooperManager already held for this looper");
+            }
+            sHeldLoopers.add(looper);
+        }
+        mLooper = looper;
+        mQueue = mLooper.getQueue();
+        // Post a message that will keep the looper blocked as long as we are dispatching.
+        new Handler(looper).post(new LooperHolder());
+    }
+
+    /**
+     * Returns the {@link MessageQueue} this object is wrapping.
+     */
+    public MessageQueue getQueue() {
+        checkReleased();
+        return mQueue;
+    }
+
+    /**
+     * Returns the next message that should be executed by this queue, may block
+     * if no messages are ready.
+     * <p>
+     * Callers should always call {@link #recycle(Message)} on the message when all
+     * interactions with it have completed.
+     */
+    public Message next() {
+        // Wait for the looper block to come up, to make sure we don't accidentally get
+        // the message for the block.
+        while (!mLooperBlocked) {
+            synchronized (this) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        checkReleased();
+        return mQueue.next();
+    }
+
+    /**
+     * Releases the looper to continue standard looping and processing of messages,
+     * no further interactions with TestLooperManager will be allowed after
+     * release() has been called.
+     */
+    public void release() {
+        synchronized (sHeldLoopers) {
+            sHeldLoopers.remove(mLooper);
+        }
+        checkReleased();
+        mReleased = true;
+        mExecuteQueue.add(new MessageExecution());
+    }
+
+    /**
+     * Executes the given message on the Looper thread this wrapper is
+     * attached to.
+     * <p>
+     * Execution will happen on the Looper's thread (whether it is the current thread
+     * or not), but all RuntimeExceptions encountered while executing the message will
+     * be thrown on the calling thread.
+     */
+    public void execute(Message message) {
+        checkReleased();
+        if (Looper.myLooper() == mLooper) {
+            // This is being called from the thread it should be executed on, we can just dispatch.
+            message.target.dispatchMessage(message);
+        } else {
+            MessageExecution execution = new MessageExecution();
+            execution.m = message;
+            synchronized (execution) {
+                mExecuteQueue.add(execution);
+                // Wait for the message to be executed.
+                try {
+                    execution.wait();
+                } catch (InterruptedException e) {
+                }
+                if (execution.response != null) {
+                    throw new RuntimeException(execution.response);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called to indicate that a Message returned by {@link #next()} has been parsed
+     * and should be recycled.
+     */
+    public void recycle(Message msg) {
+        checkReleased();
+        msg.recycleUnchecked();
+    }
+
+    /**
+     * Returns true if there are any queued messages that match the parameters.
+     *
+     * @param h      the value of {@link Message#getTarget()}
+     * @param what   the value of {@link Message#what}
+     * @param object the value of {@link Message#obj}, null for any
+     */
+    public boolean hasMessages(Handler h, Object object, int what) {
+        checkReleased();
+        return mQueue.hasMessages(h, what, object);
+    }
+
+    /**
+     * Returns true if there are any queued messages that match the parameters.
+     *
+     * @param h      the value of {@link Message#getTarget()}
+     * @param r      the value of {@link Message#getCallback()}
+     * @param object the value of {@link Message#obj}, null for any
+     */
+    public boolean hasMessages(Handler h, Object object, Runnable r) {
+        checkReleased();
+        return mQueue.hasMessages(h, r, object);
+    }
+
+    private void checkReleased() {
+        if (mReleased) {
+            throw new RuntimeException("release() has already be called");
+        }
+    }
+
+    private class LooperHolder implements Runnable {
+        @Override
+        public void run() {
+            synchronized (TestLooperManager.this) {
+                mLooperBlocked = true;
+                TestLooperManager.this.notify();
+            }
+            while (!mReleased) {
+                try {
+                    final MessageExecution take = mExecuteQueue.take();
+                    if (take.m != null) {
+                        processMessage(take);
+                    }
+                } catch (InterruptedException e) {
+                }
+            }
+            synchronized (TestLooperManager.this) {
+                mLooperBlocked = false;
+            }
+        }
+
+        private void processMessage(MessageExecution mex) {
+            synchronized (mex) {
+                try {
+                    mex.m.target.dispatchMessage(mex.m);
+                    mex.response = null;
+                } catch (Throwable t) {
+                    mex.response = t;
+                }
+                mex.notifyAll();
+            }
+        }
+    }
+
+    private static class MessageExecution {
+        private Message m;
+        private Throwable response;
+    }
+}
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 0000000..dcc79d7
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 0000000..eceaa31
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+    private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+    private static final int PARCEL_TOKEN_WAVEFORM = 2;
+    private static final int PARCEL_TOKEN_EFFECT = 3;
+
+    /**
+     * The default vibration strength of the device.
+     */
+    public static final int DEFAULT_AMPLITUDE = -1;
+
+    /**
+     * A click effect.
+     *
+     * @see #get(int)
+     * @hide
+     */
+    public static final int EFFECT_CLICK = Effect.CLICK;
+
+    /**
+     * A double click effect.
+     *
+     * @see #get(int)
+     * @hide
+     */
+    public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+    /** @hide to prevent subclassing from outside of the framework */
+    public VibrationEffect() { }
+
+    /**
+     * Create a one shot vibration.
+     *
+     * One shot vibrations will vibrate constantly for the specified period of time at the
+     * specified amplitude, and then stop.
+     *
+     * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+     * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+     * {@link #DEFAULT_AMPLITUDE}.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+        VibrationEffect effect = new OneShot(milliseconds, amplitude);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * <p>
+     * The amplitude array of the generated waveform will be the same size as the given
+     * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+     * starting with 0. Therefore the first timing value will be the period to wait before turning
+     * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+     * strength, etc.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+     *                of 0 will cause the timing / amplitude pair to be ignored.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int repeat) {
+        int[] amplitudes = new int[timings.length];
+        for (int i = 0; i < (timings.length / 2); i++) {
+            amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+        }
+        return createWaveform(timings, amplitudes, repeat);
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+     *                will cause the pair to be ignored.
+     * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+     *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+     *                   amplitude value of 0 implies the motor is off.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+        VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Get a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * @param effectId The ID of the effect to perform:
+     * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+     *
+     * @return The desired effect.
+     * @hide
+     */
+    public static VibrationEffect get(int effectId) {
+        VibrationEffect effect = new Prebaked(effectId);
+        effect.validate();
+        return effect;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    public abstract void validate();
+
+    /** @hide */
+    public static class OneShot extends VibrationEffect implements Parcelable {
+        private long mTiming;
+        private int mAmplitude;
+
+        public OneShot(Parcel in) {
+            this(in.readLong(), in.readInt());
+        }
+
+        public OneShot(long milliseconds, int amplitude) {
+            mTiming = milliseconds;
+            mAmplitude = amplitude;
+        }
+
+        public long getTiming() {
+            return mTiming;
+        }
+
+        public int getAmplitude() {
+            return mAmplitude;
+        }
+
+        @Override
+        public void validate() {
+            if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
+                throw new IllegalArgumentException(
+                        "amplitude must either be DEFAULT_AMPLITUDE, " +
+                        "or between 1 and 255 inclusive");
+            }
+            if (mTiming <= 0) {
+                throw new IllegalArgumentException("timing must be positive");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.OneShot)) {
+                return false;
+            }
+            VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+            return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 37 * (int) mTiming;
+            result = 37 * mAmplitude;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+            out.writeLong(mTiming);
+            out.writeInt(mAmplitude);
+        }
+
+        public static final Parcelable.Creator<OneShot> CREATOR =
+            new Parcelable.Creator<OneShot>() {
+                @Override
+                public OneShot createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new OneShot(in);
+                }
+                @Override
+                public OneShot[] newArray(int size) {
+                    return new OneShot[size];
+                }
+            };
+    }
+
+    /** @hide */
+    public static class Waveform extends VibrationEffect implements Parcelable {
+        private long[] mTimings;
+        private int[] mAmplitudes;
+        private int mRepeat;
+
+        public Waveform(Parcel in) {
+            this(in.createLongArray(), in.createIntArray(), in.readInt());
+        }
+
+        public Waveform(long[] timings, int[] amplitudes, int repeat) {
+            mTimings = new long[timings.length];
+            System.arraycopy(timings, 0, mTimings, 0, timings.length);
+            mAmplitudes = new int[amplitudes.length];
+            System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+            mRepeat = repeat;
+        }
+
+        public long[] getTimings() {
+            return mTimings;
+        }
+
+        public int[] getAmplitudes() {
+            return mAmplitudes;
+        }
+
+        public int getRepeatIndex() {
+            return mRepeat;
+        }
+
+        @Override
+        public void validate() {
+            if (mTimings.length != mAmplitudes.length) {
+                throw new IllegalArgumentException(
+                        "timing and amplitude arrays must be of equal length");
+            }
+            if (!hasNonZeroEntry(mTimings)) {
+                throw new IllegalArgumentException("at least one timing must be non-zero");
+            }
+            for (long timing : mTimings) {
+                if (timing < 0) {
+                    throw new IllegalArgumentException("timings must all be >= 0");
+                }
+            }
+            for (int amplitude : mAmplitudes) {
+                if (amplitude < -1 || amplitude > 255) {
+                    throw new IllegalArgumentException(
+                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+                }
+            }
+            if (mRepeat < -1 || mRepeat >= mTimings.length) {
+                throw new IllegalArgumentException("repeat index must be >= -1");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Waveform)) {
+                return false;
+            }
+            VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+            return Arrays.equals(mTimings, other.mTimings) &&
+                Arrays.equals(mAmplitudes, other.mAmplitudes) &&
+                mRepeat == other.mRepeat;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 37 * Arrays.hashCode(mTimings);
+            result = 37 * Arrays.hashCode(mAmplitudes);
+            result = 37 * mRepeat;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Waveform{mTimings=" + Arrays.toString(mTimings) +
+                ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
+                ", mRepeat=" + mRepeat +
+                "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_WAVEFORM);
+            out.writeLongArray(mTimings);
+            out.writeIntArray(mAmplitudes);
+            out.writeInt(mRepeat);
+        }
+
+        private static boolean hasNonZeroEntry(long[] vals) {
+            for (long val : vals) {
+                if (val != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
+        public static final Parcelable.Creator<Waveform> CREATOR =
+            new Parcelable.Creator<Waveform>() {
+                @Override
+                public Waveform createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Waveform(in);
+                }
+                @Override
+                public Waveform[] newArray(int size) {
+                    return new Waveform[size];
+                }
+            };
+    }
+
+    /** @hide */
+    public static class Prebaked extends VibrationEffect implements Parcelable {
+        private int mEffectId;
+
+        public Prebaked(Parcel in) {
+            this(in.readInt());
+        }
+
+        public Prebaked(int effectId) {
+            mEffectId = effectId;
+        }
+
+        public int getId() {
+            return mEffectId;
+        }
+
+        @Override
+        public void validate() {
+            if (mEffectId != EFFECT_CLICK) {
+                throw new IllegalArgumentException("Unknown prebaked effect type");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Prebaked)) {
+                return false;
+            }
+            VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+            return mEffectId == other.mEffectId;
+        }
+
+        @Override
+        public int hashCode() {
+            return mEffectId;
+        }
+
+        @Override
+        public String toString() {
+            return "Prebaked{mEffectId=" + mEffectId + "}";
+        }
+
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_EFFECT);
+            out.writeInt(mEffectId);
+        }
+
+        public static final Parcelable.Creator<Prebaked> CREATOR =
+            new Parcelable.Creator<Prebaked>() {
+                @Override
+                public Prebaked createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Prebaked(in);
+                }
+                @Override
+                public Prebaked[] newArray(int size) {
+                    return new Prebaked[size];
+                }
+            };
+    }
+
+    public static final Parcelable.Creator<VibrationEffect> CREATOR =
+            new Parcelable.Creator<VibrationEffect>() {
+                @Override
+                public VibrationEffect createFromParcel(Parcel in) {
+                    int token = in.readInt();
+                    if (token == PARCEL_TOKEN_ONE_SHOT) {
+                        return new OneShot(in);
+                    } else if (token == PARCEL_TOKEN_WAVEFORM) {
+                        return new Waveform(in);
+                    } else if (token == PARCEL_TOKEN_EFFECT) {
+                        return new Prebaked(in);
+                    } else {
+                        throw new IllegalStateException(
+                                "Unexpected vibration event type token in parcel.");
+                    }
+                }
+                @Override
+                public VibrationEffect[] newArray(int size) {
+                    return new VibrationEffect[size];
+                }
+            };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666..b1f6421 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@
     public abstract boolean hasVibrator();
 
     /**
+     * Check whether the vibrator has amplitude control.
+     *
+     * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+     */
+    public abstract boolean hasAmplitudeControl();
+
+    /**
      * Vibrate constantly for the specified period of time.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#VIBRATE}.
      *
      * @param milliseconds The number of milliseconds to vibrate.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
      */
+    @Deprecated
     public void vibrate(long milliseconds) {
         vibrate(milliseconds, null);
     }
@@ -75,9 +85,14 @@
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
      *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
      *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
      */
+    @Deprecated
     public void vibrate(long milliseconds, AudioAttributes attributes) {
-        vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+        VibrationEffect effect =
+                VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+        vibrate(effect, attributes);
     }
 
     /**
@@ -99,7 +114,10 @@
      * @param pattern an array of longs of times for which to turn the vibrator on or off.
      * @param repeat the index into pattern at which to repeat, or -1 if
      *        you don't want to repeat.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
      */
+    @Deprecated
     public void vibrate(long[] pattern, int repeat) {
         vibrate(pattern, repeat, null);
     }
@@ -127,26 +145,34 @@
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
      *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
      *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
      */
+    @Deprecated
     public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
-        vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+        // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+        // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+        if (repeat < -1 || repeat >= pattern.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
+    }
+
+    public void vibrate(VibrationEffect vibe) {
+        vibrate(vibe, null);
+    }
+
+    public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, vibe, attributes);
     }
 
     /**
+     * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+     * that the vibration is owned by someone else.
      * @hide
-     * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
-     * the vibration is owned by someone else.
      */
-    public abstract void vibrate(int uid, String opPkg, long milliseconds,
-            AudioAttributes attributes);
-
-    /**
-     * @hide
-     * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
-     * the vibration is owned by someone else.
-     */
-    public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes);
+    public abstract void vibrate(int uid, String opPkg,
+            VibrationEffect vibe, AudioAttributes attributes);
 
     /**
      * Turn the vibrator off.
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index d9c749a..4d14277 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -82,6 +82,7 @@
  * @attr ref android.R.styleable#Preference_defaultValue
  * @attr ref android.R.styleable#Preference_shouldDisableView
  * @attr ref android.R.styleable#Preference_recycleEnabled
+ * @attr ref android.R.styleable#Preference_singleLineTitle
  */
 public class Preference implements Comparable<Preference> {
     /**
@@ -133,6 +134,7 @@
     private boolean mDependencyMet = true;
     private boolean mParentDependencyMet = true;
     private boolean mRecycleEnabled = true;
+    private boolean mSingleLineTitle = true;
 
     /**
      * @see #setShouldDisableView(boolean)
@@ -296,6 +298,10 @@
                 case com.android.internal.R.styleable.Preference_recycleEnabled:
                     mRecycleEnabled = a.getBoolean(attr, mRecycleEnabled);
                     break;
+
+                case com.android.internal.R.styleable.Preference_singleLineTitle:
+                    mSingleLineTitle = a.getBoolean(attr, mSingleLineTitle);
+                    break;
             }
         }
         a.recycle();
@@ -597,6 +603,7 @@
             if (!TextUtils.isEmpty(title)) {
                 titleView.setText(title);
                 titleView.setVisibility(View.VISIBLE);
+                titleView.setSingleLine(mSingleLineTitle);
             } else {
                 titleView.setVisibility(View.GONE);
             }
@@ -903,6 +910,27 @@
     }
 
     /**
+     * Sets whether to constrain the title of this Preference to a single line instead of
+     * letting it wrap onto multiple lines.
+     *
+     * @param singleLineTitle set {@code true} if the title should be constrained to one line
+     */
+    public void setSingleLineTitle(boolean singleLineTitle) {
+        mSingleLineTitle = singleLineTitle;
+        notifyChanged();
+    }
+
+    /**
+     * Gets whether the title of this preference is constrained to a single line.
+     *
+     * @see #setSingleLineTitle(boolean)
+     * @return {@code true} if the title of this preference is constrained to a single line
+     */
+    public boolean isSingleLineTitle() {
+        return mSingleLineTitle;
+    }
+
+    /**
      * Returns a unique ID for this Preference.  This ID should be unique across all
      * Preference objects in a hierarchy.
      *
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index dac8354..b4f19d8 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8983,6 +8983,11 @@
          * value of this extra is a {@code String} and should be the value of {@link
          * android.accounts.Account#hashCode()} for some account returned by {@link
          * android.accounts.AccountManager#getAccounts()}.
+         * <p>
+         * If the extra is not specified, the app can decide which account to use.
+         * <p>
+         * If the account specified in the extra cannot be used for any reason (account missing, not
+         * usable by the app, etc), the message should not be sent.
          */
         public static final String EXTRA_SENDER_ACCOUNT_HASH =
                 "android.provider.extra.SENDER_ACCOUNT_HASH";
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index c4684e7..93adf83 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -534,6 +534,14 @@
         }
 
         /**
+         * Used to trigger special logic for directories.
+         * @hide
+         */
+        public static final Uri getDirectoryUri(String volumeName) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + "/dir");
+        }
+
+        /**
          * Fields for master table for all media files.
          * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
          */
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index ebe02c2..47e7803 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -49,12 +49,14 @@
 
     private final ArrayList<AutofillId> mFieldIds;
     private final ArrayList<AutofillValue> mFieldValues;
+    private final ArrayList<RemoteViews> mFieldPresentations;
     private final RemoteViews mPresentation;
     private final IntentSender mAuthentication;
 
     private Dataset(Builder builder) {
         mFieldIds = builder.mFieldIds;
         mFieldValues = builder.mFieldValues;
+        mFieldPresentations = builder.mFieldPresentations;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
     }
@@ -70,6 +72,12 @@
     }
 
     /** @hide */
+    public RemoteViews getFieldPresentation(int index) {
+        final RemoteViews customPresentation = mFieldPresentations.get(index);
+        return customPresentation != null ? customPresentation : mPresentation;
+    }
+
+    /** @hide */
     public @Nullable RemoteViews getPresentation() {
         return mPresentation;
     }
@@ -91,6 +99,8 @@
         return new StringBuilder("Dataset [")
                 .append(", fieldIds=").append(mFieldIds)
                 .append(", fieldValues=").append(mFieldValues)
+                .append(", fieldPresentations=")
+                .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
                 .append(", hasPresentation=").append(mPresentation != null)
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(']').toString();
@@ -103,6 +113,7 @@
     public static final class Builder {
         private ArrayList<AutofillId> mFieldIds;
         private ArrayList<AutofillValue> mFieldValues;
+        private ArrayList<RemoteViews> mFieldPresentations;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private boolean mDestroyed;
@@ -118,6 +129,15 @@
         }
 
         /**
+         * Creates a new builder for a dataset where each field will be visualized independently.
+         *
+         * <p>When using this constructor, fields must be set through
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+         */
+        public Builder() {
+        }
+
+        /**
          * Requires a dataset authentication before autofilling the activity with this dataset.
          *
          * <p>This method is called when you need to provide an authentication
@@ -175,24 +195,54 @@
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
          * @param value value to be auto filled.
          * @return This builder.
+         * @throws IllegalStateException if the builder was constructed without a presentation
+         * ({@link RemoteViews}).
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @NonNull AutofillValue value) {
             throwIfDestroyed();
+            if (mPresentation == null) {
+                throw new IllegalStateException("Dataset presentation not set on constructor");
+            }
+            setValueAndPresentation(id, value, null);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, usin a custom presentation to visualize it.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value value to be auto filled.
+         * @param presentation The presentation used to visualize this field.
+         * @return This builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @NonNull AutofillValue value,
+                @NonNull RemoteViews presentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            setValueAndPresentation(id, value, presentation);
+            return this;
+        }
+
+        private void setValueAndPresentation(AutofillId id, AutofillValue value,
+                RemoteViews presentation) {
             Preconditions.checkNotNull(id, "id cannot be null");
             Preconditions.checkNotNull(value, "value cannot be null");
             if (mFieldIds != null) {
                 final int existingIdx = mFieldIds.indexOf(id);
                 if (existingIdx >= 0) {
                     mFieldValues.set(existingIdx, value);
-                    return this;
+                    mFieldPresentations.set(existingIdx, presentation);
+                    return;
                 }
             } else {
                 mFieldIds = new ArrayList<>();
                 mFieldValues = new ArrayList<>();
+                mFieldPresentations = new ArrayList<>();
             }
             mFieldIds.add(id);
             mFieldValues.add(value);
-            return this;
+            mFieldPresentations.add(presentation);
         }
 
         /**
@@ -234,6 +284,7 @@
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeTypedArrayList(mFieldIds, flags);
         parcel.writeTypedArrayList(mFieldValues, flags);
+        parcel.writeParcelableList(mFieldPresentations, flags);
         parcel.writeParcelable(mAuthentication, flags);
     }
 
@@ -243,15 +294,22 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final Builder builder = new Builder(parcel.readParcelable(null));
+            final RemoteViews presentation = parcel.readParcelable(null);
+            final Builder builder = (presentation == null)
+                    ? new Builder()
+                    : new Builder(presentation);
             final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
             final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+            final ArrayList<RemoteViews> presentations = new ArrayList<>();
+            parcel.readParcelableList(presentations, null);
             final int idCount = (ids != null) ? ids.size() : 0;
             final int valueCount = (values != null) ? values.size() : 0;
             for (int i = 0; i < idCount; i++) {
                 final AutofillId id = ids.get(i);
                 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
-                builder.setValue(id, value);
+                final RemoteViews fieldPresentation = presentations.isEmpty() ? null
+                        : presentations.get(i);
+                builder.setValueAndPresentation(id, value, fieldPresentation);
             }
             builder.setAuthentication(parcel.readParcelable(null));
             return builder.build();
diff --git a/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl b/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl
new file mode 100644
index 0000000..7de8b63
--- /dev/null
+++ b/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.vr;
+
+/** @hide */
+oneway interface IPersistentVrStateCallbacks {
+
+    void onPersistentVrStateChanged(in boolean enabled);
+
+}
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index 6034c18..fce06d6d 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -17,6 +17,7 @@
 package android.service.vr;
 
 import android.service.vr.IVrStateCallbacks;
+import android.service.vr.IPersistentVrStateCallbacks;
 
 /** @hide */
 interface IVrManager {
@@ -36,6 +37,20 @@
     void unregisterListener(in IVrStateCallbacks cb);
 
     /**
+     * Add a callback to be notified when persistent VR mode state changes.
+     *
+     * @param cb the callback instance to add.
+     */
+    void registerPersistentVrStateListener(in IPersistentVrStateCallbacks cb);
+
+    /**
+     * Remove the callack from the current set of registered callbacks.
+     *
+     * @param cb the callback to remove.
+     */
+    void unregisterPersistentVrStateListener(in IPersistentVrStateCallbacks cb);
+
+    /**
      * Return current VR mode state.
      *
      * @return {@code true} if VR mode is enabled.
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 04596fa..9c15e00 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.FontListParser;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -106,8 +107,17 @@
         private final float mStyleValue;
 
         public Axis(int tag, float styleValue) {
-            this.mTag = tag;
-            this.mStyleValue = styleValue;
+            mTag = tag;
+            mStyleValue = styleValue;
+        }
+
+        /** @hide */
+        public Axis(@NonNull String tagString, float styleValue) {
+            if (!FontListParser.isValidTag(tagString)) {
+                throw new IllegalArgumentException("Invalid tag pattern: " + tagString);
+            }
+            mTag = FontListParser.makeTag(tagString);
+            mStyleValue = styleValue;
         }
 
         /**
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 92c70bd..6d4281b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -201,6 +201,29 @@
         public void clearError() {
             mLastWtf = null;
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            // Not using ByteBuffer.equals since it takes buffer position into account and we
+            // always use absolute positions here.
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Event other = (Event) o;
+            return Arrays.equals(mBuffer.array(), other.mBuffer.array());
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            // Not using ByteBuffer.hashCode since it takes buffer position into account and we
+            // always use absolute positions here.
+            return Arrays.hashCode(mBuffer.array());
+        }
     }
 
     // We assume that the native methods deal with any concurrency issues.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5494377..6dedbde 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -50,7 +50,7 @@
  * <li>The real display area specifies the part of the display that contains content
  * including the system decorations.  Even so, the real display area may be smaller than the
  * physical size of the display if the window manager is emulating a smaller display
- * using (adb shell am display-size).  Use the following methods to query the
+ * using (adb shell wm size).  Use the following methods to query the
  * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li>
  * </ul>
  * </p><p>
@@ -947,7 +947,7 @@
      * The size is adjusted based on the current rotation of the display.
      * </p><p>
      * The real size may be smaller than the physical size of the screen when the
-     * window manager is emulating a smaller display (using adb shell am display-size).
+     * window manager is emulating a smaller display (using adb shell wm size).
      * </p>
      *
      * @param outSize Set to the real size of the display.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 350675f..5269296 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7275,11 +7275,16 @@
      * fills in all data that can be inferred from the view itself.
      * @param flags optional flags (currently {@code 0}).
      */
-    @CallSuper
     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
         onProvideStructureForAssistOrAutofill(structure, true);
     }
 
+    private void setAutofillId(ViewStructure structure) {
+        // The autofill id needs to be unique, but its value doesn't matter,
+        // so it's better to reuse the accessibility id to save space.
+        structure.setAutofillId(getAccessibilityViewId());
+    }
+
     private void onProvideStructureForAssistOrAutofill(ViewStructure structure,
             boolean forAutofill) {
         final int id = mID;
@@ -7299,13 +7304,11 @@
         }
 
         if (forAutofill) {
+            setAutofillId(structure);
             final @AutofillType int autofillType = getAutofillType();
             // Don't need to fill autofill info if view does not support it.
             // For example, only TextViews that are editable support autofill
             if (autofillType != AUTOFILL_TYPE_NONE) {
-                // The autofill id needs to be unique, but its value doesn't matter, so it's better
-                // to reuse the accessibility id to save space.
-                structure.setAutofillId(getAccessibilityViewId());
                 structure.setAutofillType(autofillType);
                 structure.setAutofillHint(getAutofillHint());
                 structure.setAutofillValue(getAutofillValue());
@@ -7404,6 +7407,9 @@
 
     private void onProvideVirtualStructureForAssistOrAutofill(ViewStructure structure,
             boolean forAutofill) {
+        if (forAutofill) {
+            setAutofillId(structure);
+        }
         // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
         // this method should take a boolean with the type of request.
         AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
@@ -7671,6 +7677,9 @@
                 AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 ViewStructure child = structure.newChild(i);
+                // TODO(b/33197203): add CTS test to autofill virtual children based on
+                // Accessibility API.
+                child.setAutofillId(structure, i);
                 populateVirtualStructure(child, provider, cinfo, forAutofill);
                 cinfo.recycle();
             }
@@ -7707,9 +7716,7 @@
         boolean blocked = forAutofill ? isAutofillBlocked() : isAssistBlocked();
         if (!blocked) {
             if (forAutofill) {
-                // The autofill id needs to be unique, but its value doesn't matter,
-                // so it's better to reuse the accessibility id to save space.
-                structure.setAutofillId(getAccessibilityViewId());
+                setAutofillId(structure);
                 // NOTE: flags are not currently supported, hence 0
                 onProvideAutofillStructure(structure, 0);
                 onProvideAutofillVirtualStructure(structure, 0);
@@ -17892,7 +17899,8 @@
             // This case should hopefully never or seldom happen
             canvas = new Canvas(bitmap);
         }
-
+        boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled();
+        canvas.setHwBitmapsInSwModeEnabled(true);
         if ((backgroundColor & 0xff000000) != 0) {
             bitmap.eraseColor(backgroundColor);
         }
@@ -17920,6 +17928,7 @@
 
         canvas.restoreToCount(restoreCount);
         canvas.setBitmap(null);
+        canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode);
 
         if (attachInfo != null) {
             // Restore the cached Canvas for our siblings
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 989cb13d..0c669f3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -41,6 +42,17 @@
     public abstract void setId(int id, String packageName, String typeName, String entryName);
 
     /**
+     * Sets the name of the identifier for this view.
+     *
+     * <p>Typically used when adding virtual children (through
+     * {@link #asyncNewChild(int)}) that does not map to Android {@link View}
+     * - otherwise, it's better to call {@link #setId(int, String, String, String)}.
+     *
+     * @param entryName The entry name of the view's identifier, or {@code null} if there is none.
+     */
+    public abstract void setIdEntry(String entryName);
+
+    /**
      * Set the basic dimensions of this view.
      *
      * @param left The view's left position, in pixels relative to its parent's left edge.
@@ -269,20 +281,6 @@
     public abstract ViewStructure newChild(int index);
 
     /**
-     * Create a new child {@link ViewStructure} in this view for autofill purposes.
-     *
-     * @param index the index (in the list of children) to put the new child at (see
-     *            {@link #addChildCount(int)} and {@link #setChildCount(int)}.
-     * @param virtualId an opaque ID to the Android System (although it could be meaningful to the
-     *            {@link View} creating the {@link ViewStructure}), but it's the same id used on
-     *            {@link View#autofill(android.util.SparseArray)}.
-     * @param flags currently {@code 0}.
-     *
-     * @return Returns an fresh {@link ViewStructure} ready to be filled in.
-     */
-    public abstract ViewStructure newChild(int index, int virtualId, int flags);
-
-    /**
      * Like {@link #newChild}, but allows the caller to asynchronously populate the returned
      * child.  It can transfer the returned {@link ViewStructure} to another thread for it
      * to build its content (and children etc).  Once done, some thread must call
@@ -293,25 +291,13 @@
     public abstract ViewStructure asyncNewChild(int index);
 
     /**
-     * Like {@link #newChild(int, int, int)}, but allows the caller to asynchronously
-     * populate the returned child.
+     * Sets the {@link AutofillId} for this virtual node.
      *
-     * <p>It can transfer the returned {@link ViewStructure} to another thread for it to build its
-     * content (and children etc).
-     *
-     * <p>Once done, some thread must call {@link #asyncCommit()} to tell the containing
-     * {@link ViewStructure} that the async population is done.
-     *
-     * @param index the index (in the list of children) to put the new child at (see
-     *            {@link #addChildCount(int)} and {@link #setChildCount(int)}.
-     * @param virtualId an opaque ID to the Android System (although it could be meaningful to the
-     *            {@link View} creating the {@link ViewStructure}), but it's the same id used on
+     * @param parent parent node.
+     * @param virtualId an opaque ID to the Android System; it's the same id used on
      *            {@link View#autofill(android.util.SparseArray)}.
-     * @param flags currently {@code 0}.
-     *
-     * @return Returns an fresh {@link ViewStructure} ready to be filled in.
      */
-    public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
+    public abstract void setAutofillId(@NonNull ViewStructure parent, int virtualId);
 
     /**
      * Sets the {@link View#getAutofillType()} that can be used to autofill this node.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9a931c2..c2b4138 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -41,6 +41,7 @@
 import android.os.RemoteException;
 import android.print.PrintDocumentAdapter;
 import android.security.KeyChain;
+import android.text.InputType;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.DragEvent;
@@ -2611,6 +2612,76 @@
         mProvider.getViewDelegate().onProvideVirtualStructure(structure);
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
+     * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
+     * understood by the {@link android.service.autofill.AutofillService} implementations:
+     *
+     * <ol>
+     *   <li>{@link ViewStructure#setClassName(String)} should be use to describe the type of node:
+     *   <ol>
+     *       <li>If the Android SDK provides a similar View, the full-qualified class name of that
+     *           view should be used.
+     *       <li>Otherwise, the class name should be {@code HTML.iframe}.
+     *   </ol>
+     *   <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+     *       {@link ViewStructure#setAutofillHint(String[])}.
+     *   <li>The {@code type} attribute of {@code INPUT} tags maps to
+     *       {@link ViewStructure#setInputType(int)}.
+     *   <li>The {@code name} attribute maps to {@link ViewStructure#setIdEntry(String)}.
+     *   <li>The {@code value} attribute maps to {@link ViewStructure#setText(CharSequence)}.
+     *   <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
+     *   <li>{@link ViewStructure#setDataIsSensitive(boolean)} whould only be called with
+     *       {@code true} for form fields whose {@code value} attribute was not pre-loaded.
+     * </ol>
+     *
+     * <p>Example1: an HTML form with 2 fields for username and password.
+     *
+     * <pre class="prettyprint">
+     *    <input type="text" name="username" value="mr.sparkle" autocomplete="username" placeholder="Email or username">
+     *    <input type="password" name="password" autocomplete="current-password" placeholder="Password">
+     * </pre>
+     *
+     * <p>Would map to:
+     *
+     * <pre class="prettyprint">
+     *     ViewStructure username = //structure.newChildForAutofill(...);
+     *     username.setClassName("input");
+     *     username.setInputType("android.widget.EditText");
+     *     username.setAutofillHints("username");
+     *     username.setIdEntry("username");
+     *     username.setHint("Email or username");
+     *     username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+     *     username.setAutofillValue(AutofillValue.forText("mr.sparkle"));
+     *     username.setText("mr.sparkle");
+     *     username.setDataIsSensitive(true); // Contains real username, which is sensitive
+     *
+     *     ViewStructure password = //structure.newChildForAutofill(...);
+     *     password.setInputType("android.widget.EditText");
+     *     password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+     *     password.setAutofillHints("current-password");
+     *     password.setIdEntry("password");
+     *     password.setHint("Password");
+     *     password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+     *     password.setDataIsSensitive(false); // Value is not set
+     * </pre>
+     *
+     * <p>Example2: an IFRAME tag.
+     *
+     * <pre class="prettyprint">
+     *    <iframe src="http://example.com/login"/>
+     * </pre>
+     *
+     * <p>Would map to:
+     *
+     * <pre class="prettyprint">
+     *     ViewStructure iframe = //structure.newChildForAutofill(...);
+     *     iframe.setClassName("HTML.iframe");
+     *     iframe.setUrl("http://example.com/login");
+     * </pre>
+     */
     @Override
     public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
         mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0906d1a..81c2f5d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -198,7 +198,9 @@
             if (sProviderInstance != null) return sProviderInstance;
 
             final int uid = android.os.Process.myUid();
-            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
+                    || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
+                    || uid == android.os.Process.BLUETOOTH_UID) {
                 throw new UnsupportedOperationException(
                         "For security reasons, WebView is not allowed in privileged processes");
             }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 59881b5..a6a9db4 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -132,7 +132,7 @@
 
     private CharSequence mDescFormat;
 
-    private boolean mAttached;
+    private boolean mRegistered;
 
     private Calendar mTime;
     private String mTimeZone;
@@ -252,7 +252,7 @@
         }
 
         createTime(mTimeZone);
-        // Wait until onAttachedToWindow() to handle the ticker
+        // Wait until registering for events to handle the ticker
         chooseFormat(false);
     }
 
@@ -503,12 +503,9 @@
         boolean hadSeconds = mHasSeconds;
         mHasSeconds = DateFormat.hasSeconds(mFormat);
 
-        if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
-            if (hadSeconds) {
-                getHandler().removeCallbacks(mTicker);
-            } else if (getVisibility() == VISIBLE) {
-                mTicker.run();
-            }
+        if (handleTicker && mRegistered && hadSeconds != mHasSeconds) {
+            if (hadSeconds) getHandler().removeCallbacks(mTicker);
+            else mTicker.run();
         }
     }
 
@@ -520,50 +517,27 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        if (!mAttached) {
-            mAttached = true;
+    public void onVisibilityAggregated(boolean isVisible) {
+        if (!mRegistered && isVisible) {
+            mRegistered = true;
 
             registerReceiver();
             registerObserver();
 
             createTime(mTimeZone);
 
-            if (getVisibility() == VISIBLE) {
-                if (mHasSeconds) {
-                    mTicker.run();
-                } else {
-                    onTimeChanged();
-                }
+            if (mHasSeconds) {
+                mTicker.run();
+            } else {
+                onTimeChanged();
             }
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        if (mAttached) {
+        } else if (mRegistered && !isVisible) {
             unregisterReceiver();
             unregisterObserver();
 
             getHandler().removeCallbacks(mTicker);
 
-            mAttached = false;
-        }
-    }
-
-    @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        if (mAttached) {
-            if (isVisible && mHasSeconds) {
-                mTicker.run();
-            } else {
-                getHandler().removeCallbacks(mTicker);
-            }
-            onTimeChanged();
+            mRegistered = false;
         }
     }
 
@@ -586,7 +560,7 @@
     }
 
     private void registerObserver() {
-        if (mAttached) {
+        if (mRegistered) {
             if (mFormatChangeObserver == null) {
                 mFormatChangeObserver = new FormatChangeObserver(getHandler());
             }
@@ -613,11 +587,9 @@
     }
 
     private void onTimeChanged() {
-        if (getVisibility() == VISIBLE) {
-            mTime.setTimeInMillis(System.currentTimeMillis());
-            setText(DateFormat.format(mFormat, mTime));
-            setContentDescription(DateFormat.format(mDescFormat, mTime));
-        }
+        mTime.setTimeInMillis(System.currentTimeMillis());
+        setText(DateFormat.format(mFormat, mTime));
+        setContentDescription(DateFormat.format(mDescFormat, mTime));
     }
 
     /** @hide */
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 83cc9f0..6e9e350 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -129,21 +129,35 @@
         }
 
         final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
+        final String childId;
         if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             if (!file.mkdir()) {
                 throw new IllegalStateException("Failed to mkdir " + file);
             }
+            childId = getDocIdForFile(file);
+            addFolderToMediaStore(getFileForDocId(childId, true));
         } else {
             try {
                 if (!file.createNewFile()) {
                     throw new IllegalStateException("Failed to touch " + file);
                 }
+                childId = getDocIdForFile(file);
             } catch (IOException e) {
                 throw new IllegalStateException("Failed to touch " + file + ": " + e);
             }
         }
 
-        return getDocIdForFile(file);
+        return childId;
+    }
+
+    private void addFolderToMediaStore(File visibleFolder) {
+        assert(visibleFolder.isDirectory());
+
+        final ContentResolver resolver = getContext().getContentResolver();
+        final Uri uri = MediaStore.Files.getDirectoryUri("external");
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
+        resolver.insert(uri, values);
     }
 
     @Override
@@ -193,7 +207,9 @@
     private void moveInMediaStore(File oldVisibleFile, File newVisibleFile) {
         if (newVisibleFile != null) {
             final ContentResolver resolver = getContext().getContentResolver();
-            final Uri externalUri = MediaStore.Files.getContentUri("external");
+            final Uri externalUri = newVisibleFile.isDirectory()
+                    ? MediaStore.Files.getDirectoryUri("external")
+                    : MediaStore.Files.getContentUri("external");
 
             ContentValues values = new ContentValues();
             values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 0a9faa1..b245678 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -18,6 +18,11 @@
 
 import android.os.Process;
 import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructCapUserData;
+import android.system.StructCapUserHeader;
 import android.util.BootTimingsTraceLog;
 import android.util.Slog;
 import com.android.internal.os.Zygote.MethodAndArgsCaller;
@@ -122,6 +127,7 @@
         command.append(' ');
         command.append(targetSdkVersion);
         Zygote.appendQuotedShellArgs(command, args);
+        preserveCapabilities();
         Zygote.execShell(command.toString());
     }
 
@@ -159,4 +165,57 @@
 
         RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
     }
+
+    /**
+     * Copy current capabilities to ambient capabilities. This is required for apps using
+     * capabilities, as execv will re-evaluate the capability set, and the set of sh is
+     * empty. Ambient capabilities have to be set to inherit them effectively.
+     *
+     * Note: This is BEST EFFORT ONLY. In case capabilities can't be raised, this function
+     *       will silently return. In THIS CASE ONLY, as this is a development feature, it
+     *       is better to return and try to run anyways, instead of blocking the wrapped app.
+     *       This is acceptable here as failure will leave the wrapped app with strictly less
+     *       capabilities, which may make it crash, but not exceed its allowances.
+     */
+    private static void preserveCapabilities() {
+        StructCapUserHeader header = new StructCapUserHeader(
+                OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
+        StructCapUserData[] data;
+        try {
+            data = Os.capget(header);
+        } catch (ErrnoException e) {
+            Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capget", e);
+            return;
+        }
+
+        if (data[0].permitted != data[0].inheritable ||
+                data[1].permitted != data[1].inheritable) {
+            data[0] = new StructCapUserData(data[0].effective, data[0].permitted,
+                    data[0].permitted);
+            data[1] = new StructCapUserData(data[1].effective, data[1].permitted,
+                    data[1].permitted);
+            try {
+                Os.capset(header, data);
+            } catch (ErrnoException e) {
+                Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capset", e);
+                return;
+            }
+        }
+
+        for (int i = 0; i < 64; i++) {
+            int dataIndex = OsConstants.CAP_TO_INDEX(i);
+            int capMask = OsConstants.CAP_TO_MASK(i);
+            if ((data[dataIndex].inheritable & capMask) != 0) {
+                try {
+                    Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE, i, 0,
+                            0);
+                } catch (ErrnoException ex) {
+                    // Only log here. Try to run the wrapped application even without this
+                    // ambient capability. It may crash after fork, but at least we'll try.
+                    Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed to raise ambient capability "
+                            + i, ex);
+                }
+            }
+        }
+    }
 }
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index fb7c5c4..4e68602 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -34,12 +34,16 @@
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
+#include <utils/FatVector.h>
 #include <minikin/FontFamily.h>
 
 #include <memory>
 
 namespace android {
 
+// Must be same with Java constant in Typeface.Builder. See Typeface.java
+constexpr jint RESOLVE_BY_FONT_TABLE = -1;
+
 struct NativeFamilyBuilder {
     uint32_t langId;
     int variant;
@@ -81,24 +85,53 @@
     delete family;
 }
 
-static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData,
-        size_t fontSize, int ttcIndex, jint givenWeight, jboolean givenItalic) {
+static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
+        jint givenWeight, jint givenItalic) {
+    uirenderer::FatVector<SkFontMgr::FontParameters::Axis, 2> skiaAxes;
+    for (const auto& axis : builder->axes) {
+        skiaAxes.emplace_back(SkFontMgr::FontParameters::Axis{axis.axisTag, axis.value});
+    }
+
+    const size_t fontSize = data->size();
+    const void* fontPtr = data->data();
+    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
+
+    SkFontMgr::FontParameters params;
+    params.setCollectionIndex(ttcIndex);
+    params.setAxes(skiaAxes.data(), skiaAxes.size());
+
+    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
+    if (face == NULL) {
+        ALOGE("addFont failed to create font, invalid request");
+        builder->axes.clear();
+        return false;
+    }
     std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontData, fontSize, ttcIndex,
-                    std::vector<minikin::FontVariation>());
-    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
+                    builder->axes);
+
     int weight = givenWeight / 100;
-    bool italic = givenItalic;
-    if (weight == 0) {
-        if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) {
+    bool italic = givenItalic == 1;
+    if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
+        int os2Weight;
+        bool os2Italic;
+        if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
             ALOGE("analyzeStyle failed. Using default style");
-            weight = 4;
-            italic = false;
+            os2Weight = 4;
+            os2Italic = false;
+        }
+        if (givenWeight == RESOLVE_BY_FONT_TABLE) {
+            weight = os2Weight;
+        }
+        if (givenItalic == RESOLVE_BY_FONT_TABLE) {
+            italic = os2Italic;
         }
     }
 
-    builder->fonts.push_back(minikin::Font(
-            std::move(minikinFont), minikin::FontStyle(weight, italic)));
+    builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight, italic)));
+    builder->axes.clear();
+    return true;
 }
 
 static void release_global_ref(const void* /*data*/, void* context) {
@@ -125,80 +158,47 @@
 }
 
 static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf,
-        jint ttcIndex) {
+        jint ttcIndex, jint weight, jint isItalic) {
     NPE_CHECK_RETURN_ZERO(env, bytebuf);
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
     const void* fontPtr = env->GetDirectBufferAddress(bytebuf);
     if (fontPtr == NULL) {
         ALOGE("addFont failed to create font, buffer invalid");
+        builder->axes.clear();
         return false;
     }
     jlong fontSize = env->GetDirectBufferCapacity(bytebuf);
     if (fontSize < 0) {
         ALOGE("addFont failed to create font, buffer size invalid");
+        builder->axes.clear();
         return false;
     }
     jobject fontRef = MakeGlobalRefOrDie(env, bytebuf);
     sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    SkFontMgr::FontParameters params;
-    params.setCollectionIndex(ttcIndex);
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
-    if (face == NULL) {
-        ALOGE("addFont failed to create font");
-        return false;
-    }
-    addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex, 0, false);
-    return true;
+    return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
 }
 
 static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr,
-        jobject font, jint ttcIndex, jint weight, jboolean isItalic) {
+        jobject font, jint ttcIndex, jint weight, jint isItalic) {
     NPE_CHECK_RETURN_ZERO(env, font);
-
     NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
-
-    // Declare axis native type.
-    std::vector<SkFontMgr::FontParameters::Axis> skiaAxes;
-    skiaAxes.reserve(builder->axes.size());
-    for (const minikin::FontVariation& minikinAxis : builder->axes) {
-        skiaAxes.push_back({minikinAxis.axisTag, minikinAxis.value});
-    }
-
     const void* fontPtr = env->GetDirectBufferAddress(font);
     if (fontPtr == NULL) {
         ALOGE("addFont failed to create font, buffer invalid");
+        builder->axes.clear();
         return false;
     }
     jlong fontSize = env->GetDirectBufferCapacity(font);
     if (fontSize < 0) {
         ALOGE("addFont failed to create font, buffer size invalid");
+        builder->axes.clear();
         return false;
     }
     jobject fontRef = MakeGlobalRefOrDie(env, font);
     sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    SkFontMgr::FontParameters params;
-    params.setCollectionIndex(ttcIndex);
-    params.setAxes(skiaAxes.data(), skiaAxes.size());
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
-    if (face == NULL) {
-        ALOGE("addFont failed to create font, invalid request");
-        return false;
-    }
-    std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
-                    std::vector<minikin::FontVariation>());
-    builder->fonts.push_back(minikin::Font(std::move(minikinFont),
-            minikin::FontStyle(weight / 100, isItalic)));
-    return true;
+    return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
 }
 
 static void releaseAsset(const void* ptr, void* context) {
@@ -206,18 +206,21 @@
 }
 
 static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr,
-        jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint weight,
-        jboolean isItalic) {
+        jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint ttcIndex,
+        jint weight, jint isItalic) {
     NPE_CHECK_RETURN_ZERO(env, jassetMgr);
     NPE_CHECK_RETURN_ZERO(env, jpath);
 
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
     AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
     if (NULL == mgr) {
+        builder->axes.clear();
         return false;
     }
 
     ScopedUtfChars str(env, jpath);
     if (str.c_str() == nullptr) {
+        builder->axes.clear();
         return false;
     }
 
@@ -230,27 +233,19 @@
     }
 
     if (NULL == asset) {
+        builder->axes.clear();
         return false;
     }
 
     const void* buf = asset->getBuffer(false);
     if (NULL == buf) {
         delete asset;
+        builder->axes.clear();
         return false;
     }
 
-    size_t bufSize = asset->getLength();
     sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), SkFontMgr::FontParameters()));
-    if (face == NULL) {
-        ALOGE("addFontFromAsset failed to create font %s", str.c_str());
-        return false;
-    }
-
-    addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */, weight, isItalic);
+    addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
     return true;
 }
 
@@ -266,10 +261,10 @@
     { "nCreateFamily",         "(J)J", (void*)FontFamily_create },
     { "nAbort",                "(J)V", (void*)FontFamily_abort },
     { "nUnrefFamily",          "(J)V", (void*)FontFamily_unref },
-    { "nAddFont",              "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
-    { "nAddFontWeightStyle",   "(JLjava/nio/ByteBuffer;IIZ)Z",
+    { "nAddFont",              "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont },
+    { "nAddFontWeightStyle",   "(JLjava/nio/ByteBuffer;III)Z",
             (void*)FontFamily_addFontWeightStyle },
-    { "nAddFontFromAssetManager",    "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIZ)Z",
+    { "nAddFontFromAssetManager",    "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIII)Z",
             (void*)FontFamily_addFontFromAssetManager },
     { "nAddAxisValue",         "(JIF)V", (void*)FontFamily_addAxisValue },
 };
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 58e4051..a3edf8d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -486,6 +486,7 @@
     <protected-broadcast android:name="android.intent.action.ACTION_RADIO_OFF" />
 
     <protected-broadcast android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
+    <protected-broadcast android:name="android.accounts.action.VISIBLE_ACCOUNTS_CHANGED" />
     <protected-broadcast android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
 
     <protected-broadcast android:name="com.android.phone.SIP_INCOMING_CALL" />
@@ -3606,6 +3607,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.PreloadsFileCacheExpirationJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
     </application>
 
 </manifest>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4432e3c..eecb021 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4100,8 +4100,9 @@
     </declare-styleable>
 
     <declare-styleable name="ProgressBar">
+        <!-- Defines the minimum value. -->
         <attr name="min" format="integer" />
-        <!-- Defines the maximum value the progress can take. -->
+        <!-- Defines the maximum value. -->
         <attr name="max" format="integer" />
         <!-- Defines the default progress value, between 0 and max. -->
         <attr name="progress" format="integer" />
@@ -7198,6 +7199,9 @@
         <!-- Whether the preference has enabled to have its view recycled when used in the list
              view. This is true by default. -->
         <attr name="recycleEnabled" format="boolean" />
+        <!-- Whether to use single line for the preference title text. By default, preference title
+             will be constrained to one line, so the default value of this attribute is true. -->
+        <attr name="singleLineTitle" format="boolean" />
     </declare-styleable>
 
     <!-- Base attributes available to CheckBoxPreference. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cf6bd9e..b9409f2 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1422,7 +1422,7 @@
             <enum name="video" value="2" />
             <!-- Apps which primarily work with images or photos, such as camera or gallery apps. -->
             <enum name="image" value="3" />
-            <!-- Apps which are primarily social apps, such as messaging, communication, or social network apps. -->
+            <!-- Apps which are primarily social apps, such as messaging, communication, email, or social network apps. -->
             <enum name="social" value="4" />
             <!-- Apps which are primarily news apps, such as newspapers, magazines, or sports apps. -->
             <enum name="news" value="5" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9b2aba3..68e766e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2508,6 +2508,9 @@
     <!-- How long history of previous vibrations should be kept for the dumpsys. -->
     <integer name="config_previousVibrationsDumpLimit">20</integer>
 
+    <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+    <integer name="config_defaultVibrationAmplitude">255</integer>
+
     <!-- Number of retries Cell Data should attempt for a given error code before
          restarting the modem.
          Error codes not listed will not lead to modem restarts.
@@ -2688,6 +2691,10 @@
     <!-- List of packages to enable in carrier demo mode (comma separated). -->
     <string name="config_carrierDemoModePackages" translatable="false"></string>
 
+    <!-- Number of days preloaded file cache should be preserved on a device before it can be
+         deleted -->
+    <integer name="config_keepPreloadsMinDays">7</integer>
+
     <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
     <bool name="config_useRoundIcon">false</bool>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f965c69..8b1b9d3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2808,6 +2808,7 @@
         <public name="recycleEnabled"/>
         <public name="isStatic" />
         <public name="isFeatureSplit" />
+        <public name="singleLineTitle" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 30b2778..d4db258 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -209,8 +209,7 @@
     <!-- Displayed to tell the user that they should switch their network preference. -->
     <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
     <!-- Displayed to tell the user that they should switch their network preference. -->
-    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings > Cellular networks > Preferred network type."</string>
-
+    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at System &gt; Network &amp; Internet &gt; Mobile networks &gt; Preferred network type."</string>
 
     <!-- Displayed to tell the user that peer changed TTY mode -->
     <string name="peerTtyModeFull">Peer requested TTY Mode FULL</string>
@@ -263,7 +262,7 @@
     <string-array name="wfcOperatorErrorNotificationMessages">
         <item>Register with your carrier</item>
     </string-array>
-    <!-- Template for showing cellular network operator name while WFC is active -->
+    <!-- Template for showing mobile network operator name while WFC is active -->
     <string-array name="wfcSpnFormats">
         <item>%s</item>
         <item>%s Wi-Fi Calling</item>
@@ -272,8 +271,8 @@
     <string name="wifi_calling_off_summary">Off</string>
     <!-- WFC, summary for Wi-Fi Preferred -->
     <string name="wfc_mode_wifi_preferred_summary">Wi-Fi preferred</string>
-    <!-- WFC, summary for Cellular Preferred -->
-    <string name="wfc_mode_cellular_preferred_summary">Cellular preferred</string>
+    <!-- WFC, summary for Mobile data Preferred -->
+    <string name="wfc_mode_cellular_preferred_summary">Mobile preferred</string>
     <!-- WFC, summary for Wi-Fi Only -->
     <string name="wfc_mode_wifi_only_summary">Wi-Fi only</string>
 
@@ -392,7 +391,7 @@
         This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
     <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
 
-    <!-- Content title for a notification. This notification indicates that the device is managed 
+    <!-- Content title for a notification. This notification indicates that the device is managed
          and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
     <string name="network_logging_notification_title">Device is managed</string>
     <!-- Content text for a notification. Tapping opens a dialog with more information on device management and network
@@ -2972,18 +2971,18 @@
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
 
-    <!-- A notification might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's title. %1$s is the network type that the device switched to, e.g., cellular data. It is one of the strings in the network_switch_type_name array. -->
+    <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's title. %1$s is the network type that the device switched to, e.g., cellular data. It is one of the strings in the network_switch_type_name array. -->
     <string name="network_switch_metered">Switched to <xliff:g id="network_type">%1$s</xliff:g></string>
 
-    <!-- A notification might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
+    <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
     <string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no Internet access. Charges may apply.</string>
 
-    <!-- A toast might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
+    <!-- A toast might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
     <string name="network_switch_metered_toast">Switched from <xliff:g id="previous_network">%1$s</xliff:g> to <xliff:g id="new_network">%2$s</xliff:g></string>
 
     <!-- Network type names used in the network_switch_metered and network_switch_metered_detail strings. These must be kept in the sync with the values NetworkCapabilities.TRANSPORT_xxx values, and in the same order. -->
     <string-array name="network_switch_type_name">
-        <item>cellular data</item>
+        <item>mobile data</item>
         <item>Wi-Fi</item>
         <item>Bluetooth</item>
         <item>Ethernet</item>
@@ -3067,13 +3066,13 @@
     <!-- See SIM_REMOVED_DIALOG.  This is the title of that dialog. -->
     <string name="sim_removed_title">SIM card removed</string>
     <!-- See SIM_REMOVED_DIALOG.  This is the message of that dialog. -->
-    <string name="sim_removed_message">The cellular network will be unavailable until you restart with a valid SIM card inserted.</string>
+    <string name="sim_removed_message">The mobile network will be unavailable until you restart with a valid SIM card inserted.</string>
     <!-- See SIM_REMOVED_DIALOG.  This is the button of that dialog. -->
     <string name="sim_done_button">Done</string>
     <!-- See SIM_ADDED_DIALOG.  This is the title of that dialog. -->
     <string name="sim_added_title">SIM card added</string>
     <!-- See SIM_ADDED_DIALOG.  This is the message of that dialog. -->
-    <string name="sim_added_message">Restart your device to access the cellular network.</string>
+    <string name="sim_added_message">Restart your device to access the mobile network.</string>
     <!-- See SIM_ADDED_DIALOG.  This is the button of that dialog. -->
     <string name="sim_restart_button">Restart</string>
     <!-- See Carrier_App_Dialog. This is the message of that dialog. -->
@@ -3628,7 +3627,7 @@
     <!-- Notification title when 4G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_4g_limit_title">4G data limit reached</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_mobile_limit_title">Cellular data limit reached</string>
+    <string name="data_usage_mobile_limit_title">Mobile data limit reached</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_wifi_limit_title">Wi-Fi data limit reached</string>
     <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. -->
@@ -3639,7 +3638,7 @@
     <!-- Notification title when 4G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_mobile_limit_snoozed_title">Cellular data limit exceeded</string>
+    <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_wifi_limit_snoozed_title">Wi-Fi data limit exceeded</string>
     <!-- Notification body when data usage has exceeded limit threshold. -->
@@ -4538,7 +4537,7 @@
     <string name="app_category_video">Movies &amp; Video</string>
     <!-- Category title for apps which primarily work with images or photos, such as camera or gallery apps. [CHAR LIMIT=32] -->
     <string name="app_category_image">Photos &amp; Images</string>
-    <!-- Category title for apps which are primarily social apps, such as messaging, communication, or social network apps. [CHAR LIMIT=32] -->
+    <!-- Category title for apps which are primarily social apps, such as messaging, communication, email, or social network apps. [CHAR LIMIT=32] -->
     <string name="app_category_social">Social &amp; Communication</string>
     <!-- Category title for apps which are primarily news apps, such as newspapers, magazines, or sports apps. [CHAR LIMIT=32] -->
     <string name="app_category_news">News &amp; Magazines</string>
@@ -4588,4 +4587,18 @@
     <!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
     <string name="autofill_save_type_credit_card">credit card</string>
 
+    <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake -->
+    <string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string>
+
+    <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for Tsunami -->
+    <string name="etws_primary_default_message_tsunami">Evacuate immediately from coastal regions and riverside areas to a safer place such as high ground.</string>
+
+    <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake and Tsunami -->
+    <string name="etws_primary_default_message_earthquake_and_tsunami">Stay calm and seek shelter nearby.</string>
+
+    <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for test -->
+    <string name="etws_primary_default_message_test">Emergency messages test</string>
+
+    <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
+    <string name="etws_primary_default_message_others"></string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f6f29d9..4ef3922 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -421,6 +421,7 @@
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
   <java-symbol type="integer" name="config_mdc_initial_max_retry" />
+  <java-symbol type="integer" name="config_keepPreloadsMinDays" />
   <java-symbol type="bool" name="config_hasPermanentDpad" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
@@ -1816,6 +1817,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+  <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="integer" name="config_radioScanningTimeout" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
@@ -2919,4 +2921,14 @@
   <java-symbol type="string" name="notification_channel_retail_mode" />
   <java-symbol type="string" name="notification_channel_usb" />
 
+  <!-- ETWS primary messages -->
+  <java-symbol type="string" name="etws_primary_default_message_earthquake" />
+
+  <java-symbol type="string" name="etws_primary_default_message_tsunami" />
+
+  <java-symbol type="string" name="etws_primary_default_message_earthquake_and_tsunami" />
+
+  <java-symbol type="string" name="etws_primary_default_message_test" />
+
+  <java-symbol type="string" name="etws_primary_default_message_others" />
 </resources>
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
index a685e2b..6c1ce44 100644
--- a/core/res/res/xml/time_zones_by_country.xml
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -336,6 +336,10 @@
 
     <timezone code="ck">Pacific/Rarotonga</timezone>
 
+    <!-- CHILE, -3:00 -->
+
+    <timezone code="cl">America/Punta_Arenas</timezone>
+
     <!-- CHILE, -4:00 -->
 
     <timezone code="cl">America/Santiago</timezone>
diff --git a/core/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java
index 0be89d5..62b5410 100644
--- a/core/tests/coretests/src/android/database/PageViewCursorTest.java
+++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java
@@ -19,15 +19,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.os.Bundle;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-import android.util.MathUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -259,12 +253,19 @@
     }
 
     @Test
-    public void testPagingMarker() {
+    public void testAutoPagedExtra() {
         mCursor = new PageViewCursor(mDelegate, 5, 100);
         assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
     }
 
     @Test
+    public void testGetWindow() {
+        mCursor = new PageViewCursor(mDelegate, 5, 5);
+        CursorWindow window = mCursor.getWindow();
+        assertEquals(5, window.getNumRows());
+    }
+
+    @Test
     public void testWrap() {
         Bundle queryArgs = new Bundle();
         queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
diff --git a/core/tests/coretests/src/android/graphics/VariationParserTest.java b/core/tests/coretests/src/android/graphics/VariationParserTest.java
index a2ead40..fdabb13 100644
--- a/core/tests/coretests/src/android/graphics/VariationParserTest.java
+++ b/core/tests/coretests/src/android/graphics/VariationParserTest.java
@@ -28,7 +28,7 @@
 
     @SmallTest
     public void testParseFontVariationSetting() {
-        int tag = FontListParser.makeTag('w', 'd', 't', 'h');
+        int tag = FontListParser.makeTag("wdth");
         List<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings("'wdth' 1");
         assertEquals(tag, axes.get(0).getTag());
         assertEquals(1.0f, axes.get(0).getStyleValue());
@@ -45,7 +45,7 @@
         assertEquals(tag, axes.get(0).getTag());
         assertEquals(0.5f, axes.get(0).getStyleValue());
 
-        tag = FontListParser.makeTag('A', 'X', ' ', ' ');
+        tag = FontListParser.makeTag("AX  ");
         axes = FontListParser.parseFontVariationSettings("'AX  ' 1");
         assertEquals(tag, axes.get(0).getTag());
         assertEquals(1.0f, axes.get(0).getStyleValue());
@@ -93,8 +93,8 @@
     public void testParseFontVariationStyleSettings() {
         List<FontConfig.Axis> axes =
                 FontListParser.parseFontVariationSettings("'wdth' 10,'AX  '\r1");
-        int tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
-        int tag2 = FontListParser.makeTag('A', 'X', ' ', ' ');
+        int tag1 = FontListParser.makeTag("wdth");
+        int tag2 = FontListParser.makeTag("AX  ");
         assertEquals(tag1, axes.get(0).getTag());
         assertEquals(10.0f, axes.get(0).getStyleValue());
         assertEquals(tag2, axes.get(1).getTag());
@@ -102,7 +102,7 @@
 
         // Test only spacers are allowed before tag
         axes = FontListParser.parseFontVariationSettings("     'wdth' 10,ab'wdth' 1");
-        tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
+        tag1 = FontListParser.makeTag("wdth");
         assertEquals(tag1, axes.get(0).getTag());
         assertEquals(10.0f, axes.get(0).getStyleValue());
         assertEquals(1, axes.size());
@@ -119,8 +119,8 @@
 
     @SmallTest
     public void testMakeTag() {
-      assertEquals(0x77647468, FontListParser.makeTag('w', 'd', 't', 'h'));
-      assertEquals(0x41582020, FontListParser.makeTag('A', 'X', ' ', ' '));
-      assertEquals(0x20202020, FontListParser.makeTag(' ', ' ', ' ', ' '));
+      assertEquals(0x77647468, FontListParser.makeTag("wdth"));
+      assertEquals(0x41582020, FontListParser.makeTag("AX  "));
+      assertEquals(0x20202020, FontListParser.makeTag("    "));
     }
 }
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
index 7a4980a..56e977c 100644
--- a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
@@ -109,7 +109,7 @@
     }
 
     public static String expectedSchedulerGroup(int prio) {
-        return prio < Process.THREAD_PRIORITY_BACKGROUND ? "/" : "/bg_non_interactive";
+        return "/";
     }
 
     public void testPassPriorityToService() throws Exception {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 77a1035..8a8d027 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -339,6 +339,14 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.tv">
+        <permission name="android.permission.DVB_DEVICE" />
+        <permission name="android.permission.GLOBAL_SEARCH" />
+        <permission name="android.permission.MODIFY_PARENTAL_CONTROLS" />
+        <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
+        <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.vpndialogs">
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.CONTROL_VPN"/>
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 2ebd2cc..1f339f7 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -48,6 +48,7 @@
      */
     protected int mScreenDensity = Bitmap.DENSITY_NONE;
     protected int mDensity = Bitmap.DENSITY_NONE;
+    private boolean mAllowHwBitmapsInSwMode = false;
 
     protected void throwIfCannotDraw(Bitmap bitmap) {
         if (bitmap.isRecycled()) {
@@ -511,8 +512,23 @@
                 indices, indexOffset, indexCount, paint.getNativeInstance());
     }
 
+    /**
+     * @hide
+     */
+    public void setHwBitmapsInSwModeEnabled(boolean enabled) {
+        mAllowHwBitmapsInSwMode = enabled;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isHwBitmapsInSwModeEnabled() {
+        return mAllowHwBitmapsInSwMode;
+    }
+
     private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
-        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+        if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
+                && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
             throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
         }
     }
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 16fc2b1..1b25a627 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -81,7 +81,8 @@
         }
     }
 
-    public boolean addFont(String path, int ttcIndex) {
+    public boolean addFont(String path, int ttcIndex, FontConfig.Axis[] axes, int weight,
+            int italic) {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("Unable to call addFont after freezing.");
         }
@@ -89,22 +90,29 @@
             FileChannel fileChannel = file.getChannel();
             long fontSize = fileChannel.size();
             ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
-            return nAddFont(mBuilderPtr, fontBuffer, ttcIndex);
+            if (axes != null) {
+                for (FontConfig.Axis axis : axes) {
+                    nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+                }
+            }
+            return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic);
         } catch (IOException e) {
             Log.e(TAG, "Error mapping font file " + path);
             return false;
         }
     }
 
-    public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes,
-            int weight, boolean style) {
+    public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes,
+            int weight, int italic) {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
         }
-        for (FontConfig.Axis axis : axes) {
-            nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+        if (axes != null) {
+            for (FontConfig.Axis axis : axes) {
+                nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+            }
         }
-        return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, style);
+        return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, italic);
     }
 
     /**
@@ -120,11 +128,18 @@
      * @return
      */
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
-            boolean isAsset, int weight, boolean isItalic) {
+            boolean isAsset, int ttcIndex, int weight, int isItalic,
+            FontConfig.Axis[] axes) {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
         }
-        return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, weight, isItalic);
+        if (axes != null) {
+            for (FontConfig.Axis axis : axes) {
+                nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+            }
+        }
+        return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight,
+                isItalic);
     }
 
     private static native long nInitBuilder(String lang, int variant);
@@ -137,11 +152,14 @@
 
     @CriticalNative
     private static native void nUnrefFamily(long nativePtr);
-    private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex);
+    // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+    // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
+    private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
+            int weight, int isItalic);
     private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
-            int ttcIndex, int weight, boolean isItalic);
+            int ttcIndex, int weight, int isItalic);
     private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
-            String path, int cookie, boolean isAsset, int weight, boolean isItalic);
+            String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic);
 
     // The added axis values are only valid for the next nAddFont* method call.
     @CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 1b6969f..b78df34 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -98,15 +98,17 @@
             } catch (NumberFormatException e) {
                 continue;  // ignoreing invalid number format
             }
-            int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2),
-                    tagString.charAt(3));
+            int tag = makeTag(tagString);
             axisList.add(new FontConfig.Axis(tag, styleValue));
         }
         return axisList;
     }
 
-    @VisibleForTesting
-    public static int makeTag(char c1, char c2, char c3, char c4) {
+    public static int makeTag(String tagString) {
+        char c1 = tagString.charAt(0);
+        char c2 = tagString.charAt(1);
+        char c3 = tagString.charAt(2);
+        char c4 = tagString.charAt(3);
         return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
     }
 
@@ -198,6 +200,13 @@
      */
     private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}");
 
+    public static boolean isValidTag(String tagString) {
+        if (tagString == null || tagString.length() != 4) {
+            return false;
+        }
+        return TAG_PATTERN.matcher(tagString).matches();
+    }
+
     /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
      *  '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
      */
@@ -208,8 +217,8 @@
             throws XmlPullParserException, IOException {
         int tag = 0;
         String tagStr = parser.getAttributeValue(null, "tag");
-        if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
-            tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3));
+        if (isValidTag(tagStr)) {
+            tag = makeTag(tagStr);
         } else {
             throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
         }
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 8511c1f..2afe375 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -21,11 +21,15 @@
 import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
 import static android.content.res.FontResourcesParser.FamilyResourceEntry;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.graphics.FontListParser;
 import android.graphics.fonts.FontRequest;
 import android.graphics.fonts.FontResult;
 import android.os.Bundle;
@@ -40,12 +44,14 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -55,9 +61,12 @@
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * The Typeface class specifies the typeface and intrinsic style of a font.
@@ -148,13 +157,16 @@
     public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
         if (sFallbackFonts != null) {
             synchronized (sDynamicTypefaceCache) {
-                final String key = createAssetUid(mgr, path);
+                final String key = Builder.createAssetUid(
+                        mgr, path, 0 /* ttcIndex */, null /* axes */);
                 Typeface typeface = sDynamicTypefaceCache.get(key);
                 if (typeface != null) return typeface;
 
                 FontFamily fontFamily = new FontFamily();
+                // TODO: introduce ttc index and variation settings to resource type font.
                 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
-                        0 /* use OS/2 table to determine weight and italic */, false)) {
+                        0 /* ttcIndex */, Builder.RESOLVE_BY_FONT_TABLE /* weight */,
+                        Builder.RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
                     fontFamily.freeze();
                     FontFamily[] families = {fontFamily};
                     typeface = createFromFamiliesWithDefault(families);
@@ -198,8 +210,10 @@
             FontFamily fontFamily = new FontFamily();
             for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
                 if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
-                        0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(),
-                      fontFile.isItalic())) {
+                        0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
+                        fontFile.getWeight(),
+                        fontFile.isItalic() ? Builder.ITALIC : Builder.NORMAL,
+                        null /* axes */)) {
                     return null;
                 }
             }
@@ -207,7 +221,8 @@
             FontFamily[] familyChain = { fontFamily };
             typeface = createFromFamiliesWithDefault(familyChain);
             synchronized (sDynamicTypefaceCache) {
-                final String key = createAssetUid(mgr, path);
+                final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
+                        null /* axes */);
                 sDynamicTypefaceCache.put(key, typeface);
             }
             return typeface;
@@ -221,7 +236,7 @@
      */
     public static Typeface findFromCache(AssetManager mgr, String path) {
         synchronized (sDynamicTypefaceCache) {
-            final String key = createAssetUid(mgr, path);
+            final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */);
             Typeface typeface = sDynamicTypefaceCache.get(key);
             if (typeface != null) {
                 return typeface;
@@ -332,8 +347,9 @@
                 int weight = (style & BOLD) != 0 ? 700 : 400;
                 // TODO: this method should be
                 // create(fd, ttcIndex, fontVariationSettings, style).
-                if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
-                                null, weight, (style & ITALIC) != 0)) {
+                if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
+                                null, weight,
+                                (style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) {
                     Log.e(TAG, "Error creating font " + request.getQuery());
                     callback.onTypefaceRequestFailed(
                             FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
@@ -432,6 +448,347 @@
     };
 
     /**
+     * A builder class for creating new Typeface instance.
+     *
+     * Examples,
+     * 1) Create Typeface from ttf file.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder.obtain();
+     * builder.setSourceFromFilePath("your_font_file.ttf");
+     * Typeface typeface = builder.build();
+     * builder.recycle();
+     * </code>
+     * </pre>
+     *
+     * 2) Create Typeface from ttc file in assets directory.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder.obtain();
+     * builder.setSourceFromAsset(getAssets(), "your_font_file.ttc");
+     * builder.setTtcIndex(2);  // set index of font collection.
+     * Typeface typeface = builder.build();
+     * builder.recycle();
+     * </code>
+     * </pre>
+     *
+     * 3) Create Typeface from existing Typeface with variation settings.
+     * <pre>
+     *
+     * <p>Note that only one source can be specified for the single Typeface.</p>
+     */
+    /** @hide TODO: Make this API public. */
+    public static final class Builder {
+        /**
+         * Value for weight and italic.
+         *
+         * Indicates the value is resolved by font metadata.
+         */
+        // Must be same with C++ constant in core/jni/android/graphics/FontFamily.cpp
+        public static final int RESOLVE_BY_FONT_TABLE = -1;
+
+        /**
+         * Value for italic.
+         *
+         * Indicates the font style is not italic.
+         */
+        public static final int NORMAL = 0;
+
+        /**
+         * Value for italic.
+         *
+         * Indicates the font style is italic.
+         */
+        public static final int ITALIC = 1;
+
+        private int mTtcIndex;
+        private FontConfig.Axis[] mAxes;
+
+        private AssetManager mAssetManager;
+        private String mPath;
+        private FileDescriptor mFd;
+        private @IntRange(from = -1) int mWeight = RESOLVE_BY_FONT_TABLE;
+
+        /** @hide */
+        @Retention(SOURCE)
+        @IntDef({RESOLVE_BY_FONT_TABLE, NORMAL, ITALIC})
+        public @interface Italic {}
+        private @Italic int mItalic = RESOLVE_BY_FONT_TABLE;
+
+        private boolean mHasSourceSet = false;
+        private boolean mRecycled = false;
+
+        /** Use Builder.obtain() instead */
+        private void Builder() {}
+
+        private static AtomicReference<Builder> mCache = new AtomicReference<>();
+
+        /**
+         * Returns Typeface.Builder from pool.
+         */
+        public static Builder obtain() {
+            final Builder builder = mCache.getAndSet(null);
+            if (builder != null) {
+                builder.mRecycled = false;
+                return builder;
+            }
+            return new Builder();
+        }
+
+        /**
+         * Resets the internal states.
+         */
+        public void reset() {
+            checkNotRecycled();
+            mTtcIndex = 0;
+            mAxes = null;
+
+            mAssetManager = null;
+            mPath = null;
+            mFd = null;
+
+            mWeight = RESOLVE_BY_FONT_TABLE;
+            mItalic = RESOLVE_BY_FONT_TABLE;
+
+            mHasSourceSet = false;
+        }
+
+        /**
+         * Returns the instance to the pool.
+         */
+        public void recycle() {
+            reset();
+            mRecycled = true;
+
+            mCache.compareAndSet(null, this);
+        }
+
+        private void checkNotRecycled() {
+            if (mRecycled) {
+                throw new IllegalStateException("Don't use Builder after calling recycle()");
+            }
+        }
+
+        private void checkSingleFontSource() {
+            if (mHasSourceSet) {
+                throw new IllegalStateException("Typeface can only built with single font source.");
+            }
+        }
+
+        /**
+         * Sets a font file as a source of Typeface.
+         *
+         * @param path The file object refers to the font file.
+         */
+        public Builder setSourceFromFile(@NonNull File path) {
+            return setSourceFromFilePath(path.getAbsolutePath());
+        }
+
+        /**
+         * Sets a font file as a source of Typeface.
+         *
+         * @param fd The file descriptor. The passed fd must be mmap-able.
+         */
+        public Builder setSourceFromFile(@NonNull FileDescriptor fd) {
+            checkNotRecycled();
+            checkSingleFontSource();
+            mFd = fd;
+            mHasSourceSet = true;
+            return this;
+        }
+
+        /**
+         * Sets a font file as a source of Typeface.
+         *
+         * @param path The full path to the font file.
+         */
+        public Builder setSourceFromFilePath(@NonNull String path) {
+            checkNotRecycled();
+            checkSingleFontSource();
+            mPath = path;
+            mHasSourceSet = true;
+            return this;
+        }
+
+        /**
+         * Sets an asset entry as a source of Typeface.
+         *
+         * @param assetManager The application's asset manager
+         * @param path The file name of the font data in the asset directory
+         */
+        public Builder setSourceFromAsset(@NonNull AssetManager assetManager,
+                @NonNull String path) {
+            checkNotRecycled();
+            checkSingleFontSource();
+            mAssetManager = Preconditions.checkNotNull(assetManager);
+            mPath = Preconditions.checkStringNotEmpty(path);
+            mHasSourceSet = true;
+            return this;
+        }
+
+        /**
+         * Sets weight of the font.
+         *
+         * By passing {@link #RESOLVE_BY_FONT_TABLE}, weight value is resolved by OS/2 table in
+         * font file if possible.
+         * @param weight a weight value or {@link #RESOLVE_BY_FONT_TABLE}
+         */
+        public Builder setWeight(@IntRange(from = -1) int weight) {
+            checkNotRecycled();
+            mWeight = weight;
+            return this;
+        }
+
+        /**
+         * Sets italic information of the font.
+         *
+         * By passing {@link #RESOLVE_BY_FONT_TABLE}, italic or normal is determined by OS/2 table
+         * in font file if possible.
+         * @param italic One of {@link #NORMAL}, {@link #ITALIC}, {@link #RESOLVE_BY_FONT_TABLE}.
+         *                 will be used.
+         */
+        public Builder setItalic(@Italic int italic) {
+            checkNotRecycled();
+            mItalic = italic;
+            return this;
+        }
+
+        /**
+         * Sets an idex of the font collection.
+         *
+         * Can not be used for Typeface source. build() method will return null for invalid index.
+         * @param ttcIndex An index of the font collection. If the font source is not font
+         *                 collection, do not call this method or specify 0.
+         */
+        public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+            checkNotRecycled();
+            mTtcIndex = ttcIndex;
+            return this;
+        }
+
+        /**
+         * Sets a font variation settings.
+         *
+         * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
+         */
+        public Builder setFontVariationSettings(@Nullable String variationSettings) {
+            checkNotRecycled();
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
+            final List<FontConfig.Axis> axesList = FontListParser.parseFontVariationSettings(
+                    variationSettings);
+            mAxes = axesList.toArray(new FontConfig.Axis[axesList.size()]);
+            return this;
+        }
+
+        /**
+         * Sets a font variation settings.
+         *
+         * @param axes An array of font variation axis tag-value pairs.
+         */
+        public Builder setFontVariationSettings(@Nullable FontConfig.Axis[] axes) {
+            checkNotRecycled();
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
+            mAxes = axes;
+            return this;
+        }
+
+        /**
+         * Creates a unique id for a given AssetManager and asset path.
+         *
+         * @param mgr  AssetManager instance
+         * @param path The path for the asset.
+         * @param ttcIndex The TTC index for the font.
+         * @param axes The font variation settings.
+         * @return Unique id for a given AssetManager and asset path.
+         */
+        private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
+                @Nullable FontConfig.Axis[] axes) {
+            final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
+            final StringBuilder builder = new StringBuilder();
+            final int size = pkgs.size();
+            for (int i = 0; i < size; i++) {
+                builder.append(pkgs.valueAt(i));
+                builder.append("-");
+            }
+            builder.append(path);
+            builder.append("-");
+            builder.append(Integer.toString(ttcIndex));
+            builder.append("-");
+            if (axes != null) {
+                for (FontConfig.Axis axis : axes) {
+                    builder.append(Integer.toHexString(axis.getTag()));
+                    builder.append("-");
+                    builder.append(Float.toString(axis.getStyleValue()));
+                }
+            }
+            return builder.toString();
+        }
+
+        /**
+         * Generates new Typeface from specified configuration.
+         *
+         * @return Newly created Typeface. May return null if some parameters are invalid.
+         */
+        public Typeface build() {
+            checkNotRecycled();
+            if (!mHasSourceSet) {
+                return null;
+            }
+
+            if (mFd != null) {  // set source by setSourceFromFile(FileDescriptor)
+                try (FileInputStream fis = new FileInputStream(mFd)) {
+                    FileChannel channel = fis.getChannel();
+                    long size = channel.size();
+                    ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+
+                    final FontFamily fontFamily = new FontFamily();
+                    if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
+                        fontFamily.abortCreation();
+                        return null;
+                    }
+                    fontFamily.freeze();
+                    FontFamily[] families = { fontFamily };
+                    return createFromFamiliesWithDefault(families);
+                } catch (IOException e) {
+                    return null;
+                }
+            } else if (mAssetManager != null) {  // set source by setSourceFromAsset()
+                final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes);
+                synchronized (sDynamicTypefaceCache) {
+                    Typeface typeface = sDynamicTypefaceCache.get(key);
+                    if (typeface != null) return typeface;
+                    final FontFamily fontFamily = new FontFamily();
+                    if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
+                            true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
+                        fontFamily.abortCreation();
+                        return null;
+                    }
+                    fontFamily.freeze();
+                    FontFamily[] families = { fontFamily };
+                    typeface = createFromFamiliesWithDefault(families);
+                    sDynamicTypefaceCache.put(key, typeface);
+                    return typeface;
+                }
+            } else if (mPath != null) {  // set source by setSourceFromFile(File)
+                final FontFamily fontFamily = new FontFamily();
+                if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
+                    fontFamily.abortCreation();
+                    return null;
+                }
+                fontFamily.freeze();
+                FontFamily[] families = { fontFamily };
+                return createFromFamiliesWithDefault(families);
+            } else {
+                throw new IllegalArgumentException("No source was set.");
+            }
+        }
+    }
+
+    /**
      * Create a typeface object given a family name, and option style information.
      * If null is passed for the name, then the "default" font will be chosen.
      * The resulting typeface object can be queried (getStyle()) to discover what
@@ -518,49 +875,26 @@
      * @return The new typeface.
      */
     public static Typeface createFromAsset(AssetManager mgr, String path) {
+        if (path == null) {
+            throw new NullPointerException();  // for backward compatibility
+        }
         if (sFallbackFonts != null) {
-            synchronized (sDynamicTypefaceCache) {
-                final String key = createAssetUid(mgr, path);
-                Typeface typeface = sDynamicTypefaceCache.get(key);
-                if (typeface != null) return typeface;
-
-                FontFamily fontFamily = new FontFamily();
-                if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
-                        0 /* use OS/2 table to determine weight and italic */, false)) {
-                    fontFamily.freeze();
-                    FontFamily[] families = { fontFamily };
-                    typeface = createFromFamiliesWithDefault(families);
-                    sDynamicTypefaceCache.put(key, typeface);
+            final Builder builder = Builder.obtain();
+            try {
+                builder.setSourceFromAsset(mgr, path);
+                Typeface typeface = builder.build();
+                if (typeface != null) {
                     return typeface;
-                } else {
-                    fontFamily.abortCreation();
                 }
+            } finally {
+                builder.recycle();
             }
         }
+        // For the compatibility reasons, throw runtime exception if failed to create Typeface.
         throw new RuntimeException("Font asset not found " + path);
     }
 
     /**
-     * Creates a unique id for a given AssetManager and asset path.
-     *
-     * @param mgr  AssetManager instance
-     * @param path The path for the asset.
-     * @return Unique id for a given AssetManager and asset path.
-     */
-    private static String createAssetUid(final AssetManager mgr, String path) {
-        final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
-        final StringBuilder builder = new StringBuilder();
-        builder.append("asset:");
-        final int size = pkgs.size();
-        for (int i = 0; i < size; i++) {
-            builder.append(pkgs.valueAt(i));
-            builder.append("-");
-        }
-        builder.append(path);
-        return builder.toString();
-    }
-
-    /**
      * Creates a unique id for a given font provider and query.
      */
     private static String createProviderUid(String authority, String query) {
@@ -578,7 +912,9 @@
      * @param path The path to the font data.
      * @return The new typeface.
      */
-    public static Typeface createFromFile(File path) {
+    public static Typeface createFromFile(@Nullable File path) {
+        // For the compatibility reasons, leaving possible NPE here.
+        // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
         return createFromFile(path.getAbsolutePath());
     }
 
@@ -588,15 +924,24 @@
      * @param path The full path to the font data.
      * @return The new typeface.
      */
-    public static Typeface createFromFile(String path) {
+    public static Typeface createFromFile(@Nullable String path) {
+        if (path == null) {
+            // For the compatibility reasons, need to throw NPE if the argument is null.
+            // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull
+            throw new NullPointerException();
+        }
         if (sFallbackFonts != null) {
-            FontFamily fontFamily = new FontFamily();
-            if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
-                fontFamily.freeze();
-                FontFamily[] families = { fontFamily };
-                return createFromFamiliesWithDefault(families);
-            } else {
-                fontFamily.abortCreation();
+            final Builder builder = Builder.obtain();
+            try {
+                builder.setSourceFromFilePath(path);
+                Typeface typeface = builder.build();
+                if (typeface != null) {
+                    // For the compatibility reasons, throw runtime exception if failed to create
+                    // Typeface.
+                    return typeface;
+                }
+            } finally {
+                builder.recycle();
             }
         }
         throw new RuntimeException("Font not found " + path);
@@ -606,9 +951,8 @@
      * Create a new typeface from an array of font families.
      *
      * @param families array of font families
-     * @hide
      */
-    public static Typeface createFromFamilies(FontFamily[] families) {
+    private static Typeface createFromFamilies(FontFamily[] families) {
         long[] ptrArray = new long[families.length];
         for (int i = 0; i < families.length; i++) {
             ptrArray[i] = families[i].mNativePtr;
@@ -621,9 +965,8 @@
      * also the font families in the fallback list.
      *
      * @param families array of font families
-     * @hide
      */
-    public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
+    private static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
         long[] ptrArray = new long[families.length + sFallbackFonts.length];
         for (int i = 0; i < families.length; i++) {
             ptrArray[i] = families[i].mNativePtr;
@@ -660,8 +1003,8 @@
                     continue;
                 }
             }
-            if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
-                    font.getWeight(), font.isItalic())) {
+            if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
+                    font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) {
                 Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
             }
         }
@@ -806,6 +1149,7 @@
     }
 
     private static native long nativeCreateFromTypeface(long native_instance, int style);
+    // TODO: clean up: change List<FontConfig.Axis> to FontConfig.Axis[]
     private static native long nativeCreateFromTypefaceWithVariation(
             long native_instance, List<FontConfig.Axis> axes);
     private static native long nativeCreateWeightAlias(long native_instance, int weight);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 244c525..09840a5 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4430,6 +4430,7 @@
         if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) {
             ALOGW("ResTable_map at %d is beyond type chunk data %d",
                  (int)curOff, dtohl(entry.type->header.size));
+            free(set);
             return BAD_TYPE;
         }
         map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff);
@@ -4442,6 +4443,7 @@
             if (grp->dynamicRefTable.lookupResourceId(&newName) != NO_ERROR) {
                 ALOGE("Failed resolving ResTable_map name at %d with ident 0x%08x",
                         (int) curOff, (int) newName);
+                free(set);
                 return UNKNOWN_ERROR;
             }
         }
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 68d3dd5..8823a92 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -33,65 +33,10 @@
 
 const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
 
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
-        bool useStagingData) {
-    float matrixScale = getMatrixScale(groupStackedMatrix);
-    if (matrixScale == 0) {
-        // When either x or y is scaled to 0, we don't need to draw anything.
-        return;
-    }
-
-    SkMatrix pathMatrix(groupStackedMatrix);
-    pathMatrix.postScale(scaleX, scaleY);
-
-    //TODO: try apply the path matrix to the canvas instead of creating a new path.
-    SkPath renderPath;
-    renderPath.reset();
-
-    if (useStagingData) {
-        SkPath tmpPath;
-        getStagingPath(&tmpPath);
-        renderPath.addPath(tmpPath, pathMatrix);
-    } else {
-        renderPath.addPath(getUpdatedPath(), pathMatrix);
-    }
-
-    float minScale = fmin(scaleX, scaleY);
-    float strokeScale = minScale * matrixScale;
-    drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
-}
-
 void Path::dump() {
     ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
 }
 
-float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
-    // Given unit vectors A = (0, 1) and B = (1, 0).
-    // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
-    // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
-    // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
-    // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
-    //
-    // For non-skew case, which is most of the cases, matrix scale is computing exactly the
-    // scale on x and y axis, and take the minimal of these two.
-    // For skew case, an unit square will mapped to a parallelogram. And this function will
-    // return the minimal height of the 2 bases.
-    SkVector skVectors[2];
-    skVectors[0].set(0, 1);
-    skVectors[1].set(1, 0);
-    groupStackedMatrix.mapVectors(skVectors, 2);
-    float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
-    float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
-    float crossProduct = skVectors[0].cross(skVectors[1]);
-    float maxScale = fmax(scaleX, scaleY);
-
-    float matrixScale = 0;
-    if (maxScale > 0) {
-        matrixScale = fabs(crossProduct) / maxScale;
-    }
-    return matrixScale;
-}
-
 // Called from UI thread during the initial setup/theme change.
 Path::Path(const char* pathStr, size_t strLength) {
     PathParser::ParseResult result;
@@ -104,18 +49,19 @@
     mStagingProperties.syncProperties(path.mStagingProperties);
 }
 
-const SkPath& Path::getUpdatedPath() {
-    if (mSkPathDirty) {
-        mSkPath.reset();
-        VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
-        mSkPathDirty = false;
+const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+    if (useStagingData) {
+        tempStagingPath->reset();
+        VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
+        return *tempStagingPath;
+    } else {
+        if (mSkPathDirty) {
+            mSkPath.reset();
+            VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+            mSkPathDirty = false;
+        }
+        return mSkPath;
     }
-    return mSkPath;
-}
-
-void Path::getStagingPath(SkPath* outPath) {
-    outPath->reset();
-    VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
 }
 
 void Path::syncProperties() {
@@ -157,26 +103,35 @@
     }
 }
 
-const SkPath& FullPath::getUpdatedPath() {
-    if (!mSkPathDirty && !mProperties.mTrimDirty) {
+const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+    if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
         return mTrimmedSkPath;
     }
-    Path::getUpdatedPath();
-    if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
-        mProperties.mTrimDirty = false;
-        applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
-                mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
-        return mTrimmedSkPath;
+    Path::getUpdatedPath(useStagingData, tempStagingPath);
+    SkPath *outPath;
+    if (useStagingData) {
+        SkPath inPath = *tempStagingPath;
+        applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
+                mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+        outPath = tempStagingPath;
     } else {
-        return mSkPath;
+        if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+            mProperties.mTrimDirty = false;
+            applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+                    mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+            outPath = &mTrimmedSkPath;
+        } else {
+            outPath = &mSkPath;
+        }
     }
-}
-
-void FullPath::getStagingPath(SkPath* outPath) {
-    Path::getStagingPath(outPath);
-    SkPath inPath = *outPath;
-    applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
-            mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+    const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+    bool setFillPath = properties.getFillGradient() != nullptr
+            || properties.getFillColor() != SK_ColorTRANSPARENT;
+    if (setFillPath) {
+        SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+        outPath->setFillType(ft);
+    }
+    return *outPath;
 }
 
 void FullPath::dump() {
@@ -192,16 +147,17 @@
     return SkColorSetA(color, alphaBytes * alpha);
 }
 
-void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
-                        const SkMatrix& matrix, bool useStagingData){
+void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
     const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+    SkPath tempStagingPath;
+    const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
 
     // Draw path's fill, if fill color or gradient is valid
     bool needsFill = false;
     SkPaint paint;
     if (properties.getFillGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
-        paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
         needsFill = true;
     } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -211,8 +167,6 @@
     if (needsFill) {
         paint.setStyle(SkPaint::Style::kFill_Style);
         paint.setAntiAlias(true);
-        SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
-        renderPath.setFillType(ft);
         outCanvas->drawPath(renderPath, paint);
     }
 
@@ -220,7 +174,7 @@
     bool needsStroke = false;
     if (properties.getStrokeGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
-        paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
         needsStroke = true;
     } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -232,7 +186,7 @@
         paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
         paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
         paint.setStrokeMiter(properties.getStrokeMiterLimit());
-        paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+        paint.setStrokeWidth(properties.getStrokeWidth());
         outCanvas->drawPath(renderPath, paint);
     }
 }
@@ -306,36 +260,28 @@
     }
 }
 
-void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-        float strokeScale, const SkMatrix& matrix, bool useStagingData){
-    outCanvas->clipPath(renderPath);
+void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
+    SkPath tempStagingPath;
+    outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
 }
 
 Group::Group(const Group& group) : Node(group) {
     mStagingProperties.syncProperties(group.mStagingProperties);
 }
 
-void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
-        float scaleY, bool useStagingData) {
-    // TODO: Try apply the matrix to the canvas instead of passing it down the tree
-
-    // Calculate current group's matrix by preConcat the parent's and
-    // and the current one on the top of the stack.
-    // Basically the Mfinal = Mviewport * M0 * M1 * M2;
-    // Mi the local matrix at level i of the group tree.
+void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
+    // Save the current clip and matrix information, which is local to this group.
+    SkAutoCanvasRestore saver(outCanvas, true);
+    // apply the current group's matrix to the canvas
     SkMatrix stackedMatrix;
     const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
     getLocalMatrix(&stackedMatrix, prop);
-    stackedMatrix.postConcat(currentMatrix);
-
-    // Save the current clip information, which is local to this group.
-    outCanvas->save();
+    outCanvas->concat(stackedMatrix);
     // Draw the group tree in the same order as the XML file.
     for (auto& child : mChildren) {
-        child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+        child->draw(outCanvas, useStagingData);
     }
-    // Restore the previous clip information.
-    outCanvas->restore();
+    // Restore the previous clip and matrix information.
 }
 
 void Group::dump() {
@@ -556,7 +502,8 @@
             mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
     float scaleX = outCache.width() / viewportWidth;
     float scaleY = outCache.height() / viewportHeight;
-    mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+    outCanvas.scale(scaleX, scaleY);
+    mRootNode->draw(&outCanvas, useStagingData);
 }
 
 bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 8244a39..729a4dd 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -109,8 +109,7 @@
         mName = node.mName;
     }
     Node() {}
-    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
-            float scaleX, float scaleY, bool useStagingData) = 0;
+    virtual void draw(SkCanvas* outCanvas, bool useStagingData) = 0;
     virtual void dump() = 0;
     void setName(const char* name) {
         mName = name;
@@ -169,9 +168,6 @@
     Path() {}
 
     void dump() override;
-    void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
-            float scaleX, float scaleY, bool useStagingData) override;
-    static float getMatrixScale(const SkMatrix& groupStackedMatrix);
     virtual void syncProperties() override;
     virtual void onPropertyChanged(Properties* prop) override {
         if (prop == &mStagingProperties) {
@@ -193,10 +189,7 @@
     PathProperties* mutateProperties() { return &mProperties; }
 
 protected:
-    virtual const SkPath& getUpdatedPath();
-    virtual void getStagingPath(SkPath* outPath);
-    virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+    virtual const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath);
 
     // Internal data, render thread only.
     bool mSkPathDirty = true;
@@ -364,6 +357,7 @@
     FullPath(const FullPath& path); // for cloning
     FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
     FullPath() : Path() {}
+    void draw(SkCanvas* outCanvas, bool useStagingData) override;
     void dump() override;
     FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
     const FullPathProperties* stagingProperties() { return &mStagingProperties; }
@@ -387,10 +381,7 @@
     }
 
 protected:
-    const SkPath& getUpdatedPath() override;
-    void getStagingPath(SkPath* outPath) override;
-    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+    const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
 private:
 
     FullPathProperties mProperties = FullPathProperties(this);
@@ -407,10 +398,7 @@
     ClipPath(const ClipPath& path) : Path(path) {}
     ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
     ClipPath() : Path() {}
-
-protected:
-    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+    void draw(SkCanvas* outCanvas, bool useStagingData) override;
 };
 
 class ANDROID_API Group: public Node {
@@ -519,8 +507,7 @@
     GroupProperties* mutateProperties() { return &mProperties; }
 
     // Methods below could be called from either UI thread or Render Thread.
-    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
-            float scaleX, float scaleY, bool useStagingData) override;
+    virtual void draw(SkCanvas* outCanvas, bool useStagingData) override;
     void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
     void dump() override;
     static bool isValidProperty(int propertyId);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 79429ec..a895cba 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -206,10 +206,10 @@
         return new T();
     }
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
-        return sk_sp<SkSurface>();
+        return nullptr;
     }
-    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
-        return sk_sp<SkImage>();
+    sk_sp<SkImage> onNewImageSnapshot() override {
+        return nullptr;
     }
     T* canvas() { return static_cast<T*>(getCanvas()); }
     void onCopyOnWrite(ContentChangeMode) override {}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 8e0d3ee..6f264e1 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -347,51 +347,6 @@
     }
 }
 
-TEST(VectorDrawable, matrixScale) {
-    struct MatrixAndScale {
-        float buffer[9];
-        float matrixScale;
-    };
-
-    const MatrixAndScale sMatrixAndScales[] {
-        {
-            {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
-            1.0
-        },
-        {
-            {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
-            1.0f,
-        },
-        {
-            {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
-            1.5f,
-        },
-        {
-            {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
-            0.99999994f,
-        },
-        {
-            {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
-            0.99999994f,
-        },
-        {
-            {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
-            0.9999999f,
-        },
-        {
-            {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
-            1.0000001f,
-        },
-    };
-
-    for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
-        SkMatrix matrix;
-        matrix.set9(matrixAndScale.buffer);
-        float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
-        EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
-    }
-}
-
 TEST(VectorDrawable, groupProperties) {
     //TODO: Also need to test property sync and dirty flag when properties change.
     VectorDrawable::Group group;
@@ -458,7 +413,7 @@
 
     // Setting the fill gradient increments the ref count of the shader by 1
     path.mutateStagingProperties()->setFillGradient(shader);
-    path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+    path.draw(&canvas, true);
     // Resetting the fill gradient decrements the ref count of the shader by 1
     path.mutateStagingProperties()->setFillGradient(nullptr);
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 2e9133b..20be2ba 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -161,7 +161,7 @@
                     try {
                         mDatabase.getMapper().startAddingDocuments(documentId);
                         if (mDatabase.getMapper().putStorageDocuments(
-                                documentId, device.eventsSupported, device.roots)) {
+                                documentId, device.operationsSupported, device.roots)) {
                             changed = true;
                         }
                         if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ec52742c..f6c5ade 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -777,14 +777,14 @@
     <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
 
     <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging -->
-    <string name="power_remaining_duration_only">about <xliff:g id="time">%1$s</xliff:g> left</string>
+    <string name="power_remaining_duration_only">Approx. <xliff:g id="time">%1$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
     <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
     <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g>
-        - approx. <xliff:g id="time">%2$s</xliff:g> left</string>
+        - about <xliff:g id="time">%2$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
     <string name="power_discharging_duration_short"><xliff:g id="level">%1$s</xliff:g>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
new file mode 100644
index 0000000..d3bdeb7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.wifi;
+
+import android.content.Context;
+import android.os.Looper;
+
+/**
+ * Factory method used to inject WifiTracker instances.
+ */
+public class WifiTrackerFactory {
+    private static boolean sTestingMode = false;
+
+    private static WifiTracker sTestingWifiTracker;
+
+    public static void enableTestingMode() {
+        sTestingMode = true;
+    }
+
+    public static void disableTestingMode() {
+        sTestingMode = false;
+    }
+
+    public static void setTestingWifiTracker(WifiTracker tracker) {
+        sTestingWifiTracker = tracker;
+    }
+
+    public static WifiTracker create(
+            Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
+            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+        if(sTestingMode) {
+            return sTestingWifiTracker;
+        }
+        return new WifiTracker(
+                context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+    }
+}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 72bdbf1..5ffc8f9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -726,6 +726,10 @@
     <!-- The alpha to apply to the recents row when it doesn't have focus -->
     <item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
 
+    <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+         loading full resolution screenshots. -->
+    <dimen name="recents_fast_fling_velocity">600dp</dimen>
+
     <!-- The size of the PIP drag-to-dismiss target. -->
     <dimen name="pip_dismiss_target_size">48dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1f58d4c..f8d1bfb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1582,7 +1582,7 @@
      */
     public void reportSimUnlocked(int subId) {
         if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
-        int slotId = SubscriptionManager.getSlotId(subId);
+        int slotId = SubscriptionManager.getSlotIndex(subId);
         handleSimStateChange(subId, slotId, State.READY);
     }
 
@@ -1751,7 +1751,7 @@
         for (int i = 0; i < list.size(); i++) {
             final SubscriptionInfo info = list.get(i);
             final int id = info.getSubscriptionId();
-            int slotId = SubscriptionManager.getSlotId(id);
+            int slotId = SubscriptionManager.getSlotIndex(id);
             if (state == getSimState(id) && bestSlotId > slotId ) {
                 resultId = id;
                 bestSlotId = slotId;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 4dfaf45..374086d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -25,6 +25,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.NightDisplayController;
 import com.android.internal.util.Preconditions;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.plugins.ActivityStarter;
@@ -248,6 +249,9 @@
         mProviders.put(PluginDependencyProvider.class, () ->
                 new PluginDependencyProvider(get(PluginManager.class)));
 
+        mProviders.put(LocalBluetoothManager.class, () ->
+                LocalBluetoothManager.getInstance(mContext, null));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index d3e939f..6da8272 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -33,6 +33,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -59,6 +60,7 @@
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.HighResThumbnailLoader;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -184,6 +186,7 @@
         return sTaskLoader;
     }
 
+
     public static SystemServicesProxy getSystemServices() {
         return sSystemServicesProxy;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c9debb2..f0a9bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -375,6 +375,8 @@
 
         // Notify of the next draw
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
+
+        Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
     }
 
     @Override
@@ -529,6 +531,7 @@
         mReceivedNewIntent = false;
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
+        Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
 
         if (!isChangingConfigurations()) {
             // Workaround for b/22542869, if the RecentsActivity is started again, but without going
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index d7c1391..25eea95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -637,7 +637,7 @@
     }
 
     /** Returns the top task thumbnail for the given task id */
-    public ThumbnailData getTaskThumbnail(int taskId) {
+    public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
         if (mAm == null) return null;
 
         // If we are mocking, then just return a dummy thumbnail
@@ -649,7 +649,7 @@
             return thumbnailData;
         }
 
-        ThumbnailData thumbnailData = getThumbnail(taskId);
+        ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
         if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
             thumbnailData.thumbnail.setHasAlpha(false);
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -669,7 +669,7 @@
     /**
      * Returns a task thumbnail from the activity manager
      */
-    public @NonNull ThumbnailData getThumbnail(int taskId) {
+    public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
         if (mAm == null) {
             return new ThumbnailData();
         }
@@ -678,7 +678,8 @@
         if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
             ActivityManager.TaskSnapshot snapshot = null;
             try {
-                snapshot = ActivityManager.getService().getTaskSnapshot(taskId);
+                snapshot = ActivityManager.getService().getTaskSnapshot(taskId,
+                        false /* reducedResolution */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to retrieve snapshot", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 0000000..be8da9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskCallbacks;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+    @GuardedBy("mLoadQueue")
+    private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+    @GuardedBy("mLoadQueue")
+    private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+    @GuardedBy("mLoadQueue")
+    private boolean mLoaderIdling;
+
+    private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+    private final Thread mLoadThread;
+    private final Handler mMainThreadHandler;
+    private final SystemServicesProxy mSystemServicesProxy;
+    private boolean mLoading;
+    private boolean mVisible;
+    private boolean mFlingingFast;
+
+    public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) {
+        mMainThreadHandler = new Handler(looper);
+        mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+        mLoadThread.start();
+        mSystemServicesProxy = ssp;
+    }
+
+    public void setVisible(boolean visible) {
+        mVisible = visible;
+        updateLoading();
+    }
+
+    public void setFlingingFast(boolean flingingFast) {
+        if (mFlingingFast == flingingFast) {
+            return;
+        }
+        mFlingingFast = flingingFast;
+        updateLoading();
+    }
+
+    @VisibleForTesting
+    boolean isLoading() {
+        return mLoading;
+    }
+
+    private void updateLoading() {
+        setLoading(mVisible && !mFlingingFast);
+    }
+
+    private void setLoading(boolean loading) {
+        if (loading == mLoading) {
+            return;
+        }
+        synchronized (mLoadQueue) {
+            mLoading = loading;
+            if (!loading) {
+                stopLoading();
+            } else {
+                startLoading();
+            }
+        }
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void startLoading() {
+        for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+            Task t = mVisibleTasks.get(i);
+            if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+                    && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+                mLoadQueue.add(t);
+            }
+        }
+        mLoadQueue.notifyAll();
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void stopLoading() {
+        mLoadQueue.clear();
+        mLoadQueue.notifyAll();
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. Note that this is different from
+     * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+     * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+     * has been updated.
+     */
+    public void onTaskVisible(Task t) {
+        t.addCallback(this);
+        mVisibleTasks.add(t);
+        if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.add(t);
+                mLoadQueue.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+     * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+     */
+    public void onTaskInvisible(Task t) {
+        t.removeCallback(this);
+        mVisibleTasks.remove(t);
+        synchronized (mLoadQueue) {
+            mLoadQueue.remove(t);
+        }
+    }
+
+    @VisibleForTesting
+    void waitForLoaderIdle() {
+        while (true) {
+            synchronized (mLoadQueue) {
+                if (mLoadQueue.isEmpty() && mLoaderIdling) {
+                    return;
+                }
+            }
+            SystemClock.sleep(100);
+        }
+    }
+
+    @Override
+    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+        if (thumbnailData != null && !thumbnailData.reducedResolution) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.remove(task);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskDataUnloaded() {
+    }
+
+    @Override
+    public void onTaskStackIdChanged() {
+    }
+
+    private final Runnable mLoader = new Runnable() {
+
+        @Override
+        public void run() {
+            setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+            while (true) {
+                Task next = null;
+                synchronized (mLoadQueue) {
+                    if (!mLoading || mLoadQueue.isEmpty()) {
+                        try {
+                            mLoaderIdling = true;
+                            mLoadQueue.wait();
+                            mLoaderIdling = false;
+                        } catch (InterruptedException e) {
+                            // Don't care.
+                        }
+                    } else {
+                        next = mLoadQueue.poll();
+                        if (next != null) {
+                            mLoadingTasks.add(next);
+                        }
+                    }
+                }
+                if (next != null) {
+                    loadTask(next);
+                }
+            }
+        }
+
+        private void loadTask(Task t) {
+            ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+                    false /* reducedResolution */);
+            mMainThreadHandler.post(() -> {
+                synchronized (mLoadQueue) {
+                    mLoadingTasks.remove(t);
+                }
+                if (mVisibleTasks.contains(t)) {
+                    t.notifyTaskDataLoaded(thumbnail, t.icon);
+                }
+            });
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 12c10df..f8d123b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -188,7 +188,8 @@
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
                     : null;
-            Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
+            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                    false /* loadIfNotCached */);
             int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
             int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
             boolean isSystemApp = (info != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 40a4a2b..e378d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.util.Log;
 import android.util.LruCache;
 
@@ -37,6 +38,7 @@
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task.TaskKey;
 
 import java.io.PrintWriter;
 import java.util.Map;
@@ -156,7 +158,6 @@
                     }
                 }
             } else {
-                RecentsConfiguration config = Recents.getConfiguration();
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 // If we've stopped the loader, then fall through to the above logic to wait on
                 // the load thread
@@ -190,7 +191,8 @@
                         }
 
                         if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-                        ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
+                        ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+                                true /* reducedResolution */);
 
                         if (cachedThumbnailData.thumbnail == null) {
                             cachedThumbnailData.thumbnail = mDefaultThumbnail;
@@ -242,6 +244,7 @@
     private final TaskKeyLruCache<String> mContentDescriptionCache;
     private final TaskResourceLoadQueue mLoadQueue;
     private final BackgroundTaskLoader mLoader;
+    private final HighResThumbnailLoader mHighResThumbnailLoader;
 
     private final int mMaxThumbnailCacheSize;
     private final int mMaxIconCacheSize;
@@ -293,6 +296,8 @@
                 mClearActivityInfoOnEviction);
         mActivityInfoCache = new LruCache(numRecentTasks);
         mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+        mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+                Looper.getMainLooper());
     }
 
     /** Returns the size of the app icon cache. */
@@ -305,6 +310,10 @@
         return mMaxThumbnailCacheSize;
     }
 
+    public HighResThumbnailLoader getHighResThumbnailLoader() {
+        return mHighResThumbnailLoader;
+    }
+
     /** Creates a new plan for loading the recent tasks. */
     public RecentsTaskLoadPlan createLoadPlan(Context context) {
         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
@@ -346,7 +355,7 @@
     /** Releases the task resource data back into the pool. */
     public void unloadTaskData(Task t) {
         mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(null, mDefaultIcon);
+        t.notifyTaskDataUnloaded(mDefaultIcon);
     }
 
     /** Completely removes the resource data from the pool. */
@@ -356,7 +365,7 @@
         mActivityLabelCache.remove(t.key);
         mContentDescriptionCache.remove(t.key);
         if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(null, mDefaultIcon);
+            t.notifyTaskDataUnloaded(mDefaultIcon);
         }
     }
 
@@ -491,16 +500,16 @@
     /**
      * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
      */
-    Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+    ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         if (loadIfNotCached) {
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                 // Load the thumbnail from the system
-                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
                 if (thumbnailData.thumbnail != null) {
-                    return thumbnailData.thumbnail;
+                    return thumbnailData;
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 2f2e866..29d0a23 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.model;
 
 import android.app.ActivityManager;
+import android.app.ActivityManager.TaskThumbnail;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -141,7 +142,7 @@
      * which can then fall back to the application icon.
      */
     public Drawable icon;
-    public Bitmap thumbnail;
+    public ThumbnailData thumbnail;
     @ViewDebug.ExportedProperty(category="recents")
     public String title;
     @ViewDebug.ExportedProperty(category="recents")
@@ -199,11 +200,11 @@
     }
 
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
-                Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
-                String appInfoDescription, int colorPrimary, int colorBackground,
-                boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
-                boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
-                int resizeMode, ComponentName topActivity, boolean isLocked) {
+            ThumbnailData thumbnail, String title, String titleDescription,
+            String dismissDescription, String appInfoDescription, int colorPrimary,
+            int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
+            boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
+            int resizeMode, ComponentName topActivity, boolean isLocked) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -301,7 +302,7 @@
     /** Notifies the callback listeners that this task has been loaded */
     public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
         this.icon = applicationIcon;
-        this.thumbnail = thumbnailData != null ? thumbnailData.thumbnail : null;
+        this.thumbnail = thumbnailData;
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
             mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
@@ -309,9 +310,9 @@
     }
 
     /** Notifies the callback listeners that this task has been unloaded */
-    public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
+    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
         icon = defaultApplicationIcon;
-        thumbnail = defaultThumbnail;
+        thumbnail = null;
         for (int i = mCallbacks.size() - 1; i >= 0; i--) {
             mCallbacks.get(i).onTaskDataUnloaded();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 09a3712..33ff1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -29,12 +29,16 @@
     public Bitmap thumbnail;
     public int orientation;
     public final Rect insets = new Rect();
+    public boolean reducedResolution;
+    public float scale;
 
     public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
         ThumbnailData out = new ThumbnailData();
         out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
         out.insets.set(snapshot.getContentInsets());
         out.orientation = snapshot.getOrientation();
+        out.reducedResolution = snapshot.isReducedResolution();
+        out.scale = snapshot.getScale();
         return out;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 40aad45..b7cedf7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -83,8 +83,8 @@
 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
@@ -96,10 +96,10 @@
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-
 import com.android.systemui.recents.views.grid.GridTaskView;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -217,6 +217,9 @@
     // grid layout.
     private TaskViewFocusFrame mTaskViewFocusFrame;
 
+    private Task mPrefetchingTask;
+    private final float mFastFlingVelocity;
+
     // A convenience update listener to request updating clipping of tasks
     private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
             new ValueAnimator.AnimatorUpdateListener() {
@@ -273,6 +276,7 @@
         mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
                 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
                 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mDividerSize = ssp.getDockedDividerSize(context);
         mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
         mDisplayRect = ssp.getDisplayRect();
@@ -663,6 +667,8 @@
             }
         }
 
+        updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
+
         // Update the focus if the previous focused task was returned to the view pool
         if (lastFocusedTaskIndex != -1) {
             int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
@@ -1200,6 +1206,8 @@
         if (mStackScroller.computeScroll()) {
             // Notify accessibility
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
+                    mStackScroller.getScrollVelocity() > mFastFlingVelocity);
         }
         if (mDeferredTaskViewLayoutAnimation != null) {
             relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
@@ -1657,13 +1665,41 @@
             tv.setNoUserInteractionState();
         }
 
-        // Load the task data
-        Recents.getTaskLoader().loadTaskData(task);
+        if (task == mPrefetchingTask) {
+            task.notifyTaskDataLoaded(task.thumbnail, task.icon);
+        } else {
+            // Load the task data
+            Recents.getTaskLoader().loadTaskData(task);
+        }
+        Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
     }
 
     private void unbindTaskView(TaskView tv, Task task) {
-        // Report that this task's data is no longer being used
-        Recents.getTaskLoader().unloadTaskData(task);
+        if (task != mPrefetchingTask) {
+            // Report that this task's data is no longer being used
+            Recents.getTaskLoader().unloadTaskData(task);
+        }
+        Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
+    }
+
+    private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
+        Task t = null;
+        boolean somethingVisible = frontIndex != -1 && backIndex != -1;
+        if (somethingVisible && frontIndex < tasks.size() - 1) {
+            t = tasks.get(frontIndex + 1);
+        }
+        if (mPrefetchingTask != t) {
+            if (mPrefetchingTask != null) {
+                int index = tasks.indexOf(mPrefetchingTask);
+                if (index < backIndex || index > frontIndex) {
+                    Recents.getTaskLoader().unloadTaskData(mPrefetchingTask);
+                }
+            }
+            mPrefetchingTask = t;
+            if (t != null) {
+                Recents.getTaskLoader().loadTaskData(t);
+            }
+        }
     }
 
     /**** TaskViewCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 1fa73c6..8cd3d15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -262,6 +262,10 @@
         return !mScroller.isFinished();
     }
 
+    float getScrollVelocity() {
+        return mScroller.getCurrVelocity();
+    }
+
     /** Stops the scroller and any current fling. */
     void stopScroller() {
         if (!mScroller.isFinished()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e0dac09..5989b33 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -66,7 +66,7 @@
     protected Rect mThumbnailRect = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
     protected float mThumbnailScale;
-    private float mFullscreenThumbnailScale;
+    private float mFullscreenThumbnailScale = 1f;
     /** The height, in pixels, of the task view's title bar. */
     private int mTitleBarHeight;
     private boolean mSizeToFit = false;
@@ -116,12 +116,6 @@
         mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
         mBgFillPaint.setColor(Color.WHITE);
         mLockedPaint.setColor(Color.WHITE);
-        if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
-            mFullscreenThumbnailScale = 1f;
-        } else {
-            mFullscreenThumbnailScale = res.getFraction(
-                    com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
-        }
         mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
     }
 
@@ -190,6 +184,7 @@
         if (thumbnailData != null && thumbnailData.thumbnail != null) {
             Bitmap bm = thumbnailData.thumbnail;
             bm.prepareToDraw();
+            mFullscreenThumbnailScale = thumbnailData.scale;
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mThumbnailRect.set(0, 0,
@@ -297,7 +292,8 @@
                         (float) mTaskViewRect.width() / mThumbnailRect.width(),
                         (float) mTaskViewRect.height() / mThumbnailRect.height());
             }
-            mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+            mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
+                    -mThumbnailData.insets.top * mFullscreenThumbnailScale);
             mMatrix.postScale(mThumbnailScale, mThumbnailScale);
             mBitmapShader.setLocalMatrix(mMatrix);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 2b52b48..5fb642f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -31,6 +31,7 @@
 import android.app.IActivityManager;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -107,6 +108,7 @@
     private int mNavigationBarMode;
     private AccessibilityManager mAccessibilityManager;
     private MagnificationContentObserver mMagnificationObserver;
+    private ContentResolver mContentResolver;
 
     private int mDisabledFlags1;
     private StatusBar mStatusBar;
@@ -138,9 +140,10 @@
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
         mAccessibilityManager.addAccessibilityServicesStateChangeListener(
                 this::updateAccessibilityServicesState);
+        mContentResolver = getContext().getContentResolver();
         mMagnificationObserver = new MagnificationContentObserver(
                 getContext().getMainThreadHandler());
-        getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+        mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
                 mMagnificationObserver);
 
@@ -163,7 +166,7 @@
         mCommandQueue.removeCallbacks(this);
         mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
                 this::updateAccessibilityServicesState);
-        getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
+        mContentResolver.unregisterContentObserver(mMagnificationObserver);
         try {
             WindowManagerGlobal.getWindowManagerService()
                     .removeRotationWatcher(mRotationWatcher);
@@ -563,7 +566,7 @@
     private void updateAccessibilityServicesState() {
         int requestingServices = 0;
         try {
-            if (Settings.Secure.getInt(getContext().getContentResolver(),
+            if (Settings.Secure.getInt(mContentResolver,
                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) {
                 requestingServices++;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 15c4afe..36d24b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -29,6 +29,7 @@
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.Dependency;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -52,7 +53,7 @@
     private int mState;
 
     public BluetoothControllerImpl(Context context, Looper bgLooper) {
-        mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+        mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
         if (mLocalBluetoothManager != null) {
             mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
             mLocalBluetoothManager.getEventManager().registerCallback(this);
@@ -174,24 +175,30 @@
     private void updateConnected() {
         // Make sure our connection state is up to date.
         int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
-        if (state != mConnectionState) {
-            mConnectionState = state;
-            mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+        if (mLastDevice != null && !mLastDevice.isConnected()) {
+            // Clear out last device if no longer connected.
+            mLastDevice = null;
         }
-        if (mLastDevice != null && mLastDevice.isConnected()) {
-            // Our current device is still valid.
-            return;
-        }
-        mLastDevice = null;
+        // If any of the devices are in a higher state than the adapter, move the adapter into
+        // that state.
         for (CachedBluetoothDevice device : getDevices()) {
-            if (device.isConnected()) {
+            int maxDeviceState = device.getMaxConnectionState();
+            if (maxDeviceState > state) {
+                state = maxDeviceState;
+            }
+            if (mLastDevice == null && device.isConnected()) {
+                // Set as last connected device only if we don't have one.
                 mLastDevice = device;
             }
         }
-        if (mLastDevice == null && mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
+
+        if (mLastDevice == null && state == BluetoothAdapter.STATE_CONNECTED) {
             // If somehow we think we are connected, but have no connected devices, we aren't
             // connected.
-            mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+            state = BluetoothAdapter.STATE_DISCONNECTED;
+        }
+        if (state != mConnectionState) {
+            mConnectionState = state;
             mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
         }
     }
@@ -238,7 +245,6 @@
     public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
         mLastDevice = cachedDevice;
         updateConnected();
-        mConnectionState = state;
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 0000000..4d632af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiTestCase {
+
+    private HighResThumbnailLoader mLoader;
+
+    @Mock
+    private SystemServicesProxy mMockSystemServicesProxy;
+    @Mock
+    private Task mTask;
+
+    private ThumbnailData mThumbnailData = new ThumbnailData();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper());
+        mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
+        when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
+                .thenReturn(mThumbnailData);
+        mLoader.setVisible(true);
+    }
+
+    @Test
+    public void testLoading() throws Exception {
+        mLoader.setVisible(true);
+        assertTrue(mLoader.isLoading());
+        mLoader.setVisible(false);
+        assertFalse(mLoader.isLoading());
+        mLoader.setVisible(true);
+        mLoader.setFlingingFast(true);
+        assertFalse(mLoader.isLoading());
+        mLoader.setFlingingFast(false);
+        assertTrue(mLoader.isLoading());
+    }
+
+    @Test
+    public void testLoad() throws Exception {
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testFlinging_notLoaded() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    /**
+     * Tests whether task is loaded after stopping to fling
+     */
+    @Test
+    public void testAfterFlinging() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.setFlingingFast(false);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testAlreadyLoaded() throws Exception {
+        mTask.thumbnail = new ThumbnailData();
+        mTask.thumbnail.reducedResolution = false;
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
new file mode 100644
index 0000000..8808988
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT 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.policy;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BluetoothControllerImplTest extends SysuiTestCase {
+
+    private LocalBluetoothManager mMockBluetoothManager;
+    private CachedBluetoothDeviceManager mMockDeviceManager;
+    private LocalBluetoothAdapter mMockAdapter;
+    private TestableLooper mTestableLooper;
+    private BluetoothControllerImpl mBluetoothControllerImpl;
+
+    private List<CachedBluetoothDevice> mDevices;
+
+    @Before
+    public void setup() throws Exception {
+        mTestableLooper = new TestableLooper();
+        mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
+        mDevices = new ArrayList<>();
+        mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
+        when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+        when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
+        mMockAdapter = mock(LocalBluetoothAdapter.class);
+        when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter);
+        when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class));
+
+        mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+                mTestableLooper.getLooper());
+    }
+
+    @Test
+    public void testNoConnectionWithDevices() {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        mDevices.add(device);
+        when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+
+        mBluetoothControllerImpl.onConnectionStateChanged(null,
+                BluetoothAdapter.STATE_DISCONNECTED);
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index a8c8752..85eecdf 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -36,6 +36,8 @@
 import android.view.autofill.AutofillValue;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
+import android.widget.RemoteViews;
+
 import com.android.internal.R;
 import libcore.util.Objects;
 
@@ -110,15 +112,15 @@
                 final Dataset dataset = response.getDatasets().get(i);
                 final int index = dataset.getFieldIds().indexOf(focusedViewId);
                 if (index >= 0) {
-                    final AutofillValue value = dataset.getFieldValues().get(index);
+                    final RemoteViews presentation = dataset.getFieldPresentation(index);
                     final View view;
                     try {
-                        view = dataset.getPresentation().apply(context, null);
+                        view = presentation.apply(context, null);
                     } catch (RuntimeException e) {
                         Slog.e(TAG, "Error inflating remote views", e);
                         continue;
                     }
-
+                    final AutofillValue value = dataset.getFieldValues().get(index);
                     String valueText = null;
                     if (value.isText()) {
                         valueText = value.getTextValue().toString().toLowerCase();
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 794ece6..d312902 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@
     tzdata_update2 \
     android.hidl.base@1.0-java-static \
     android.hardware.biometrics.fingerprint@2.1-java-static \
+    android.hardware.vibrator@1.0-java-constants \
 
 ifneq ($(INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index c9dd116..98242f9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -489,7 +489,8 @@
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                     }
                 });
-            } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
+            } else if (mSentLowBatteryBroadcast &&
+                    mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 46c9f25..78c0fe6 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -667,7 +667,7 @@
     @Override
     public boolean setActiveScorer(String packageName) {
         // Only the system can set the active scorer
-        if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
+        if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
             throw new SecurityException(
                     "Caller is neither the system process nor a score requester.");
         }
@@ -736,7 +736,7 @@
     @Override
     public List<NetworkScorerAppData> getAllValidScorers() {
         // Only the system can access this data.
-        if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
+        if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
             throw new SecurityException(
                     "Caller is neither the system process nor a score requester.");
         }
@@ -747,7 +747,7 @@
     @Override
     public void disableScoring() {
         // Only the active scorer or the system should be allowed to disable scoring.
-        if (!isCallerActiveScorer(getCallingUid()) || !callerCanRequestScores()) {
+        if (!isCallerActiveScorer(getCallingUid()) && !callerCanRequestScores()) {
             throw new SecurityException(
                     "Caller is neither the active scorer nor the scorer manager.");
         }
diff --git a/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java
new file mode 100644
index 0000000..6fd0256
--- /dev/null
+++ b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.R;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link JobService} that marks
+ * {@link Environment#getDataPreloadsFileCacheDirectory() preloaded file cache} as expired after a
+ * pre-configured timeout.
+ */
+public class PreloadsFileCacheExpirationJobService extends JobService {
+    private static final boolean DEBUG = false; // Do not submit with true
+    private static final String TAG = "PreloadsFileCacheExpirationJobService";
+
+    // TODO move all JOB_IDs into a single class to avoid collisions
+    private static final int JOB_ID = 100500;
+
+    private static final String PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED
+            = "persist.sys.preloads.file_cache_expired";
+
+    public static void schedule(Context context) {
+        int keepPreloadsMinDays = Resources.getSystem().getInteger(
+                R.integer.config_keepPreloadsMinDays); // Default is 1 week
+        long keepPreloadsMinTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(2)
+                : TimeUnit.DAYS.toMillis(keepPreloadsMinDays);
+        long keepPreloadsMaxTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(3)
+                : TimeUnit.DAYS.toMillis(keepPreloadsMinDays + 1);
+
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder("Scheduling expiration job to run in ");
+            TimeUtils.formatDuration(keepPreloadsMinTimeoutMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        JobInfo expirationJob = new JobInfo.Builder(JOB_ID,
+                new ComponentName(context, PreloadsFileCacheExpirationJobService.class))
+                .setPersisted(true)
+                .setMinimumLatency(keepPreloadsMinTimeoutMs)
+                .setOverrideDeadline(keepPreloadsMaxTimeoutMs)
+                .build();
+
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        jobScheduler.schedule(expirationJob);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        SystemProperties.set(PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED, "1");
+        Slog.i(TAG, "Set " + PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED + "=1");
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952..c4676d1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
 import android.media.AudioManager;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
@@ -42,6 +44,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
 
-    private final LinkedList<Vibration> mVibrations;
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
-    private Vibration mCurrentVibration;
+    private final boolean mSupportsAmplitudeControl;
+    private final int mDefaultVibrationAmplitude;
+    private final VibrationEffect[] mFallbackEffects;
     private final WorkSource mTmpWorkSource = new WorkSource();
     private final Handler mH = new Handler();
+    private final Object mLock = new Object();
 
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@
     private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
 
-    volatile VibrateThread mThread;
+    private volatile VibrateThread mThread;
 
-    // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+    // mInputDeviceVibrators lock should be acquired after mLock, if both are
     // to be acquired
     private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
 
+    private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
     private boolean mLowPowerMode;
     private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@
     native static void vibratorInit();
     native static void vibratorOn(long milliseconds);
     native static void vibratorOff();
+    native static boolean vibratorSupportsAmplitudeControl();
+    native static void vibratorSetAmplitude(int amplitude);
+    native static long vibratorPerformEffect(long effect, long strength);
 
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
-        private final long    mTimeout;
-        private final long    mStartTime;
-        private final long[]  mPattern;
-        private final int     mRepeat;
-        private final int     mUsageHint;
-        private final int     mUid;
-        private final String  mOpPkg;
+        private final VibrationEffect mEffect;
+        private final long mStartTime;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
-            this(token, millis, null, 0, usageHint, uid, opPkg);
-        }
-
-        Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
-                String opPkg) {
-            this(token, 0, pattern, repeat, usageHint, uid, opPkg);
-        }
-
-        private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat, int usageHint, int uid, String opPkg) {
+        private Vibration(IBinder token, VibrationEffect effect,
+                int usageHint, int uid, String opPkg) {
             mToken = token;
-            mTimeout = millis;
+            mEffect = effect;
             mStartTime = SystemClock.uptimeMillis();
-            mPattern = pattern;
-            mRepeat = repeat;
             mUsageHint = usageHint;
             mUid = uid;
             mOpPkg = opPkg;
         }
 
         public void binderDied() {
-            synchronized (mVibrations) {
-                mVibrations.remove(this);
+            synchronized (mLock) {
                 if (this == mCurrentVibration) {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
                 }
             }
         }
 
         public boolean hasLongerTimeout(long millis) {
-            if (mTimeout == 0) {
-                // This is a pattern, return false to play the simple
-                // vibration.
-                return false;
+            // If the current effect is a one shot vibration that will end after the given timeout
+            // for the new one shot vibration, then just let the current vibration finish. All
+            // other effect types will get pre-empted.
+            if (mEffect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+                return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
             }
-            if ((mStartTime + mTimeout)
-                    < (SystemClock.uptimeMillis() + millis)) {
-                // If this vibration will end before the time passed in, let
-                // the new vibration play.
-                return false;
-            }
-            return true;
+            return false;
         }
 
         public boolean isSystemHapticFeedback() {
+            boolean repeating = false;
+            if (mEffect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+                repeating = (waveform.getRepeatIndex() < 0);
+            }
             return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
-                    && mRepeat < 0;
+                    && !repeating;
         }
     }
 
     private static class VibrationInfo {
-        long timeout;
-        long startTime;
-        long[] pattern;
-        int repeat;
-        int usageHint;
-        int uid;
-        String opPkg;
+        private final long mStartTime;
+        private final VibrationEffect mEffect;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+        public VibrationInfo(long startTime, VibrationEffect effect,
                 int usageHint, int uid, String opPkg) {
-            this.timeout = timeout;
-            this.startTime = startTime;
-            this.pattern = pattern;
-            this.repeat = repeat;
-            this.usageHint = usageHint;
-            this.uid = uid;
-            this.opPkg = opPkg;
+            mStartTime = startTime;
+            mEffect = effect;
+            mUsageHint = usageHint;
+            mUid = uid;
+            mOpPkg = opPkg;
         }
 
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("timeout: ")
-                    .append(timeout)
                     .append(", startTime: ")
-                    .append(startTime)
-                    .append(", pattern: ")
-                    .append(Arrays.toString(pattern))
-                    .append(", repeat: ")
-                    .append(repeat)
+                    .append(mStartTime)
+                    .append(", effect: ")
+                    .append(mEffect)
                     .append(", usageHint: ")
-                    .append(usageHint)
+                    .append(mUsageHint)
                     .append(", uid: ")
-                    .append(uid)
+                    .append(mUid)
                     .append(", opPkg: ")
-                    .append(opPkg)
+                    .append(mOpPkg)
                     .toString();
         }
     }
@@ -207,25 +194,38 @@
         // restart instead of a fresh boot.
         vibratorOff();
 
+        mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
         mContext = context;
-        PowerManager pm = (PowerManager)context.getSystemService(
-                Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
-        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mAppOpsService =
+            IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
         mPreviousVibrationsLimit = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
 
-        mVibrations = new LinkedList<>();
+        mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
         mPreviousVibrations = new LinkedList<>();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         context.registerReceiver(mIntentReceiver, filter);
+
+        long[] clickEffectTimings = getLongIntArray(context.getResources(),
+                com.android.internal.R.array.config_virtualKeyVibePattern);
+        VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+        VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+                new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+        mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
     }
 
     public void systemReady() {
@@ -242,7 +242,7 @@
 
                     @Override
                     public void onLowPowerModeChanged(PowerSaveState result) {
-                        updateInputDeviceVibrators();
+                        updateVibrators();
                     }
         });
 
@@ -253,11 +253,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                updateInputDeviceVibrators();
+                updateVibrators();
             }
         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
 
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@
 
         @Override
         public void onChange(boolean SelfChange) {
-            updateInputDeviceVibrators();
+            updateVibrators();
         }
     }
 
@@ -276,6 +276,15 @@
         return doVibratorExists();
     }
 
+    @Override // Binder call
+    public boolean hasAmplitudeControl() {
+        synchronized (mInputDeviceVibrators) {
+            // Input device vibrators don't support amplitude controls yet, but are still used over
+            // the system vibrator when connected.
+            return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+        }
+    }
+
     private void verifyIncomingUid(int uid) {
         if (uid == Binder.getCallingUid()) {
             return;
@@ -287,103 +296,96 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
+    /**
+     * Validate the incoming VibrationEffect.
+     *
+     * We can't throw exceptions here since we might be called from some system_server component,
+     * which would bring the whole system down.
+     *
+     * @return whether the VibrationEffect is valid
+     */
+    private static boolean verifyVibrationEffect(VibrationEffect effect) {
+        if (effect == null) {
+            // Effect must not be null.
+            Slog.wtf(TAG, "effect must not be null");
+            return false;
+        }
+        try {
+            effect.validate();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+            return false;
+        }
+        return true;
+    }
+
+    private static long[] getLongIntArray(Resources r, int resid) {
+        int[] ar = r.getIntArray(resid);
+        if (ar == null) {
+            return null;
+        }
+        long[] out = new long[ar.length];
+        for (int i = 0; i < ar.length; i++) {
+            out[i] = ar[i];
+        }
+        return out;
+    }
+
     @Override // Binder call
-    public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
+    public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
             IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        if (token == null) {
+            Slog.e(TAG, "token must not be null");
+            return;
+        }
         verifyIncomingUid(uid);
-        // We're running in the system server so we cannot crash. Check for a
-        // timeout of 0 or negative. This will ensure that a vibration has
-        // either a timeout of > 0 or a non-null pattern.
-        if (milliseconds <= 0 || (mCurrentVibration != null
-                && mCurrentVibration.hasLongerTimeout(milliseconds))) {
-            // Ignore this vibration since the current vibration will play for
-            // longer than milliseconds.
+        if (!verifyVibrationEffect(effect)) {
             return;
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
-        }
-
-        Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
-                doCancelVibrateLocked();
-                addToPreviousVibrationsLocked(vib);
-                startVibrationLocked(vib);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private boolean isAll0(long[] pattern) {
-        int N = pattern.length;
-        for (int i = 0; i < N; i++) {
-            if (pattern[i] != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override // Binder call
-    public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
-            int usageHint, IBinder token) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires VIBRATE permission");
-        }
-        verifyIncomingUid(uid);
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            if (DEBUG) {
-                String s = "";
-                int N = pattern.length;
-                for (int i=0; i<N; i++) {
-                    s += " " + pattern[i];
+        // If our current vibration is longer than the new vibration and is the same amplitude,
+        // then just let the current one finish.
+        if (effect instanceof VibrationEffect.OneShot
+                && mCurrentVibration != null
+                && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+            VibrationEffect.OneShot currentOneShot =
+                    (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+            if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+                    && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+                if (DEBUG) {
+                    Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
                 }
-                Slog.d(TAG, "Vibrating with pattern:" + s);
-            }
-
-            // we're running in the server so we can't fail
-            if (pattern == null || pattern.length == 0
-                    || isAll0(pattern)
-                    || repeat >= pattern.length || token == null) {
                 return;
             }
+        }
 
-            Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+        Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+
+        // Only link against waveforms since they potentially don't have a finish if
+        // they're repeating. Let other effects just play out until they're done.
+        if (effect instanceof VibrationEffect.Waveform) {
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
                 return;
             }
+        }
 
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
                 doCancelVibrateLocked();
-                if (repeat >= 0) {
-                    mVibrations.addFirst(vib);
-                    startNextVibrationLocked();
-                } else {
-                    // A negative repeat means that this pattern is not meant
-                    // to repeat. Treat it like a simple vibration.
-                    startVibrationLocked(vib);
-                }
+                startVibrationLocked(vib);
                 addToPreviousVibrationsLocked(vib);
             }
-        }
-        finally {
-            Binder.restoreCallingIdentity(identity);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -391,8 +393,8 @@
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
         }
-        mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
-                vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+        mPreviousVibrations.addLast(new VibrationInfo(
+                    vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
     }
 
     @Override // Binder call
@@ -401,97 +403,97 @@
                 android.Manifest.permission.VIBRATE,
                 "cancelVibrate");
 
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                final Vibration vib = removeVibrationLocked(token);
-                if (vib == mCurrentVibration) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Canceling vibration.");
-                    }
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration.");
+                }
+                long ident = Binder.clearCallingIdentity();
+                try {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
                 }
             }
         }
-        finally {
-            Binder.restoreCallingIdentity(identity);
-        }
     }
 
-    private final Runnable mVibrationRunnable = new Runnable() {
+    private final Runnable mVibrationEndRunnable = new Runnable() {
         @Override
         public void run() {
-            synchronized (mVibrations) {
-                doCancelVibrateLocked();
-                startNextVibrationLocked();
-            }
+            onVibrationFinished();
         }
     };
 
-    // Lock held on mVibrations
     private void doCancelVibrateLocked() {
+        mH.removeCallbacks(mVibrationEndRunnable);
         if (mThread != null) {
-            synchronized (mThread) {
-                mThread.mDone = true;
-                mThread.notify();
-            }
+            mThread.cancel();
             mThread = null;
         }
         doVibratorOff();
-        mH.removeCallbacks(mVibrationRunnable);
         reportFinishVibrationLocked();
     }
 
-    // Lock held on mVibrations
-    private void startNextVibrationLocked() {
-        if (mVibrations.size() <= 0) {
-            reportFinishVibrationLocked();
-            mCurrentVibration = null;
-            return;
+    // Callback for whenever the current vibration has finished played out
+    public void onVibrationFinished() {
+        if (DEBUG) {
+            Slog.e(TAG, "Vibration finished, cleaning up");
         }
-        startVibrationLocked(mVibrations.getFirst());
+        synchronized (mLock) {
+            // Make sure the vibration is really done. This also reports that the vibration is
+            // finished.
+            doCancelVibrateLocked();
+        }
     }
 
-    // Lock held on mVibrations
     private void startVibrationLocked(final Vibration vib) {
-        try {
-            if (mLowPowerMode
-                    && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
-                return;
+        if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, low power mode");
             }
-
-            if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
-                    !shouldVibrateForRingtone()) {
-                return;
-            }
-
-            int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
-                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
-                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
-            }
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mCurrentVibration = vib;
-            } else {
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
-                }
-                mH.post(mVibrationRunnable);
-                return;
-            }
-        } catch (RemoteException e) {
+            return;
         }
-        if (vib.mTimeout != 0) {
-            doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
-            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
-        } else {
+
+        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+                !shouldVibrateForRingtone()) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+            }
+            return;
+        }
+
+        final int mode = getAppOpMode(vib);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            if (mode == AppOpsManager.MODE_ERRORED) {
+                // We might be getting calls from within system_server, so we don't actually want
+                // to throw a SecurityException here.
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+            }
+            return;
+        }
+        startVibrationInnerLocked(vib);
+    }
+
+    private void startVibrationInnerLocked(Vibration vib) {
+        mCurrentVibration = vib;
+        if (vib.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+            doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+            mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+        } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
             // mThread better be null here. doCancelVibrate should always be
             // called before startNextVibrationLocked or startVibrationLocked.
-            mThread = new VibrateThread(vib);
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+            mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
             mThread.start();
+        } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+            long timeout = doVibratorPrebakedEffectLocked(vib);
+            if (timeout > 0) {
+                mH.postDelayed(mVibrationEndRunnable, timeout);
+            }
+        } else {
+            Slog.e(TAG, "Unknown vibration type, ignoring");
         }
     }
 
@@ -507,104 +509,115 @@
         }
     }
 
+    private int getAppOpMode(Vibration vib) {
+        int mode;
+        try {
+            mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            if (mode == AppOpsManager.MODE_ALLOWED) {
+                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+            mode = AppOpsManager.MODE_IGNORED;
+        }
+        return mode;
+    }
+
     private void reportFinishVibrationLocked() {
         if (mCurrentVibration != null) {
             try {
                 mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
                         AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
                         mCurrentVibration.mOpPkg);
-            } catch (RemoteException e) {
-            }
+            } catch (RemoteException e) { }
             mCurrentVibration = null;
         }
     }
 
-    // Lock held on mVibrations
-    private Vibration removeVibrationLocked(IBinder token) {
-        ListIterator<Vibration> iter = mVibrations.listIterator(0);
-        while (iter.hasNext()) {
-            Vibration vib = iter.next();
-            if (vib.mToken == token) {
-                iter.remove();
-                unlinkVibration(vib);
-                return vib;
-            }
-        }
-        // We might be looking for a simple vibration which is only stored in
-        // mCurrentVibration.
-        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
-            unlinkVibration(mCurrentVibration);
-            return mCurrentVibration;
-        }
-        return null;
-    }
-
     private void unlinkVibration(Vibration vib) {
-        if (vib.mPattern != null) {
-            // If Vibration object has a pattern,
-            // the Vibration object has also been linkedToDeath.
+        if (vib.mEffect instanceof VibrationEffect.Waveform) {
             vib.mToken.unlinkToDeath(vib, 0);
         }
     }
 
-    private void updateInputDeviceVibrators() {
-        synchronized (mVibrations) {
-            doCancelVibrateLocked();
+    private void updateVibrators() {
+        synchronized (mLock) {
+            boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+            boolean lowPowerModeUpdated = updateLowPowerModeLocked();
 
-            synchronized (mInputDeviceVibrators) {
-                mVibrateInputDevicesSetting = false;
-                try {
-                    mVibrateInputDevicesSetting = Settings.System.getIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
-                } catch (SettingNotFoundException snfe) {
-                }
+            if (devicesUpdated || lowPowerModeUpdated) {
+                // If the state changes out from under us then just reset.
+                doCancelVibrateLocked();
+            }
+        }
+    }
 
-                mLowPowerMode = mPowerManagerInternal
-                        .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+    private boolean updateInputDeviceVibratorsLocked() {
+        boolean changed = false;
+        boolean vibrateInputDevices = false;
+        try {
+            vibrateInputDevices = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+        } catch (SettingNotFoundException snfe) {
+        }
+        if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+            changed = true;
+            mVibrateInputDevicesSetting = vibrateInputDevices;
+        }
 
-                if (mVibrateInputDevicesSetting) {
-                    if (!mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = true;
-                        mIm.registerInputDeviceListener(this, mH);
-                    }
-                } else {
-                    if (mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = false;
-                        mIm.unregisterInputDeviceListener(this);
-                    }
-                }
+        if (mVibrateInputDevicesSetting) {
+            if (!mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = true;
+                mIm.registerInputDeviceListener(this, mH);
+            }
+        } else {
+            if (mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = false;
+                mIm.unregisterInputDeviceListener(this);
+            }
+        }
 
-                mInputDeviceVibrators.clear();
-                if (mVibrateInputDevicesSetting) {
-                    int[] ids = mIm.getInputDeviceIds();
-                    for (int i = 0; i < ids.length; i++) {
-                        InputDevice device = mIm.getInputDevice(ids[i]);
-                        Vibrator vibrator = device.getVibrator();
-                        if (vibrator.hasVibrator()) {
-                            mInputDeviceVibrators.add(vibrator);
-                        }
-                    }
+        mInputDeviceVibrators.clear();
+        if (mVibrateInputDevicesSetting) {
+            int[] ids = mIm.getInputDeviceIds();
+            for (int i = 0; i < ids.length; i++) {
+                InputDevice device = mIm.getInputDevice(ids[i]);
+                Vibrator vibrator = device.getVibrator();
+                if (vibrator.hasVibrator()) {
+                    mInputDeviceVibrators.add(vibrator);
                 }
             }
-
-            startNextVibrationLocked();
+            return true;
         }
+        return changed;
+    }
+
+    private boolean updateLowPowerModeLocked() {
+        boolean lowPowerMode = mPowerManagerInternal
+                .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+        if (lowPowerMode != mLowPowerMode) {
+            mLowPowerMode = lowPowerMode;
+            return true;
+        }
+        return false;
     }
 
     @Override
     public void onInputDeviceAdded(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceChanged(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private boolean doVibratorExists() {
@@ -619,41 +632,44 @@
         return vibratorExists();
     }
 
-    private void doVibratorOn(long millis, int uid, int usageHint) {
+    private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
         synchronized (mInputDeviceVibrators) {
+            if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+                amplitude = mDefaultVibrationAmplitude;
+            }
             if (DEBUG) {
-                Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+                Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+                        " with amplitude " + amplitude + ".");
             }
-            try {
-                mBatteryStatsService.noteVibratorOn(uid, millis);
-                mCurVibUid = uid;
-            } catch (RemoteException e) {
-            }
+            noteVibratorOnLocked(uid, millis);
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
-                final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
-                        .build();
+                final AudioAttributes attributes =
+                        new AudioAttributes.Builder().setUsage(usageHint).build();
                 for (int i = 0; i < vibratorCount; i++) {
                     mInputDeviceVibrators.get(i).vibrate(millis, attributes);
                 }
             } else {
+                // Note: ordering is important here! Many haptic drivers will reset their amplitude
+                // when enabled, so we always have to enable frst, then set the amplitude.
                 vibratorOn(millis);
+                doVibratorSetAmplitude(amplitude);
             }
         }
     }
 
+    private void doVibratorSetAmplitude(int amplitude) {
+        if (mSupportsAmplitudeControl) {
+            vibratorSetAmplitude(amplitude);
+        }
+    }
+
     private void doVibratorOff() {
         synchronized (mInputDeviceVibrators) {
             if (DEBUG) {
                 Slog.d(TAG, "Turning vibrator off.");
             }
-            if (mCurVibUid >= 0) {
-                try {
-                    mBatteryStatsService.noteVibratorOff(mCurVibUid);
-                } catch (RemoteException e) {
-                }
-                mCurVibUid = -1;
-            }
+            noteVibratorOffLocked();
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
                 for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@
         }
     }
 
-    private class VibrateThread extends Thread {
-        final Vibration mVibration;
-        boolean mDone;
+    private long doVibratorPrebakedEffectLocked(Vibration vib) {
+        synchronized (mInputDeviceVibrators) {
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+            // Input devices don't support prebaked effect, so skip trying it with them.
+            final int vibratorCount = mInputDeviceVibrators.size();
+            if (vibratorCount == 0) {
+                long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+                if (timeout > 0) {
+                    noteVibratorOnLocked(vib.mUid, timeout);
+                    return timeout;
+                }
+            }
+            final int id = prebaked.getId();
+            if (id < 0 || id >= mFallbackEffects.length) {
+                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+                return 0;
+            }
+            VibrationEffect effect = mFallbackEffects[id];
+            Vibration fallbackVib =
+                    new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            startVibrationInnerLocked(fallbackVib);
+        }
+        return 0;
+    }
 
-        VibrateThread(Vibration vib) {
-            mVibration = vib;
-            mTmpWorkSource.set(vib.mUid);
+    private void noteVibratorOnLocked(int uid, long millis) {
+        try {
+            mBatteryStatsService.noteVibratorOn(uid, millis);
+            mCurVibUid = uid;
+        } catch (RemoteException e) {
+        }
+    }
+
+    private void noteVibratorOffLocked() {
+        if (mCurVibUid >= 0) {
+            try {
+                mBatteryStatsService.noteVibratorOff(mCurVibUid);
+            } catch (RemoteException e) { }
+            mCurVibUid = -1;
+        }
+    }
+
+    private class VibrateThread extends Thread {
+        private final VibrationEffect.Waveform mWaveform;
+        private final int mUid;
+        private final int mUsageHint;
+
+        private boolean mForceStop;
+
+        VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+            mWaveform = waveform;
+            mUid = uid;
+            mUsageHint = usageHint;
+            mTmpWorkSource.set(uid);
             mWakeLock.setWorkSource(mTmpWorkSource);
-            mWakeLock.acquire();
         }
 
-        private void delay(long duration) {
+        private long delayLocked(long duration) {
+            long durationRemaining = duration;
             if (duration > 0) {
-                long bedtime = duration + SystemClock.uptimeMillis();
+                final long bedtime = duration + SystemClock.uptimeMillis();
                 do {
                     try {
-                        this.wait(duration);
+                        this.wait(durationRemaining);
                     }
-                    catch (InterruptedException e) {
-                    }
-                    if (mDone) {
+                    catch (InterruptedException e) { }
+                    if (mForceStop) {
                         break;
                     }
-                    duration = bedtime - SystemClock.uptimeMillis();
-                } while (duration > 0);
+                    durationRemaining = bedtime - SystemClock.uptimeMillis();
+                } while (durationRemaining > 0);
+                return duration - durationRemaining;
             }
+            return 0;
         }
 
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-            synchronized (this) {
-                final long[] pattern = mVibration.mPattern;
-                final int len = pattern.length;
-                final int repeat = mVibration.mRepeat;
-                final int uid = mVibration.mUid;
-                final int usageHint = mVibration.mUsageHint;
-                int index = 0;
-                long duration = 0;
-
-                while (!mDone) {
-                    // add off-time duration to any accumulated on-time duration
-                    if (index < len) {
-                        duration += pattern[index++];
-                    }
-
-                    // sleep until it is time to start the vibrator
-                    delay(duration);
-                    if (mDone) {
-                        break;
-                    }
-
-                    if (index < len) {
-                        // read on-time duration and start the vibrator
-                        // duration is saved for delay() at top of loop
-                        duration = pattern[index++];
-                        if (duration > 0) {
-                            VibratorService.this.doVibratorOn(duration, uid, usageHint);
-                        }
-                    } else {
-                        if (repeat < 0) {
-                            break;
-                        } else {
-                            index = repeat;
-                            duration = 0;
-                        }
-                    }
+            mWakeLock.acquire();
+            try {
+                boolean finished = playWaveform();
+                if (finished) {
+                    onVibrationFinished();
                 }
+            } finally {
                 mWakeLock.release();
             }
-            synchronized (mVibrations) {
-                if (mThread == this) {
-                    mThread = null;
+        }
+
+        /**
+         * Play the waveform.
+         *
+         * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+         */
+        public boolean playWaveform() {
+            synchronized (this) {
+                final long[] timings = mWaveform.getTimings();
+                final int[] amplitudes = mWaveform.getAmplitudes();
+                final int len = timings.length;
+                final int repeat = mWaveform.getRepeatIndex();
+
+                int index = 0;
+                long onDuration = 0;
+                while (!mForceStop) {
+                    if (index < len) {
+                        final int amplitude = amplitudes[index];
+                        final long duration = timings[index++];
+                        if (duration <= 0) {
+                            continue;
+                        }
+                        if (amplitude != 0) {
+                            if (onDuration <= 0) {
+                                // Telling the vibrator to start multiple times usually causes
+                                // effects to feel "choppy" because the motor resets at every on
+                                // command.  Instead we figure out how long our next "on" period is
+                                // going to be, tell the motor to stay on for the full duration,
+                                // and then wake up to change the amplitude at the appropriate
+                                // intervals.
+                                onDuration =
+                                        getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+                                doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+                            } else {
+                                doVibratorSetAmplitude(amplitude);
+                            }
+                        }
+
+                        long waitTime = delayLocked(duration);
+                        if (amplitude != 0) {
+                            onDuration -= waitTime;
+                        }
+                    } else if (repeat < 0) {
+                        break;
+                    } else {
+                        index = repeat;
+                    }
                 }
-                if (!mDone) {
-                    // If this vibration finished naturally, start the next
-                    // vibration.
-                    unlinkVibration(mVibration);
-                    startNextVibrationLocked();
+                return !mForceStop;
+            }
+        }
+
+        public void cancel() {
+            synchronized (this) {
+                mThread.mForceStop = true;
+                mThread.notify();
+            }
+        }
+
+        /**
+         * Get the duration the vibrator will be on starting at startIndex until the next time it's
+         * off.
+         */
+        private long getTotalOnDuration(
+                long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+            int i = startIndex;
+            long timing = 0;
+            while(amplitudes[i] != 0) {
+                timing += timings[i++];
+                if (i >= timings.length) {
+                    if (repeatIndex >= 0) {
+                        i = repeatIndex;
+                    } else {
+                        break;
+                    }
+                }
+                if (i == startIndex) {
+                    return 1000;
                 }
             }
+            return timing;
         }
     }
 
@@ -752,7 +857,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mVibrations) {
+                synchronized (mLock) {
                     // When the system is entering a non-interactive state, we want
                     // to cancel vibrations in case a misbehaving app has abandoned
                     // them.  However it may happen that the system is currently playing
@@ -762,16 +867,6 @@
                             && !mCurrentVibration.isSystemHapticFeedback()) {
                         doCancelVibrateLocked();
                     }
-
-                    // Clear all remaining vibrations.
-                    Iterator<Vibration> it = mVibrations.iterator();
-                    while (it.hasNext()) {
-                        Vibration vibration = it.next();
-                        if (vibration != mCurrentVibration) {
-                            unlinkVibration(vibration);
-                            it.remove();
-                        }
-                    }
                 }
             }
         }
@@ -788,7 +883,7 @@
             return;
         }
         pw.println("Previous vibrations:");
-        synchronized (mVibrations) {
+        synchronized (mLock) {
             for (VibrationInfo info : mPreviousVibrations) {
                 pw.print("  ");
                 pw.println(info.toString());
@@ -830,7 +925,10 @@
             if (description == null) {
                 description = "Shell command";
             }
-            vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+            VibrationEffect effect =
+                    VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+            vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
                     mToken);
             return 0;
         }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index be021ea..ce4ca02 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -71,7 +71,6 @@
         "/system/bin/mediaserver",
         "/system/bin/sdcard",
         "/system/bin/surfaceflinger",
-        "media.codec",     // system/bin/mediacodec
         "media.extractor", // system/bin/mediaextractor
         "com.android.bluetooth",  // Bluetooth service
     };
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f954f75..1a7f016 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -119,6 +119,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -210,10 +211,11 @@
         /** protected by the {@link #cacheLock} */
         private final TokenCache accountTokenCaches = new TokenCache();
 
-        /** protected by the {@link #cacheLock} */
-        // TODO use callback to set up the map.
-        private final Map<String, LinkedHashSet<String>> mApplicationAccountRequestMappings =
-                new HashMap<>();
+        /** protected by the {@link #mReceiversForType}
+         *  type -> (packageName -> number of active receivers)
+         *  type == null is used to get notifications about all account types
+         */
+        private final Map<String, Map<String, Integer>> mReceiversForType = new HashMap<>();
 
         /**
          * protected by the {@link #cacheLock}
@@ -512,7 +514,7 @@
 
     @Override
     public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) {
-        if (account == null) throw new IllegalArgumentException("account is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
         int callingUid = Binder.getCallingUid();
         int userId = UserHandle.getUserId(callingUid);
         UserAccounts accounts = getUserAccounts(userId);
@@ -541,23 +543,23 @@
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
         }
-
     }
 
     @Override
-    public int getAccountVisibility(Account a, String packageName) {
-        if (a == null) throw new IllegalArgumentException("account is null");
+    public int getAccountVisibility(Account account, String packageName) {
+        Preconditions.checkNotNull(account, "account cannot be null");
+        Preconditions.checkNotNull(packageName, "packageName cannot be null");
         int callingUid = Binder.getCallingUid();
-        if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+        if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
             && !isSystemUid(callingUid)) {
             String msg = String.format(
                     "uid %s cannot get secrets for accounts of type: %s",
                     callingUid,
-                    a.type);
+                    account.type);
             throw new SecurityException(msg);
         }
-        return resolveAccountVisibility(a, packageName,
-                getUserAccounts(UserHandle.getUserId(callingUid)));
+        return resolveAccountVisibility(account, packageName, accounts);
     }
 
     /**
@@ -573,7 +575,8 @@
     private int getAccountVisibility(Account account, String packageName, UserAccounts accounts) {
         final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
         try {
-            Integer visibility = accounts.accountsDb.findAccountVisibility(account, packageName);
+            Integer visibility =
+                accounts.accountsDb.findAccountVisibility(account, packageName);
             return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
@@ -592,6 +595,7 @@
      */
     private Integer resolveAccountVisibility(Account account, @NonNull String packageName,
             UserAccounts accounts) {
+
         Preconditions.checkNotNull(packageName, "packageName cannot be null");
 
         int uid = -1;
@@ -693,19 +697,21 @@
     }
 
     @Override
-    public boolean setAccountVisibility(Account a, String packageName, int newVisibility) {
-        if (a == null) throw new IllegalArgumentException("account is null");
+    public boolean setAccountVisibility(Account account, String packageName, int newVisibility) {
+        Preconditions.checkNotNull(account, "account cannot be null");
+        Preconditions.checkNotNull(packageName, "packageName cannot be null");
         int callingUid = Binder.getCallingUid();
-        if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+        if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
             && !isSystemUid(callingUid)) {
             String msg = String.format(
                     "uid %s cannot get secrets for accounts of type: %s",
                     callingUid,
-                    a.type);
+                    account.type);
             throw new SecurityException(msg);
         }
-        return setAccountVisibility(a, packageName, newVisibility, true /* notify */,
-                getUserAccounts(UserHandle.getUserId(callingUid)));
+        return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
+                accounts);
     }
 
     /**
@@ -722,16 +728,18 @@
     private boolean setAccountVisibility(Account account, String packageName, int newVisibility,
             boolean notify, UserAccounts accounts) {
         synchronized (accounts.cacheLock) {
-            LinkedHashSet<String> interestedPackages;
+            Map<String, Integer> packagesToVisibility;
             if (notify) {
                 if (isSpecialPackageKey(packageName)) {
-                    interestedPackages = getRequestingPackageNames(account.type, accounts);
+                    packagesToVisibility =
+                        getRequestingPackages(account, accounts);
                 } else {
                     if (!packageExistsForUser(packageName, accounts.userId)) {
                         return false; // package is not installed.
                     }
-                    interestedPackages = new LinkedHashSet<>();
-                    interestedPackages.add(packageName);
+                    packagesToVisibility = new HashMap<>();
+                    packagesToVisibility.put(packageName,
+                        resolveAccountVisibility(account, packageName, accounts));
                 }
             } else {
                 // Notifications will not be send.
@@ -740,19 +748,13 @@
                     // package is not installed and not meta value.
                     return false;
                 }
-                interestedPackages = new LinkedHashSet<>();
+                packagesToVisibility = new HashMap<>();
             }
-            Integer[] interestedPackagesVisibility = new Integer[interestedPackages.size()];
 
             final long accountId = accounts.accountsDb.findDeAccountId(account);
             if (accountId < 0) {
                 return false;
             }
-            int index = 0;
-            for (String interestedPackage : interestedPackages) {
-                interestedPackagesVisibility[index++] =
-                        resolveAccountVisibility(account, interestedPackage, accounts);
-            }
 
             final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
             try {
@@ -764,45 +766,113 @@
                 StrictMode.setThreadPolicy(oldPolicy);
             }
 
-            index = 0;
-            for (String interestedPackage : interestedPackages) {
-                int visibility = resolveAccountVisibility(account, interestedPackage, accounts);
-                if (visibility != interestedPackagesVisibility[index++]) {
-                        sendNotification(interestedPackage, account, accounts.userId);
-                }
-            }
             if (notify) {
+                for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+                    if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                        notifyPackage(packageToVisibility.getKey(), accounts);
+                    }
+                }
                 sendAccountsChangedBroadcast(accounts.userId);
             }
             return true;
         }
     }
 
-    /**
-     * Sends a direct intent to a package, notifying it of a visible account change.
-     *
-     * @param packageName to send Account to
-     * @param account to send to package
-     * @param userId User
-     */
-    private void sendNotification(String packageName, Account account, int userId) {
-        // TODO send notification so apps subscribed in runtime.
+    @Override
+    public void registerAccountListener(String[] accountTypes, String opPackageName) {
+        int callingUid = Binder.getCallingUid();
+        mAppOpsManager.checkPackage(callingUid, opPackageName);
+        registerAccountListener(accountTypes, opPackageName,
+            getUserAccounts(UserHandle.getUserId(callingUid)));
     }
 
-    private void sendNotification(Account account, UserAccounts accounts) {
-        LinkedHashSet<String> interestedPackages = getRequestingPackageNames(account.type,
-                accounts);
-        for (String packageName : interestedPackages) {
-            int visibility = resolveAccountVisibility(account, packageName, accounts);
-            if (visibility != AccountManager.VISIBILITY_NOT_VISIBLE) {
-                sendNotification(packageName, account, accounts.userId);
+    private void registerAccountListener(String[] accountTypes, String opPackageName,
+            UserAccounts accounts) {
+        synchronized (accounts.mReceiversForType) {
+            if (accountTypes == null) {
+                // null for any type
+                accountTypes = new String[] {null};
+            }
+            for (String type : accountTypes) {
+                Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+                if (receivers == null) {
+                    receivers = new HashMap<>();
+                    accounts.mReceiversForType.put(type, receivers);
+                }
+                Integer cnt = receivers.get(opPackageName);
+                receivers.put(opPackageName, cnt != null ? cnt + 1 : 1);
             }
         }
     }
 
-    LinkedHashSet<String> getRequestingPackageNames(String accountType, UserAccounts accounts) {
-        // TODO return packages registered to get notifications.
-        return new LinkedHashSet<String>();
+    @Override
+    public void unregisterAccountListener(String[] accountTypes, String opPackageName) {
+        int callingUid = Binder.getCallingUid();
+        mAppOpsManager.checkPackage(callingUid, opPackageName);
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+        synchronized (accounts.mReceiversForType) {
+            if (accountTypes == null) {
+                // null for any type
+                accountTypes = new String[] {null};
+            }
+            for (String type : accountTypes) {
+                Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+                if (receivers == null || receivers.get(opPackageName) == null) {
+                    throw new IllegalArgumentException("attempt to unregister wrong receiver");
+                }
+                Integer cnt = receivers.get(opPackageName);
+                if (cnt == 1) {
+                    receivers.remove(opPackageName);
+                } else {
+                    receivers.put(opPackageName, cnt - 1);
+                }
+            }
+        }
+    }
+
+    // Send notification to all packages which can potentially see the account
+    private void sendNotificationAccountUpdated(Account account, UserAccounts accounts) {
+        Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
+        // packages with VISIBILITY_USER_MANAGED_NOT_VISIBL still get notification.
+        // Should we notify VISIBILITY_NOT_VISIBLE packages when account is added?
+        for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+            if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                notifyPackage(packageToVisibility.getKey(), accounts);
+            }
+        }
+    }
+
+    /**
+     * Sends a direct intent to a package, notifying it of account visibility change.
+     *
+     * @param packageName to send Account to
+     * @param accounts UserAccount that currently hosts the account
+     */
+    private void notifyPackage(String packageName, UserAccounts accounts) {
+        Intent intent = new Intent(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
+        intent.setPackage(packageName);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(intent, new UserHandle(accounts.userId));
+    }
+
+    // Returns a map from package name to visibility, for packages subscribed
+    // to notifications about any account type, or type of provided account
+    // account type or all types.
+    private Map<String, Integer> getRequestingPackages(Account account, UserAccounts accounts) {
+        Set<String> packages = new HashSet<>();
+        synchronized (accounts.mReceiversForType) {
+            for (String type : new String[] {account.type, null}) {
+                Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+                if (receivers != null) {
+                    packages.addAll(receivers.keySet());
+                }
+            }
+        }
+        Map<String, Integer> result = new HashMap<>();
+        for (String packageName : packages) {
+            result.put(packageName, resolveAccountVisibility(account, packageName, accounts));
+        }
+        return result;
     }
 
     private boolean packageExistsForUser(String packageName, int userId) {
@@ -950,6 +1020,8 @@
                     if (obsoleteAuthType.contains(account.type)) {
                         Slog.w(TAG, "deleting account " + account.name + " because type "
                                 + account.type + "'s registered authenticator no longer exist.");
+                        Map<String, Integer> packagesToVisibility =
+                            getRequestingPackages(account, accounts);
                         accountsDb.beginTransaction();
                         try {
                             accountsDb.deleteDeAccount(accountId);
@@ -970,12 +1042,12 @@
                         accounts.userDataCache.remove(account);
                         accounts.authTokenCache.remove(account);
                         accounts.accountTokenCaches.remove(account);
-                        LinkedHashSet<String> interestedPackages =
-                                getRequestingPackageNames(account.type, accounts);
-                        for (String packageName : interestedPackages) {
-                            sendNotification(packageName, null, accounts.userId);
-                        }
 
+                        for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+                            if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                                notifyPackage(packageToVisibility.getKey(), accounts);
+                            }
+                        }
                     } else {
                         ArrayList<String> accountNames = accountNamesByType.get(account.type);
                         if (accountNames == null) {
@@ -1224,7 +1296,7 @@
                     + ", caller's uid " + Binder.getCallingUid()
                     + ", pid " + Binder.getCallingPid());
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
         int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
@@ -1260,8 +1332,8 @@
                     account, key, callingUid, Binder.getCallingPid());
             Log.v(TAG, msg);
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
-        if (key == null) throw new IllegalArgumentException("key is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
+        Preconditions.checkNotNull(key, "key cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -1414,9 +1486,7 @@
                     callingUid);
             Log.v(TAG, msg);
         }
-        if (account == null) {
-            throw new IllegalArgumentException("account is null");
-        }
+        Preconditions.checkNotNull(account, "account cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -1564,7 +1634,7 @@
             addAccountToLinkedRestrictedUsers(account, accounts.userId);
         }
 
-        sendNotification(account, accounts);
+        sendNotificationAccountUpdated(account, accounts);
         // Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
         sendAccountsChangedBroadcast(accounts.userId);
 
@@ -1608,9 +1678,9 @@
                     + ", caller's uid " + callingUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (response == null) throw new IllegalArgumentException("response is null");
-        if (account == null) throw new IllegalArgumentException("account is null");
-        if (features == null) throw new IllegalArgumentException("features is null");
+        Preconditions.checkArgument(account != null, "account cannot be null");
+        Preconditions.checkArgument(response != null, "response cannot be null");
+        Preconditions.checkArgument(features != null, "features cannot be null");
         int userId = UserHandle.getCallingUserId();
         checkReadAccountsPermitted(callingUid, account.type, userId,
                 opPackageName);
@@ -1806,8 +1876,7 @@
                 }
             }
 
-            // Notify authenticator.
-            sendNotification(resultAccount, accounts);
+            sendNotificationAccountUpdated(resultAccount, accounts);
             sendAccountsChangedBroadcast(accounts.userId);
         }
         return resultAccount;
@@ -1839,8 +1908,9 @@
                     + ", pid " + Binder.getCallingPid()
                     + ", for user id " + userId);
         }
-        if (response == null) throw new IllegalArgumentException("response is null");
-        if (account == null) throw new IllegalArgumentException("account is null");
+        Preconditions.checkArgument(account != null, "account cannot be null");
+        Preconditions.checkArgument(response != null, "response cannot be null");
+
         // Only allow the system process to modify accounts of other users
         if (isCrossUser(callingUid, userId)) {
             throw new SecurityException(
@@ -2006,17 +2076,7 @@
                     + " is still locked. CE data will be removed later");
         }
         synchronized (accounts.cacheLock) {
-            LinkedHashSet<String> interestedPackages =
-                    accounts.mApplicationAccountRequestMappings.get(account.type);
-            if (interestedPackages == null) {
-                interestedPackages = new LinkedHashSet<>();
-            }
-            int[] visibilityForInterestedPackages = new int[interestedPackages.size()];
-            int index = 0;
-            for (String packageName : interestedPackages) {
-                int visibility = resolveAccountVisibility(account, packageName, accounts);
-                visibilityForInterestedPackages[index++] = visibility;
-            }
+            Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
             accounts.accountsDb.beginTransaction();
             // Set to a dummy value, this will only be used if the database
             // transaction succeeds.
@@ -2040,18 +2100,13 @@
             }
             if (isChanged) {
                 removeAccountFromCacheLocked(accounts, account);
-                index = 0;
-                for (String packageName : interestedPackages) {
-                    if ((visibilityForInterestedPackages[index]
-                            != AccountManager.VISIBILITY_NOT_VISIBLE)
-                            && (visibilityForInterestedPackages[index]
-                                    != AccountManager.VISIBILITY_UNDEFINED)) {
-                        sendNotification(packageName, account, accounts.userId);
+                for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+                    if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                        notifyPackage(packageToVisibility.getKey(), accounts);
                     }
-                    ++index;
                 }
 
-                // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occured.
+                // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
                 sendAccountsChangedBroadcast(accounts.userId);
                 String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
                         : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
@@ -2094,13 +2149,13 @@
     @Override
     public void invalidateAuthToken(String accountType, String authToken) {
         int callerUid = Binder.getCallingUid();
+        Preconditions.checkNotNull(accountType, "accountType cannot be null");
+        Preconditions.checkNotNull(authToken, "authToken cannot be null");
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "invalidateAuthToken: accountType " + accountType
                     + ", caller's uid " + callerUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (accountType == null) throw new IllegalArgumentException("accountType is null");
-        if (authToken == null) throw new IllegalArgumentException("authToken is null");
         int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
@@ -2210,8 +2265,8 @@
                     + ", caller's uid " + callingUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
-        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
+        Preconditions.checkNotNull(authTokenType, "authTokenType cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -2243,8 +2298,8 @@
                     + ", caller's uid " + callingUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
-        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
+        Preconditions.checkNotNull(authTokenType, "authTokenType cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -2270,7 +2325,7 @@
                     + ", caller's uid " + callingUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -2317,7 +2372,7 @@
                 accounts.accountsDb.endTransaction();
                 if (isChanged) {
                     // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
-                    sendNotification(account, accounts);
+                    sendNotificationAccountUpdated(account, accounts);
                     sendAccountsChangedBroadcast(accounts.userId);
                 }
             }
@@ -2332,7 +2387,7 @@
                     + ", caller's uid " + callingUid
                     + ", pid " + Binder.getCallingPid());
         }
-        if (account == null) throw new IllegalArgumentException("account is null");
+        Preconditions.checkNotNull(account, "account cannot be null");
         int userId = UserHandle.getCallingUserId();
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
             String msg = String.format(
@@ -2444,8 +2499,8 @@
     public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
                                   final String authTokenType)
             throws RemoteException {
-        if (accountType == null) throw new IllegalArgumentException("accountType is null");
-        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        Preconditions.checkArgument(accountType != null, "accountType cannot be null");
+        Preconditions.checkArgument(authTokenType != null, "authTokenType cannot be null");
 
         final int callingUid = getCallingUid();
         clearCallingIdentity();
@@ -2508,7 +2563,7 @@
                     + ", caller's uid " + Binder.getCallingUid()
                     + ", pid " + Binder.getCallingPid());
         }
-        if (response == null) throw new IllegalArgumentException("response is null");
+        Preconditions.checkArgument(response != null, "response cannot be null");
         try {
             if (account == null) {
                 Slog.w(TAG, "getAuthToken called with null account");
@@ -2909,8 +2964,8 @@
                     + ", pid " + Binder.getCallingPid()
                     + ", for user id " + userId);
         }
-        if (response == null) throw new IllegalArgumentException("response is null");
-        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        Preconditions.checkArgument(response != null, "response cannot be null");
+        Preconditions.checkArgument(accountType != null, "accountType cannot be null");
         // Only allow the system process to add accounts of other users
         if (isCrossUser(callingUid, userId)) {
             throw new SecurityException(
@@ -2996,12 +3051,8 @@
                     + ", caller's uid " + Binder.getCallingUid()
                     + ", pid " + Binder.getCallingPid());
         }
-        if (response == null) {
-            throw new IllegalArgumentException("response is null");
-        }
-        if (accountType == null) {
-            throw new IllegalArgumentException("accountType is null");
-        }
+        Preconditions.checkArgument(response != null, "response cannot be null");
+        Preconditions.checkArgument(accountType != null, "accountType cannot be null");
 
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
@@ -3190,10 +3241,7 @@
                             + ", pid " + Binder.getCallingPid()
                             + ", for user id " + userId);
         }
-        if (response == null) {
-            throw new IllegalArgumentException("response is null");
-        }
-
+        Preconditions.checkArgument(response != null, "response cannot be null");
         // Session bundle is the encrypted bundle of the original bundle created by authenticator.
         // Account type is added to it before encryption.
         if (sessionBundle == null || sessionBundle.size() == 0) {
@@ -3690,9 +3738,9 @@
         // the account to be accessed by apps for which user or authenticator granted visibility.
 
         int visibility = resolveAccountVisibility(account, packageName,
-                getUserAccounts(UserHandle.getUserId(uid)));
+            getUserAccounts(UserHandle.getUserId(uid)));
         return (visibility == AccountManager.VISIBILITY_VISIBLE
-                || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+            || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 71d7b0f..05a59d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -298,6 +298,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.service.voice.VoiceInteractionSession;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -370,7 +371,6 @@
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.vr.PersistentVrStateListener;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.WindowManagerService;
 
@@ -573,6 +573,29 @@
     // Determines whether to take full screen screenshots
     static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
 
+    /**
+     * Indicates the maximum time spent waiting for the network rules to get updated.
+     */
+    private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+
+    /**
+     * State indicating that there is no need for any blocking for network.
+     */
+    @VisibleForTesting
+    static final int NETWORK_STATE_NO_CHANGE = 0;
+
+    /**
+     * State indicating that the main thread needs to be informed about the network wait.
+     */
+    @VisibleForTesting
+    static final int NETWORK_STATE_BLOCK = 1;
+
+    /**
+     * State indicating that any threads waiting for network state to get updated can be unblocked.
+     */
+    @VisibleForTesting
+    static final int NETWORK_STATE_UNBLOCK = 2;
+
     /** All system services */
     SystemServiceManager mSystemServiceManager;
     AssistUtils mAssistUtils;
@@ -604,8 +627,8 @@
     private int mVrState = NON_VR_MODE;
     private int mTopAppVrThreadTid = 0;
     private int mPersistentVrThreadTid = 0;
-    final PersistentVrStateListener mPersistentVrModeListener =
-            new PersistentVrStateListener() {
+    final IPersistentVrStateCallbacks mPersistentVrModeListener =
+            new IPersistentVrStateCallbacks.Stub() {
         @Override
         public void onPersistentVrStateChanged(boolean enabled) {
             synchronized(ActivityManagerService.this) {
@@ -1162,13 +1185,13 @@
 
         @Override
         public String toString() {
-            String result = Integer.toString(sourceUserId) + " @ " + uri.toString();
+            String result = uri.toString() + " [user " + sourceUserId + "]";
             if (prefix) result += " [prefix]";
             return result;
         }
 
         public String toSafeString() {
-            String result = Integer.toString(sourceUserId) + " @ " + uri.toSafeString();
+            String result = uri.toSafeString() + " [user " + sourceUserId + "]";
             if (prefix) result += " [prefix]";
             return result;
         }
@@ -1532,6 +1555,8 @@
     @VisibleForTesting
     long mProcStateSeqCounter = 0;
 
+    private final Injector mInjector;
+
     static final class ProcessChangeItem {
         static final int CHANGE_ACTIVITIES = 1<<0;
         int changes;
@@ -2707,10 +2732,11 @@
 
     @VisibleForTesting
     public ActivityManagerService(Injector injector) {
+        mInjector = injector;
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
-        mAppOpsService = injector.getAppOpsService();
+        mAppOpsService = mInjector.getAppOpsService(null, null);
         mBatteryStatsService = null;
         mCompatModePackages = null;
         mConstants = null;
@@ -2728,7 +2754,7 @@
         mStackSupervisor = null;
         mSystemThread = null;
         mTaskChangeNotificationController = null;
-        mUiHandler = injector.getHandler();
+        mUiHandler = injector.getUiHandler(null);
         mUserController = null;
     }
 
@@ -2736,6 +2762,7 @@
     // handlers to other threads.  So take care to be explicit about the looper.
     public ActivityManagerService(Context systemContext) {
         LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY);
+        mInjector = new Injector();
         mContext = systemContext;
         mFactoryTest = FactoryTest.getMode();
         mSystemThread = ActivityThread.currentActivityThread();
@@ -2749,7 +2776,7 @@
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
         mHandlerThread.start();
         mHandler = new MainHandler(mHandlerThread.getLooper());
-        mUiHandler = new UiHandler();
+        mUiHandler = mInjector.getUiHandler(this);
 
         mConstants = new ActivityManagerConstants(this, mHandler);
 
@@ -2796,7 +2823,7 @@
 
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
-        mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+        mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops.xml"), mHandler);
         mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
                 new IAppOpsCallback.Stub() {
                     @Override public void opChanged(int op, int uid, String packageName) {
@@ -5455,37 +5482,61 @@
         return tracesFile;
     }
 
+    public static class DumpStackFileObserver extends FileObserver {
+        // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+        private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+        static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+        private final String mTracesPath;
+        private boolean mClosed;
+
+        public DumpStackFileObserver(String tracesPath) {
+            super(tracesPath, FileObserver.CLOSE_WRITE);
+            mTracesPath = tracesPath;
+        }
+
+        @Override
+        public synchronized void onEvent(int event, String path) {
+            mClosed = true;
+            notify();
+        }
+
+        public void dumpWithTimeout(int pid) {
+            Process.sendSignal(pid, Process.SIGNAL_QUIT);
+            synchronized (this) {
+                try {
+                    wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+                } catch (InterruptedException e) {
+                    Slog.wtf(TAG, e);
+                }
+            }
+            if (!mClosed) {
+                Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+                       ". Attempting native stack collection.");
+                Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+            }
+            mClosed = false;
+        }
+    }
+
     private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
-        final boolean[] closed = new boolean[1];
-        FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
-            @Override
-            public synchronized void onEvent(int event, String path) { closed[0] = true; notify(); }
-        };
-
+        DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
         try {
             observer.startWatching();
 
             // First collect all of the stacks of the most important pids.
             if (firstPids != null) {
-                try {
-                    int num = firstPids.size();
-                    for (int i = 0; i < num; i++) {
-                        synchronized (observer) {
-                            if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
-                                    + firstPids.get(i));
-                            final long sime = SystemClock.elapsedRealtime();
-                            Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
-                            observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                            if (!closed[0]) Slog.w(TAG, "Didn't see close of " + tracesPath);
-                            if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
-                                    + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
-                        }
-                    }
-                } catch (InterruptedException e) {
-                    Slog.wtf(TAG, e);
+                int num = firstPids.size();
+                for (int i = 0; i < num; i++) {
+                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+                            + firstPids.get(i));
+                    final long sime = SystemClock.elapsedRealtime();
+                    observer.dumpWithTimeout(firstPids.get(i));
+                    if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+                            + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                 }
             }
 
@@ -5497,7 +5548,8 @@
                         if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
                         final long sime = SystemClock.elapsedRealtime();
 
-                        Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+                        Debug.dumpNativeBacktraceToFileTimeout(
+                                pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
                         if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
                                 + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                     }
@@ -5524,19 +5576,12 @@
                     ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                     if (lastPids.indexOfKey(stats.pid) >= 0) {
                         numProcs++;
-                        try {
-                            synchronized (observer) {
-                                if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
-                                        + stats.pid);
-                                final long stime = SystemClock.elapsedRealtime();
-                                Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
-                                observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                                if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
-                                        + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
-                            }
-                        } catch (InterruptedException e) {
-                            Slog.wtf(TAG, e);
-                        }
+                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+                                + stats.pid);
+                        final long stime = SystemClock.elapsedRealtime();
+                        observer.dumpWithTimeout(stats.pid);
+                        if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+                                + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
                     } else if (DEBUG_ANR) {
                         Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
                                 + stats.pid);
@@ -8626,8 +8671,15 @@
         if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
             // Require they hold a strong enough Uri permission
             if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) {
-                throw new SecurityException("Uid " + callingUid
-                        + " does not have permission to uri " + grantUri);
+                if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
+                    throw new SecurityException(
+                            "UID " + callingUid + " does not have permission to " + grantUri
+                                    + "; you could obtain access using ACTION_OPEN_DOCUMENT "
+                                    + "or related APIs");
+                } else {
+                    throw new SecurityException(
+                            "UID " + callingUid + " does not have permission to " + grantUri);
+                }
             }
         }
         return targetUid;
@@ -10001,7 +10053,7 @@
     }
 
     @Override
-    public TaskSnapshot getTaskSnapshot(int taskId) {
+    public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
         enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -10015,7 +10067,7 @@
                 }
             }
             // Don't call this while holding the lock as this operation might hit the disk.
-            return task.getSnapshot();
+            return task.getSnapshot(reducedResolution);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -10882,7 +10934,8 @@
         } catch (RemoteException ignored) {
         }
         if (cpi == null) {
-            return "Failed to find provider " + authority + " for user " + userId;
+            return "Failed to find provider " + authority + " for user " + userId
+                    + "; expected to find a valid ContentProvider for this authority";
         }
 
         ProcessRecord r = null;
@@ -10962,18 +11015,17 @@
             return null;
         }
 
-        String msg;
+        final String suffix;
         if (!cpi.exported) {
-            msg = "Permission Denial: opening provider " + cpi.name
-                    + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
-                    + ", uid=" + callingUid + ") that is not exported from uid "
-                    + cpi.applicationInfo.uid;
+            suffix = " that is not exported from UID " + cpi.applicationInfo.uid;
+        } else if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(cpi.readPermission)) {
+            suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
         } else {
-            msg = "Permission Denial: opening provider " + cpi.name
-                    + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
-                    + ", uid=" + callingUid + ") requires "
-                    + cpi.readPermission + " or " + cpi.writePermission;
+            suffix = " requires " + cpi.readPermission + " or " + cpi.writePermission;
         }
+        final String msg = "Permission Denial: opening provider " + cpi.name
+                + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+                + ", uid=" + callingUid + ")" + suffix;
         Slog.w(TAG, msg);
         return msg;
     }
@@ -22052,9 +22104,7 @@
             }
         }
 
-        for (int i = mActiveUids.size() - 1; i >= 0; --i) {
-            incrementProcStateSeqIfNeeded(mActiveUids.valueAt(i));
-        }
+        incrementProcStateSeqAndNotifyAppsLocked();
 
         mNumServiceProcs = mNewNumServiceProcs;
 
@@ -22406,39 +22456,103 @@
     }
 
     /**
-     * If {@link UidRecord#curProcStateSeq} needs to be updated, then increments the global seq
-     * counter {@link #mProcStateSeqCounter} and uses that value for {@param uidRec}.
+     * Checks if any uid is coming from background to foreground or vice versa and if so, increments
+     * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
+     * {@link #mProcStateSeqCounter} and notifies the app if it needs to block.
      */
     @VisibleForTesting
-    void incrementProcStateSeqIfNeeded(UidRecord uidRec) {
-        if (uidRec.curProcState != uidRec.setProcState && shouldIncrementProcStateSeq(uidRec)) {
-            uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+    @GuardedBy("this")
+    void incrementProcStateSeqAndNotifyAppsLocked() {
+        // Used for identifying which uids need to block for network.
+        ArrayList<Integer> blockingUids = null;
+        for (int i = mActiveUids.size() - 1; i >= 0; --i) {
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            // If the network is not restricted for uid, then nothing to do here.
+            if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
+                continue;
+            }
+            // If process state is not changed, then there's nothing to do.
+            if (uidRec.setProcState == uidRec.curProcState) {
+                continue;
+            }
+            final int blockState = getBlockStateForUid(uidRec);
+            // No need to inform the app when the blockState is NETWORK_STATE_NO_CHANGE as
+            // there's nothing the app needs to do in this scenario.
+            if (blockState == NETWORK_STATE_NO_CHANGE) {
+                continue;
+            }
+            synchronized (uidRec.lock) {
+                uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+                if (blockState == NETWORK_STATE_BLOCK) {
+                    if (blockingUids == null) {
+                        blockingUids = new ArrayList<>();
+                    }
+                    blockingUids.add(uidRec.uid);
+                } else {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
+                                + " threads for uid: " + uidRec);
+                    }
+                    if (uidRec.waitingForNetwork) {
+                        uidRec.lock.notifyAll();
+                    }
+                }
+            }
+        }
+
+        // There are no uids that need to block, so nothing more to do.
+        if (blockingUids == null) {
+            return;
+        }
+
+        for (int i = mLruProcesses.size() - 1; i >= 0; --i) {
+            final ProcessRecord app = mLruProcesses.get(i);
+            if (!blockingUids.contains(app.uid)) {
+                continue;
+            }
+            if (!app.killedByAm && app.thread != null) {
+                final UidRecord uidRec = mActiveUids.get(app.uid);
+                try {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
+                                + uidRec);
+                    }
+                    app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+                } catch (RemoteException ignored) {
+                }
+            }
         }
     }
 
     /**
-     * Checks if {@link UidRecord#curProcStateSeq} needs to be incremented depending on whether
-     * the uid is coming from background to foreground state or vice versa.
+     * Checks if the uid is coming from background to foreground or vice versa and returns
+     * appropriate block state based on this.
      *
-     * @return Returns true if the uid is coming from background to foreground state or vice versa,
-     *                 false otherwise.
+     * @return blockState based on whether the uid is coming from background to foreground or
+     *         vice versa. If bg->fg or fg->bg, then {@link #NETWORK_STATE_BLOCK} or
+     *         {@link #NETWORK_STATE_UNBLOCK} respectively, otherwise
+     *         {@link #NETWORK_STATE_NO_CHANGE}.
      */
     @VisibleForTesting
-    boolean shouldIncrementProcStateSeq(UidRecord uidRec) {
-        final boolean isAllowedOnRestrictBackground
-                = isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
-        final boolean isAllowedOnDeviceIdleOrPowerSaveMode
-                = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState);
+    int getBlockStateForUid(UidRecord uidRec) {
+        // Denotes whether uid's process state is currently allowed network access.
+        final boolean isAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState)
+                || isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
+        // Denotes whether uid's process state was previously allowed network access.
+        final boolean wasAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState)
+                || isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
 
-        final boolean wasAllowedOnRestrictBackground
-                = isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
-        final boolean wasAllowedOnDeviceIdleOrPowerSaveMode
-                = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState);
-
-        // If the uid is coming from background to foreground or vice versa,
-        // then return true. Otherwise false.
-        return (wasAllowedOnDeviceIdleOrPowerSaveMode != isAllowedOnDeviceIdleOrPowerSaveMode)
-                || (wasAllowedOnRestrictBackground != isAllowedOnRestrictBackground);
+        // When the uid is coming to foreground, AMS should inform the app thread that it should
+        // block for the network rules to get updated before launching an activity.
+        if (!wasAllowed && isAllowed) {
+            return NETWORK_STATE_BLOCK;
+        }
+        // When the uid is going to background, AMS should inform the app thread that if an
+        // activity launch is blocked for the network rules to get updated, it should be unblocked.
+        if (wasAllowed && !isAllowed) {
+            return NETWORK_STATE_UNBLOCK;
+        }
+        return NETWORK_STATE_NO_CHANGE;
     }
 
     final void runInBackgroundDisabled(int uid) {
@@ -23290,8 +23404,8 @@
 
         /**
          * Called after the network policy rules are updated by
-         * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
-         * {@param procStateSeq}.
+         * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid}
+         * and {@param procStateSeq}.
          */
         @Override
         public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
@@ -23299,8 +23413,9 @@
                 Slog.d(TAG_NETWORK, "Got update from NPMS for uid: "
                         + uid + " seq: " + procStateSeq);
             }
+            UidRecord record;
             synchronized (ActivityManagerService.this) {
-                final UidRecord record = mActiveUids.get(uid);
+                record = mActiveUids.get(uid);
                 if (record == null) {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
@@ -23308,7 +23423,98 @@
                     }
                     return;
                 }
+            }
+            synchronized (record.lock) {
+                if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "procStateSeq: " + procStateSeq + " has already"
+                                + " been handled for uid: " + uid);
+                    }
+                    return;
+                }
                 record.lastNetworkUpdatedProcStateSeq = procStateSeq;
+                if (record.curProcStateSeq > procStateSeq) {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "No need to handle older seq no., Uid: " + uid
+                                + ", curProcstateSeq: " + record.curProcStateSeq
+                                + ", procStateSeq: " + procStateSeq);
+                    }
+                    return;
+                }
+                if (record.waitingForNetwork) {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "Notifying all blocking threads for uid: " + uid
+                                + ", procStateSeq: " + procStateSeq);
+                    }
+                    record.lock.notifyAll();
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by app main thread to wait for the network policy rules to get udpated.
+     *
+     * @param procStateSeq The sequence number indicating the process state change that the main
+     *                     thread is interested in.
+     */
+    @Override
+    public void waitForNetworkStateUpdate(long procStateSeq) {
+        final int callingUid = Binder.getCallingUid();
+        if (DEBUG_NETWORK) {
+            Slog.d(TAG_NETWORK, "Called from " + callingUid + " to wait for seq: " + procStateSeq);
+        }
+        UidRecord record;
+        synchronized (this) {
+            record = mActiveUids.get(callingUid);
+            if (record == null) {
+                return;
+            }
+        }
+        synchronized (record.lock) {
+            if (record.lastDispatchedProcStateSeq < procStateSeq) {
+                if (DEBUG_NETWORK) {
+                    Slog.d(TAG_NETWORK, "Uid state change for seq no. " + procStateSeq + " is not "
+                            + "dispatched to NPMS yet, so don't wait. Uid: " + callingUid
+                            + " lastProcStateSeqDispatchedToObservers: "
+                            + record.lastDispatchedProcStateSeq);
+                }
+                return;
+            }
+            if (record.curProcStateSeq > procStateSeq) {
+                if (DEBUG_NETWORK) {
+                    Slog.d(TAG_NETWORK, "Ignore the wait requests for older seq numbers. Uid: "
+                            + callingUid + ", curProcStateSeq: " + record.curProcStateSeq
+                            + ", procStateSeq: " + procStateSeq);
+                }
+                return;
+            }
+            if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+                if (DEBUG_NETWORK) {
+                    Slog.d(TAG_NETWORK, "Network rules have been already updated for seq no. "
+                            + procStateSeq + ", so no need to wait. Uid: "
+                            + callingUid + ", lastProcStateSeqWithUpdatedNetworkState: "
+                            + record.lastNetworkUpdatedProcStateSeq);
+                }
+                return;
+            }
+            try {
+                if (DEBUG_NETWORK) {
+                    Slog.d(TAG_NETWORK, "Starting to wait for the network rules update."
+                        + " Uid: " + callingUid + " procStateSeq: " + procStateSeq);
+                }
+                final long startTime = SystemClock.uptimeMillis();
+                record.waitingForNetwork = true;
+                record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+                record.waitingForNetwork = false;
+                final long totalTime = SystemClock.uptimeMillis() - startTime;
+                if (DEBUG_NETWORK ||  totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+                    Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
+                            + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+                            + procStateSeq);
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
             }
         }
     }
@@ -23595,8 +23801,19 @@
         }
     }
 
-    static interface Injector {
-        public AppOpsService getAppOpsService();
-        public Handler getHandler();
+    @VisibleForTesting
+    public static class Injector {
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return new AppOpsService(file, handler);
+        }
+
+        public Handler getUiHandler(ActivityManagerService service) {
+            return service.new UiHandler();
+        }
+
+        public boolean isNetworkRestrictedForUid(int uid) {
+            // TODO: add implementation
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index dd8c05e..04a09fe 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -321,6 +321,7 @@
             if (info.launchedActivity.info.launchToken != null) {
                 builder.addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN,
                         info.launchedActivity.info.launchToken);
+                info.launchedActivity.info.launchToken = null;
             }
             builder.addTaggedData(APP_TRANSITION_IS_EPHEMERAL,
                     info.launchedActivity.info.applicationInfo.isInstantApp() ? 1 : 0);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 7868fdf..b9bb106 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2391,6 +2391,10 @@
         return (config.uiMode & Configuration.UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
     }
 
+    int getUid() {
+        return info.applicationInfo.uid;
+    }
+
     @Override
     public String toString() {
         if (stringName != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e64b4b3..9a4f804 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -741,8 +741,10 @@
     /** Checks if there are tasks with specific UID in the stack. */
     boolean isUidPresent(int uid) {
         for (TaskRecord task : mTaskHistory) {
-            if (task.effectiveUid == uid) {
-                return true;
+            for (ActivityRecord r : task.mActivities) {
+                if (r.getUid() == uid) {
+                    return true;
+                }
             }
         }
         return false;
@@ -751,7 +753,9 @@
     /** Get all UIDs that are present in the stack. */
     void getPresentUIDs(IntArray presentUIDs) {
         for (TaskRecord task : mTaskHistory) {
-            presentUIDs.add(task.effectiveUid);
+            for (ActivityRecord r : task.mActivities) {
+                presentUIDs.add(r.getUid());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c1bff36..5c49dfd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1227,6 +1227,10 @@
                     mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
                 }
             }
+            final String intentLaunchToken = intent.getLaunchToken();
+            if (aInfo.launchToken == null && intentLaunchToken != null) {
+                aInfo.launchToken = intentLaunchToken;
+            }
         }
         return aInfo;
     }
@@ -1632,7 +1636,11 @@
         mDisplayAccessUIDs.clear();
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
-            mDisplayAccessUIDs.append(activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+            // Only bother calculating the whitelist for private displays
+            if (activityDisplay.isPrivate()) {
+                mDisplayAccessUIDs.append(
+                        activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+            }
         }
         // Store updated lists in DisplayManager. Callers from outside of AM should get them there.
         mDisplayManagerInternal.setDisplayAccessUIDs(mDisplayAccessUIDs);
@@ -2287,7 +2295,18 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
+    private class MoveTaskToFullscreenArgs {
+        public int fromStackId;
+        public boolean onTop;
+    };
+    // Used only to closure over the arguments to moveTasksToFullscreenStack without
+    // allocation
+    private MoveTaskToFullscreenArgs mMoveToFullscreenArgs = new MoveTaskToFullscreenArgs();
+
+    private void moveTasksToFullscreenStackInnerLocked() {
+        int fromStackId = mMoveToFullscreenArgs.fromStackId;
+        boolean onTop = mMoveToFullscreenArgs.onTop;
+
         final ActivityStack stack = getStack(fromStackId);
         if (stack == null) {
             return;
@@ -2359,6 +2378,13 @@
         }
     }
 
+    void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
+        mMoveToFullscreenArgs.fromStackId = fromStackId;
+        mMoveToFullscreenArgs.onTop = onTop;
+
+        mWindowManager.inSurfaceTransaction(this::moveTasksToFullscreenStackInnerLocked);
+    }
+
     void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
             boolean preserveWindows) {
@@ -2471,12 +2497,12 @@
         return activityContainer.mStack;
     }
 
-    /**
-     * Removes the stack associated with the given {@param stackId}.  If the {@param stackId} is the
-     * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
-     * instead moved back onto the fullscreen stack.
-     */
-    void removeStackLocked(int stackId) {
+
+    // Used only to closure over the argument to removeStack without allocation.
+    private int mRemoveStackStackId;
+    void removeStackInnerLocked() {
+        int stackId = mRemoveStackStackId;
+
         final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             return;
@@ -2515,6 +2541,16 @@
     }
 
     /**
+     * Removes the stack associated with the given {@param stackId}.  If the {@param stackId} is the
+     * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
+     * instead moved back onto the fullscreen stack.
+     */
+    void removeStackLocked(int stackId) {
+        mRemoveStackStackId = stackId;
+        mWindowManager.inSurfaceTransaction(this::removeStackInnerLocked);
+    }
+
+    /**
      * Removes the task with the specified task id.
      *
      * @param taskId Identifier of the task to be removed.
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418..a668fea 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@
     /**
      * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    TaskSnapshot getSnapshot() {
+    TaskSnapshot getSnapshot(boolean reducedResolution) {
 
         // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
         // synchronized between AM and WM.
-        return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+        return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
     }
 
     void touchActiveTime() {
@@ -1236,6 +1236,10 @@
             mWindowContainerController.positionChildAt(appController, index);
         }
         r.onOverrideConfigurationSent();
+
+        // Make sure the list of display UID whitelists is updated
+        // now that this record is in a new task.
+        mService.mStackSupervisor.updateUIDsPresentOnDisplay();
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 1164876..48a1a1a 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -21,6 +21,9 @@
 import android.os.UserHandle;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Overall information about a uid that has actively running processes.
  */
@@ -40,20 +43,31 @@
      * {@link ActivityManagerService#mProcStateSeqCounter}
      * when {@link #curProcState} changes from background to foreground or vice versa.
      */
+    @GuardedBy("lock")
     long curProcStateSeq;
 
     /**
      * Last seq number for which NetworkPolicyManagerService notified ActivityManagerService that
      * network policies rules were updated.
      */
+    @GuardedBy("lock")
     long lastNetworkUpdatedProcStateSeq;
 
     /**
      * Last seq number for which AcitivityManagerService dispatched uid state change to
      * NetworkPolicyManagerService.
      */
+    @GuardedBy("lock")
     long lastDispatchedProcStateSeq;
 
+    /**
+     * Indicates if any thread is waiting for network rules to get updated for {@link #uid}.
+     */
+    @GuardedBy("lock")
+    boolean waitingForNetwork;
+
+    final Object lock = new Object();
+
     static final int CHANGE_PROCSTATE = 0;
     static final int CHANGE_GONE = 1;
     static final int CHANGE_GONE_IDLE = 2;
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 34826b6..d56fb1a 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.connectivity;
 
+import android.annotation.WorkerThread;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -74,7 +75,7 @@
     public static final String KEY_PROXY = "keyProxy";
     private String mCurrentPac;
     @GuardedBy("mProxyLock")
-    private Uri mPacUrl = Uri.EMPTY;
+    private volatile Uri mPacUrl = Uri.EMPTY;
 
     private AlarmManager mAlarmManager;
     @GuardedBy("mProxyLock")
@@ -87,33 +88,37 @@
     private int mCurrentDelay;
     private int mLastPort;
 
-    private boolean mHasSentBroadcast;
-    private boolean mHasDownloaded;
+    private volatile boolean mHasSentBroadcast;
+    private volatile boolean mHasDownloaded;
 
     private Handler mConnectivityHandler;
     private int mProxyMessage;
 
     /**
-     * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
+     * Used for locking when setting mProxyService and all references to mCurrentPac.
      */
     private final Object mProxyLock = new Object();
 
+    /**
+     * Runnable to download PAC script.
+     * The behavior relies on the assamption it always run on mNetThread to guarantee that the
+     * latest data fetched from mPacUrl is stored in mProxyService.
+     */
     private Runnable mPacDownloader = new Runnable() {
         @Override
+        @WorkerThread
         public void run() {
             String file;
-            synchronized (mProxyLock) {
-                if (Uri.EMPTY.equals(mPacUrl)) return;
-                final int oldTag = TrafficStats
-                        .getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PAC);
-                try {
-                    file = get(mPacUrl);
-                } catch (IOException ioe) {
-                    file = null;
-                    Log.w(TAG, "Failed to load PAC file: " + ioe);
-                } finally {
-                    TrafficStats.setThreadStatsTag(oldTag);
-                }
+            final Uri pacUrl = mPacUrl;
+            if (Uri.EMPTY.equals(pacUrl)) return;
+            final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PAC);
+            try {
+                file = get(pacUrl);
+            } catch (IOException ioe) {
+                file = null;
+                Log.w(TAG, "Failed to load PAC file: " + ioe);
+            } finally {
+                TrafficStats.setThreadStatsTag(oldTag);
             }
             if (file != null) {
                 synchronized (mProxyLock) {
@@ -176,9 +181,7 @@
                 // Allow to send broadcast, nothing to do.
                 return false;
             }
-            synchronized (mProxyLock) {
-                mPacUrl = proxy.getPacFileUrl();
-            }
+            mPacUrl = proxy.getPacFileUrl();
             mCurrentDelay = DELAY_1;
             mHasSentBroadcast = false;
             mHasDownloaded = false;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5258b87..be770a3 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1581,14 +1581,14 @@
 
         pw.println("Tethering:");
         pw.increaseIndent();
-        final TetheringConfiguration cfg = mConfig;
-        pw.print("preferredUpstreamIfaceTypes:");
-        synchronized (mPublicSync) {
-            for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
-                pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
-            }
-            pw.println();
 
+        pw.println("Configuration:");
+        pw.increaseIndent();
+        final TetheringConfiguration cfg = mConfig;
+        cfg.dump(pw);
+        pw.decreaseIndent();
+
+        synchronized (mPublicSync) {
             pw.println("Tether state:");
             pw.increaseIndent();
             for (int i = 0; i < mTetherStates.size(); i++) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 14d06cc..d38beb3 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -22,12 +22,15 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.net.ConnectivityManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.StringJoiner;
 
 
 /**
@@ -97,6 +100,44 @@
         return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
     }
 
+    public void dump(PrintWriter pw) {
+        dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
+        dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
+        dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
+
+        pw.print("isDunRequired: ");
+        pw.println(isDunRequired);
+
+        String[] upstreamTypes = null;
+        if (preferredUpstreamIfaceTypes != null) {
+            upstreamTypes = new String[preferredUpstreamIfaceTypes.size()];
+            int i = 0;
+            for (Integer netType : preferredUpstreamIfaceTypes) {
+                upstreamTypes[i] = ConnectivityManager.getNetworkTypeName(netType);
+                i++;
+            }
+        }
+        dumpStringArray(pw, "preferredUpstreamIfaceTypes", upstreamTypes);
+
+        dumpStringArray(pw, "dhcpRanges", dhcpRanges);
+        dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
+    }
+
+    private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
+        pw.print(label);
+        pw.print(": ");
+
+        if (values != null) {
+            final StringJoiner sj = new StringJoiner(", ", "[", "]");
+            for (String value : values) { sj.add(value); }
+            pw.print(sj.toString());
+        } else {
+            pw.print("null");
+        }
+
+        pw.println();
+    }
+
     private static boolean checkDunRequired(Context ctx) {
         final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
         final int secureSetting =
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index a947b41..f9bc12b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -62,7 +62,18 @@
 
     private final int mDisplayId;
     private final int mLayerStack;
-    private DisplayInfo mOverrideDisplayInfo; // set by the window manager
+    /**
+     * Override information set by the window manager. Will be reported instead of {@link #mInfo}
+     * if not null.
+     * @see #setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo)
+     * @see #getDisplayInfoLocked()
+     */
+    private DisplayInfo mOverrideDisplayInfo;
+    /**
+     * Current display info. Initialized with {@link #mBaseDisplayInfo}. Set to {@code null} if
+     * needs to be updated.
+     * @see #getDisplayInfoLocked()
+     */
     private DisplayInfo mInfo;
 
     // The display device that this logical display is based on and which
@@ -261,6 +272,9 @@
 
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo = null;
+            // Make sure that WM will be notified of new changes. It will then decide whether to
+            // apply them or not and will set the value again.
+            mOverrideDisplayInfo = null;
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 940f621..6e09ee2 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -83,6 +83,7 @@
 import com.android.server.job.controllers.IdleController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
+import com.android.server.job.controllers.StorageController;
 import com.android.server.job.controllers.TimeController;
 
 import libcore.util.EmptyArray;
@@ -133,6 +134,8 @@
     List<StateController> mControllers;
     /** Need direct access to this for testing. */
     BatteryController mBatteryController;
+    /** Need direct access to this for testing. */
+    StorageController mStorageController;
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
@@ -197,6 +200,7 @@
         private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
         private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
         private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
+        private static final String KEY_MIN_STORAGE_NOT_LOW_COUNT = "min_storage_not_low_count";
         private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
         private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
         private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
@@ -211,6 +215,7 @@
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
         private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
+        private static final int DEFAULT_MIN_STORAGE_NOT_LOW_COUNT = 1;
         private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
         private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
         private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
@@ -238,6 +243,11 @@
          */
         int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
         /**
+         * Minimum # of "storage not low" jobs that must be ready in order to force the JMS to
+         * schedule things early.
+         */
+        int MIN_STORAGE_NOT_LOW_COUNT = DEFAULT_MIN_STORAGE_NOT_LOW_COUNT;
+        /**
          * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
          * things early.  1 == Run connectivity jobs as soon as ready.
          */
@@ -323,6 +333,8 @@
                         DEFAULT_MIN_CHARGING_COUNT);
                 MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
                         DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
+                MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
+                        DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
                 MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
                         DEFAULT_MIN_CONNECTIVITY_COUNT);
                 MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
@@ -370,6 +382,9 @@
             pw.print("    "); pw.print(KEY_MIN_BATTERY_NOT_LOW_COUNT); pw.print("=");
             pw.print(MIN_BATTERY_NOT_LOW_COUNT); pw.println();
 
+            pw.print("    "); pw.print(KEY_MIN_STORAGE_NOT_LOW_COUNT); pw.print("=");
+            pw.print(MIN_STORAGE_NOT_LOW_COUNT); pw.println();
+
             pw.print("    "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
             pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
 
@@ -802,6 +817,8 @@
         mControllers.add(IdleController.get(this));
         mBatteryController = BatteryController.get(this);
         mControllers.add(mBatteryController);
+        mStorageController = StorageController.get(this);
+        mControllers.add(mStorageController);
         mControllers.add(AppIdleController.get(this));
         mControllers.add(ContentObserverController.get(this));
         mControllers.add(DeviceIdleJobsController.get(this));
@@ -1202,6 +1219,7 @@
         class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
             int chargingCount;
             int batteryNotLowCount;
+            int storageNotLowCount;
             int idleCount;
             int backoffCount;
             int connectivityCount;
@@ -1242,6 +1260,9 @@
                     if (job.hasBatteryNotLowConstraint()) {
                         batteryNotLowCount++;
                     }
+                    if (job.hasStorageNotLowConstraint()) {
+                        storageNotLowCount++;
+                    }
                     if (job.hasContentTriggerConstraint()) {
                         contentCount++;
                     }
@@ -1261,6 +1282,7 @@
                         connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
                         chargingCount >= mConstants.MIN_CHARGING_COUNT ||
                         batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
+                        storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
                         contentCount >= mConstants.MIN_CONTENT_COUNT ||
                         (runnableJobs != null
                                 && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
@@ -1285,6 +1307,7 @@
                 backoffCount = 0;
                 connectivityCount = 0;
                 batteryNotLowCount = 0;
+                storageNotLowCount = 0;
                 contentCount = 0;
                 runnableJobs = null;
             }
@@ -1828,6 +1851,19 @@
         }
     }
 
+    int getStorageSeq() {
+        synchronized (mLock) {
+            return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
+        }
+    }
+
+    boolean getStorageNotLow() {
+        synchronized (mLock) {
+            return mStorageController != null
+                    ? mStorageController.getTracker().isStorageNotLow() : false;
+        }
+    }
+
     private String printContextIdToJobMap(JobStatus[] map, String initial) {
         StringBuilder s = new StringBuilder(initial + ": ");
         for (int i=0; i<map.length; i++) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index ec23407..848704e 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -54,6 +54,10 @@
                     return runGetBatteryCharging(pw);
                 case "get-battery-not-low":
                     return runGetBatteryNotLow(pw);
+                case "get-storage-seq":
+                    return runGetStorageSeq(pw);
+                case "get-storage-not-low":
+                    return runGetStorageNotLow(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -181,6 +185,18 @@
         return 0;
     }
 
+    private int runGetStorageSeq(PrintWriter pw) {
+        int seq = mInternal.getStorageSeq();
+        pw.println(seq);
+        return 0;
+    }
+
+    private int runGetStorageNotLow(PrintWriter pw) {
+        boolean val = mInternal.getStorageNotLow();
+        pw.println(val);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -204,6 +220,10 @@
         pw.println("    Return whether the battery is currently considered to be charging.");
         pw.println("  get-battery-not-low");
         pw.println("    Return whether the battery is currently considered to not be low.");
+        pw.println("  get-storage-seq");
+        pw.println("    Return the last storage update sequence number that was received.");
+        pw.println("  get-storage-not-low");
+        pw.println("    Return whether storage is currently considered to not be low.");
         pw.println();
     }
 
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 05527be..91a962d 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -120,7 +120,7 @@
             mStateChangedListener.onControllerStateChanged();
         }
         // Also tell the scheduler that any ready jobs should be flushed.
-        if (stablePower) {
+        if (stablePower || batteryNotLow) {
             mStateChangedListener.onRunJobNow(null);
         }
     }
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index b65330a..94ca24c 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -138,7 +138,7 @@
      * We know the network has just come up. We want to run any jobs that are ready.
      */
     @Override
-    public synchronized void onNetworkActive() {
+    public void onNetworkActive() {
         synchronized (mLock) {
             for (int i = 0; i < mTrackedJobs.size(); i++) {
                 final JobStatus js = mTrackedJobs.get(i);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 9a55fed..ebb53a1 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -49,6 +49,7 @@
     static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING;
     static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;
     static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW;
+    static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     static final int CONSTRAINT_DEADLINE = 1<<30;
     static final int CONSTRAINT_UNMETERED = 1<<29;
@@ -334,6 +335,10 @@
         return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
     }
 
+    public boolean hasStorageNotLowConstraint() {
+        return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
+    }
+
     public boolean hasTimingDelayConstraint() {
         return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
     }
@@ -386,6 +391,10 @@
         return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
     }
 
+    boolean setStorageNotLowConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
+    }
+
     boolean setTimingDelayConstraintSatisfied(boolean state) {
         return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
     }
@@ -460,13 +469,14 @@
     }
 
     static final int CONSTRAINTS_OF_INTEREST =
-            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_TIMING_DELAY |
+            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
+            CONSTRAINT_TIMING_DELAY |
             CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
     static final int SOFT_OVERRIDE_CONSTRAINTS =
-            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
                     | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
 
     /**
@@ -562,6 +572,9 @@
         if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
             pw.print(" BATTERY_NOT_LOW");
         }
+        if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
+            pw.print(" STORAGE_NOT_LOW");
+        }
         if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
             pw.print(" TIMING_DELAY");
         }
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
new file mode 100644
index 0000000..60ae5a7
--- /dev/null
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.job.controllers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.StateChangedListener;
+import com.android.server.storage.DeviceStorageMonitorService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple controller that tracks the status of the device's storage.
+ */
+public class StorageController extends StateController {
+    private static final String TAG = "JobScheduler.Stor";
+
+    private static final Object sCreationLock = new Object();
+    private static volatile StorageController sController;
+
+    private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+    private StorageTracker mStorageTracker;
+
+    public static StorageController get(JobSchedulerService taskManagerService) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new StorageController(taskManagerService,
+                        taskManagerService.getContext(), taskManagerService.getLock());
+            }
+        }
+        return sController;
+    }
+
+    @VisibleForTesting
+    public StorageTracker getTracker() {
+        return mStorageTracker;
+    }
+
+    @VisibleForTesting
+    public static StorageController getForTesting(StateChangedListener stateChangedListener,
+            Context context) {
+        return new StorageController(stateChangedListener, context, new Object());
+    }
+
+    private StorageController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
+        mStorageTracker = new StorageTracker();
+        mStorageTracker.startTracking();
+    }
+
+    @Override
+    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
+        if (taskStatus.hasStorageNotLowConstraint()) {
+            mTrackedTasks.add(taskStatus);
+            taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
+        }
+    }
+
+    @Override
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
+        if (taskStatus.hasPowerConstraint()) {
+            mTrackedTasks.remove(taskStatus);
+        }
+    }
+
+    private void maybeReportNewStorageState() {
+        final boolean storageNotLow = mStorageTracker.isStorageNotLow();
+        boolean reportChange = false;
+        synchronized (mLock) {
+            for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
+                final JobStatus ts = mTrackedTasks.get(i);
+                boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
+                if (previous != storageNotLow) {
+                    reportChange = true;
+                }
+            }
+        }
+        // Let the scheduler know that state has changed. This may or may not result in an
+        // execution.
+        if (reportChange) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+        // Also tell the scheduler that any ready jobs should be flushed.
+        if (storageNotLow) {
+            mStateChangedListener.onRunJobNow(null);
+        }
+    }
+
+    public class StorageTracker extends BroadcastReceiver {
+        /**
+         * Track whether storage is low.
+         */
+        private boolean mStorageLow;
+        /** Sequence number of last broadcast. */
+        private int mLastBatterySeq = -1;
+
+        public StorageTracker() {
+        }
+
+        public void startTracking() {
+            IntentFilter filter = new IntentFilter();
+
+            // Storage status.  Just need to register, since STORAGE_LOW is a sticky
+            // broadcast we will receive that if it is currently active.
+            filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
+            filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+            mContext.registerReceiver(this, filter);
+        }
+
+        public boolean isStorageNotLow() {
+            return !mStorageLow;
+        }
+
+        public int getSeq() {
+            return mLastBatterySeq;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onReceiveInternal(intent);
+        }
+
+        @VisibleForTesting
+        public void onReceiveInternal(Intent intent) {
+            final String action = intent.getAction();
+            mLastBatterySeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
+                    mLastBatterySeq);
+            if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Available storage too low to do work. @ "
+                            + SystemClock.elapsedRealtime());
+                }
+                mStorageLow = true;
+            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Available stoage high enough to do work. @ "
+                            + SystemClock.elapsedRealtime());
+                }
+                mStorageLow = false;
+                maybeReportNewStorageState();
+            }
+        }
+    }
+
+    @Override
+    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
+        pw.print("Storage: not low = ");
+        pw.print(mStorageTracker.isStorageNotLow());
+        pw.print(", seq=");
+        pw.println(mStorageTracker.getSeq());
+        pw.print("Tracking ");
+        pw.print(mTrackedTasks.size());
+        pw.println(":");
+        for (int i = 0; i < mTrackedTasks.size(); i++) {
+            final JobStatus js = mTrackedTasks.get(i);
+            if (!js.shouldDump(filterUid)) {
+                continue;
+            }
+            pw.print("  #");
+            js.printUniqueId(pw);
+            pw.print(" from ");
+            UserHandle.formatUid(pw, js.getSourceUid());
+            pw.println();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d9..fa73b6b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
@@ -1305,14 +1306,16 @@
             mRankingHelper.updateNotificationChannel(pkg, uid, channel);
         }
 
-        synchronized (mNotificationList) {
+        synchronized (mNotificationLock) {
             final int N = mNotificationList.size();
             for (int i = N - 1; i >= 0; --i) {
                 NotificationRecord r = mNotificationList.get(i);
-                if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+                if (r.sbn.getPackageName().equals(pkg)
+                        && r.sbn.getUid() == uid
+                        && channel.getId() != null
+                        && channel.getId().equals(r.getChannel().getId())) {
                     r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
-                            r.sbn.getPackageName(), r.getUser().getIdentifier(),
-                            channel.getId(), false));
+                            pkg, uid, channel.getId(), false));
                 }
             }
         }
@@ -3097,8 +3100,17 @@
         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannel() != null) {
             channelId = (new Notification.TvExtender(notification)).getChannel();
         }
-        final NotificationChannel channel =  mRankingHelper.getNotificationChannelWithFallback(pkg,
+        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
                 notificationUid, channelId, false /* includeDeleted */);
+        if (channel == null) {
+            // STOPSHIP TODO: remove before release - should always throw without a valid channel.
+            if (channelId == null) {
+                Log.e(TAG, "Cannot post notification without channel ID when targeting O "
+                        + " - notification=" + notification);
+                return;
+            }
+            throw new IllegalArgumentException("No Channel found for notification=" + notification);
+        }
         final StatusBarNotification n = new StatusBarNotification(
                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                 user, null, System.currentTimeMillis());
@@ -3613,9 +3625,12 @@
         // notifying app does not have the VIBRATE permission.
         long identity = Binder.clearCallingIdentity();
         try {
-            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
-                    ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
-                            ? 0: -1, record.getAudioAttributes());
+            final boolean insistent =
+                (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+            final VibrationEffect effect = VibrationEffect.createWaveform(
+                    vibration, insistent ? 0 : -1 /*repeatIndex*/);
+            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                    effect, record.getAudioAttributes());
             return true;
         } finally{
             Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index d751a22..b043230 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -294,8 +294,9 @@
         stats.isNoisy = mSound != null || mVibration != null;
 
         if (mPreChannelsNotification
-                && (getChannel().getUserLockedFields()
-                & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
+                && (importance == IMPORTANCE_UNSPECIFIED
+                || (getChannel().getUserLockedFields()
+                & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0)) {
             if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
                 requestedImportance = IMPORTANCE_LOW;
             }
@@ -358,6 +359,7 @@
     }
 
     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
+        prefix = prefix + "  ";
         final Notification notification = sbn.getNotification();
         final Icon icon = notification.getSmallIcon();
         String iconStr = String.valueOf(icon);
@@ -365,21 +367,21 @@
             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
         }
         pw.println(prefix + this);
-        pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
-        pw.println(prefix + "  icon=" + iconStr);
-        pw.println(prefix + "  pri=" + notification.priority);
-        pw.println(prefix + "  key=" + sbn.getKey());
-        pw.println(prefix + "  seen=" + mIsSeen);
-        pw.println(prefix + "  groupKey=" + getGroupKey());
-        pw.println(prefix + "  fullscreenIntent=" + notification.fullScreenIntent);
-        pw.println(prefix + "  contentIntent=" + notification.contentIntent);
-        pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
-        pw.println(prefix + "  tickerText=" + notification.tickerText);
-        pw.println(prefix + "  contentView=" + notification.contentView);
-        pw.println(prefix + String.format("  color=0x%08x", notification.color));
-        pw.println(prefix + "  timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
+        pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
+        pw.println(prefix + "icon=" + iconStr);
+        pw.println(prefix + "pri=" + notification.priority);
+        pw.println(prefix + "key=" + sbn.getKey());
+        pw.println(prefix + "seen=" + mIsSeen);
+        pw.println(prefix + "groupKey=" + getGroupKey());
+        pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
+        pw.println(prefix + "contentIntent=" + notification.contentIntent);
+        pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
+        pw.println(prefix + "tickerText=" + notification.tickerText);
+        pw.println(prefix + "contentView=" + notification.contentView);
+        pw.println(prefix + String.format("color=0x%08x", notification.color));
+        pw.println(prefix + "timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
         if (notification.actions != null && notification.actions.length > 0) {
-            pw.println(prefix + "  actions={");
+            pw.println(prefix + "actions={");
             final int N = notification.actions.length;
             for (int i = 0; i < N; i++) {
                 final Notification.Action action = notification.actions[i];
@@ -395,7 +397,7 @@
             pw.println(prefix + "  }");
         }
         if (notification.extras != null && notification.extras.size() > 0) {
-            pw.println(prefix + "  extras={");
+            pw.println(prefix + "extras={");
             for (String key : notification.extras.keySet()) {
                 pw.print(prefix + "    " + key + "=");
                 Object val = notification.extras.get(key);
@@ -425,46 +427,46 @@
                     pw.println();
                 }
             }
-            pw.println(prefix + "  }");
+            pw.println(prefix + "}");
         }
-        pw.println(prefix + "  stats=" + stats.toString());
-        pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
-        pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
-        pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
-        pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
-        pw.println(prefix + "  mUserImportance="
+        pw.println(prefix + "stats=" + stats.toString());
+        pw.println(prefix + "mContactAffinity=" + mContactAffinity);
+        pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
+        pw.println(prefix + "mPackagePriority=" + mPackagePriority);
+        pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
+        pw.println(prefix + "mUserImportance="
                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
-        pw.println(prefix + "  mImportance="
+        pw.println(prefix + "mImportance="
                 + NotificationListenerService.Ranking.importanceToString(mImportance));
-        pw.println(prefix + "  mImportanceExplanation=" + mImportanceExplanation);
-        pw.println(prefix + "  mIntercept=" + mIntercept);
-        pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
-        pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
-        pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
-        pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
-        pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
-        pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
+        pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
+        pw.println(prefix + "mIntercept=" + mIntercept);
+        pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
+        pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
+        pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
+        pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
+        pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
+        pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
         if (mPreChannelsNotification) {
-            pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
+            pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
                     notification.defaults, notification.flags));
-            pw.println(prefix + "  n.sound=" + notification.sound);
-            pw.println(prefix + "  n.audioStreamType=" + notification.audioStreamType);
-            pw.println(prefix + "  n.audioAttributes=" + notification.audioAttributes);
+            pw.println(prefix + "n.sound=" + notification.sound);
+            pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
+            pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
-            pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
+            pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
         }
-        pw.println(prefix + "  mSound= " + mSound);
-        pw.println(prefix + "  mVibration= " + mVibration);
-        pw.println(prefix + "  mAttributes= " + mAttributes);
-        pw.println(prefix + "  mLight= " + mLight);
-        pw.println(prefix + "  mShowBadge=" + mShowBadge);
-        pw.println(prefix + "  effectiveNotificationChannel=" + getChannel());
+        pw.println(prefix + "mSound= " + mSound);
+        pw.println(prefix + "mVibration= " + mVibration);
+        pw.println(prefix + "mAttributes= " + mAttributes);
+        pw.println(prefix + "mLight= " + mLight);
+        pw.println(prefix + "mShowBadge=" + mShowBadge);
+        pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
         if (getPeopleOverride() != null) {
-            pw.println(prefix + "  overridePeople= " + TextUtils.join(",", getPeopleOverride()));
+            pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
         }
         if (getSnoozeCriteria() != null) {
-            pw.println(prefix + "  snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
+            pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 6a00722..e13df19 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,6 @@
     void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
     void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
-    NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7feaf5a..ce79465 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -213,7 +213,11 @@
                             }
                         }
 
-                        clampDefaultChannel(r);
+                        try {
+                            deleteDefaultChannelIfNeeded(r);
+                        } catch (NameNotFoundException e) {
+                            Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+                        }
                     }
                 }
             }
@@ -247,60 +251,94 @@
             r.priority = priority;
             r.visibility = visibility;
             r.showBadge = showBadge;
-            createDefaultChannelIfMissing(r);
+
+            try {
+                createDefaultChannelIfNeeded(r);
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+            }
+
             if (r.uid == Record.UNKNOWN_UID) {
                 mRestoredWithoutUids.put(pkg, r);
             } else {
                 mRecords.put(key, r);
             }
-            clampDefaultChannel(r);
         }
         return r;
     }
 
-    // Clamp the importance level of the default channel for apps targeting the new SDK version,
-    // unless the user has already changed the importance.
-    private void clampDefaultChannel(Record r) {
-        try {
-            if (r.uid != Record.UNKNOWN_UID) {
-                int userId = UserHandle.getUserId(r.uid);
-                final ApplicationInfo applicationInfo =
-                        mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
-                if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
-                    final NotificationChannel defaultChannel =
-                            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
-                    if ((defaultChannel.getUserLockedFields()
-                            & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
-                        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-                        updateConfig();
-                    }
-                }
-            }
-        } catch (NameNotFoundException e) {
-            // oh well.
+    private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
+        final int userId = UserHandle.getUserId(r.uid);
+        final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+        if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+            // Pre-O apps should have it.
+            return true;
         }
+
+        // STOPSHIP TODO: remove before release - O+ apps should never have a default channel.
+        // But for now, leave the default channel until an app has created its first channel.
+        boolean hasCreatedAChannel = false;
+        final int size = r.channels.size();
+        for (int i = 0; i < size; i++) {
+            final NotificationChannel notificationChannel = r.channels.valueAt(i);
+            if (notificationChannel != null &&
+                    notificationChannel.getId() != NotificationChannel.DEFAULT_CHANNEL_ID) {
+                hasCreatedAChannel = true;
+                break;
+            }
+        }
+        if (!hasCreatedAChannel) {
+            return true;
+        }
+
+        // Otherwise, should not have the default channel.
+        return false;
     }
 
-    private void createDefaultChannelIfMissing(Record r) {
+    private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            NotificationChannel channel;
-            channel = new NotificationChannel(
-                    NotificationChannel.DEFAULT_CHANNEL_ID,
-                    mContext.getString(R.string.default_notification_channel_label),
-                    r.importance);
-            channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
-            channel.setLockscreenVisibility(r.visibility);
-            if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-            }
-            if (r.priority != DEFAULT_PRIORITY) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-            }
-            if (r.visibility != DEFAULT_VISIBILITY) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
-            }
-            r.channels.put(channel.getId(), channel);
+            // Not present
+            return;
         }
+
+        if (shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Remove Default Channel.
+        r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+    }
+
+    private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
+        if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            // Already exists
+            return;
+        }
+
+        if (!shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Create Default Channel
+        NotificationChannel channel;
+        channel = new NotificationChannel(
+                NotificationChannel.DEFAULT_CHANNEL_ID,
+                mContext.getString(R.string.default_notification_channel_label),
+                r.importance);
+        channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+        channel.setLockscreenVisibility(r.visibility);
+        if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+        }
+        if (r.priority != DEFAULT_PRIORITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        }
+        if (r.visibility != DEFAULT_VISIBILITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+        }
+        r.channels.put(channel.getId(), channel);
     }
 
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
@@ -619,21 +657,6 @@
     }
 
     @Override
-    public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
-            String channelId, boolean includeDeleted) {
-        Record r = getOrCreateRecord(pkg, uid);
-        if (channelId == null) {
-            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
-        }
-        NotificationChannel channel = r.channels.get(channelId);
-        if (channel != null && (includeDeleted || !channel.isDeleted())) {
-            return channel;
-        } else {
-            return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
-        }
-    }
-
-    @Override
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
@@ -1057,10 +1080,9 @@
                     Record fullRecord = getRecord(pkg,
                             mPm.getPackageUidAsUser(pkg, changeUserId));
                     if (fullRecord != null) {
-                        clampDefaultChannel(fullRecord);
+                        deleteDefaultChannelIfNeeded(fullRecord);
                     }
-                } catch (NameNotFoundException e) {
-                }
+                } catch (NameNotFoundException e) {}
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 86124a8..59f8a2d 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -200,6 +200,7 @@
             // Intent that is launched if the package couldn't be installed for any reason.
             final Intent failureIntent = new Intent(origIntent);
             failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
+            failureIntent.setLaunchToken(token);
             try {
                 final IIntentSender failureIntentTarget = ActivityManager.getService()
                         .getIntentSender(
@@ -216,6 +217,7 @@
 
             // Intent that is launched if the package was installed successfully.
             final Intent successIntent = new Intent(origIntent);
+            successIntent.setLaunchToken(token);
             try {
                 final IIntentSender successIntentTarget = ActivityManager.getService()
                         .getIntentSender(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 837aa32..16c8a15 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18646,7 +18646,7 @@
     public void getPackageSizeInfo(final String packageName, int userHandle,
             final IPackageStatsObserver observer) {
         throw new UnsupportedOperationException(
-                "Shame on you for calling a hidden API. Shame!");
+                "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!");
     }
 
     private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index a904d17..c693a47 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -466,11 +466,22 @@
             }
         }
 
-        // Cache miss. Return not found for the moment.
-        //
-        // TODO(calin): this may be because of a newly installed package, an update
-        // or a new added user. We can either perform a full look up again or register
-        // observers to be notified of package/user updates.
+        if (DEBUG) {
+            // TODO(calin): Consider checking for /data/data symlink.
+            // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
+            // to load dex files through it.
+            try {
+                String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
+                if (dexPathReal != dexPath) {
+                    Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
+                            dexPath + " dexPathReal=" + dexPathReal);
+                }
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+        // Cache miss. The cache is updated during installs and uninstalls,
+        // so if we get here we're pretty sure the dex path does not exist.
         return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
     }
 
@@ -519,12 +530,9 @@
 
         public int searchDex(String dexPath, int userId) {
             // First check that this package is installed or active for the given user.
-            // If we don't have a data dir it means this user is trying to load something
-            // unavailable for them.
+            // A missing data dir means the package is not installed.
             Set<String> userDataDirs = mAppDataDirs.get(userId);
             if (userDataDirs == null) {
-                Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
-                        "user. dexPath=" + dexPath + ", userId=" + userId);
                 return DEX_SEARCH_NOT_FOUND;
             }
 
@@ -540,19 +548,6 @@
                 }
             }
 
-            // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink,
-            // /data/data/ -> /data/user/0/.
-            if (DEBUG) {
-                try {
-                    String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
-                    if (dexPathReal != dexPath) {
-                        Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
-                                dexPath + " dexPathReal=" + dexPathReal);
-                    }
-                } catch (IOException e) {
-                    // Ignore
-                }
-            }
             return DEX_SEARCH_NOT_FOUND;
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7eb4df8..31e22b9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,11 +177,13 @@
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.util.DisplayMetrics;
@@ -236,7 +238,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.vr.VrManagerInternal;
-import com.android.server.vr.PersistentVrStateListener;
 
 import java.io.File;
 import java.io.FileReader;
@@ -999,8 +1000,8 @@
     }
     MyOrientationListener mOrientationListener;
 
-    final PersistentVrStateListener mPersistentVrModeListener =
-            new PersistentVrStateListener() {
+    final IPersistentVrStateCallbacks mPersistentVrModeListener =
+            new IPersistentVrStateCallbacks.Stub() {
         @Override
         public void onPersistentVrStateChanged(boolean enabled) {
             mPersistentVrModeEnabled = enabled;
@@ -2401,7 +2402,9 @@
 
         ApplicationInfo appInfo;
         try {
-            appInfo = mContext.getPackageManager().getApplicationInfo(attrs.packageName,
+            appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+                            attrs.packageName,
+                            0 /* flags */,
                             UserHandle.getUserId(callingUid));
         } catch (PackageManager.NameNotFoundException e) {
             appInfo = null;
@@ -7527,17 +7530,35 @@
         if (hapticsDisabled && !always) {
             return false;
         }
-        long[] pattern = null;
+
+        VibrationEffect effect = getVibrationEffect(effectId);
+        if (effect == null) {
+            return false;
+        }
+
+        int owningUid;
+        String owningPackage;
+        if (win != null) {
+            owningUid = win.getOwningUid();
+            owningPackage = win.getOwningPackage();
+        } else {
+            owningUid = android.os.Process.myUid();
+            owningPackage = mContext.getOpPackageName();
+        }
+        mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+        return true;
+    }
+
+    private VibrationEffect getVibrationEffect(int effectId) {
+        long[] pattern;
         switch (effectId) {
+            case HapticFeedbackConstants.VIRTUAL_KEY:
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
                 pattern = mLongPressVibePattern;
                 break;
-            case HapticFeedbackConstants.VIRTUAL_KEY:
-                pattern = mVirtualKeyVibePattern;
-                break;
             case HapticFeedbackConstants.KEYBOARD_TAP:
-                pattern = mKeyboardTapVibePattern;
-                break;
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.CLOCK_TICK:
                 pattern = mClockTickVibePattern;
                 break;
@@ -7554,25 +7575,15 @@
                 pattern = mContextClickVibePattern;
                 break;
             default:
-                return false;
-        }
-        int owningUid;
-        String owningPackage;
-        if (win != null) {
-            owningUid = win.getOwningUid();
-            owningPackage = win.getOwningPackage();
-        } else {
-            owningUid = android.os.Process.myUid();
-            owningPackage = mContext.getOpPackageName();
+                return null;
         }
         if (pattern.length == 1) {
             // One-shot vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+            return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
         } else {
             // Pattern vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+            return VibrationEffect.createWaveform(pattern, -1);
         }
-        return true;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 12836db..0639eee 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -34,10 +34,12 @@
 import android.os.Environment;
 import android.os.FileObserver;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -52,6 +54,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import dalvik.system.VMRuntime;
 
@@ -76,12 +79,19 @@
 public class DeviceStorageMonitorService extends SystemService {
     static final String TAG = "DeviceStorageMonitorService";
 
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Current int sequence number of the update.
+     */
+    public static final String EXTRA_SEQUENCE = "seq";
+
     // TODO: extend to watch and manage caches on all private volumes
 
     static final boolean DEBUG = false;
     static final boolean localLOGV = false;
 
     static final int DEVICE_MEMORY_WHAT = 1;
+    static final int FORCE_MEMORY_WHAT = 2;
     private static final int MONITOR_INTERVAL = 1; //in minutes
     private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
 
@@ -112,6 +122,8 @@
     private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
 
     private long mThreadStartTime = -1;
+    boolean mUpdatesStopped;
+    AtomicInteger mSeq = new AtomicInteger(1);
     boolean mClearSucceeded = false;
     boolean mClearingCache;
     private final Intent mStorageLowIntent;
@@ -152,11 +164,17 @@
         @Override
         public void handleMessage(Message msg) {
             //don't handle an invalid message
-            if (msg.what != DEVICE_MEMORY_WHAT) {
-                Slog.e(TAG, "Will not process invalid message");
-                return;
+            switch (msg.what) {
+                case DEVICE_MEMORY_WHAT:
+                    checkMemory(msg.arg1 == _TRUE);
+                    return;
+                case FORCE_MEMORY_WHAT:
+                    forceMemory(msg.arg1, msg.arg2);
+                    return;
+                default:
+                    Slog.w(TAG, "Will not process invalid message");
+                    return;
             }
-            checkMemory(msg.arg1 == _TRUE);
         }
     };
 
@@ -239,12 +257,36 @@
         }
     }
 
+    void forceMemory(int opts, int seq) {
+        if ((opts&OPTION_UPDATES_STOPPED) == 0) {
+            if (mUpdatesStopped) {
+                mUpdatesStopped = false;
+                checkMemory(true);
+            }
+        } else {
+            mUpdatesStopped = true;
+            final boolean forceLow = (opts&OPTION_STORAGE_LOW) != 0;
+            if (mLowMemFlag != forceLow || (opts&OPTION_FORCE_UPDATE) != 0) {
+                mLowMemFlag = forceLow;
+                if (forceLow) {
+                    sendNotification(seq);
+                } else {
+                    cancelNotification(seq);
+                }
+            }
+        }
+    }
+
     void checkMemory(boolean checkCache) {
+        if (mUpdatesStopped) {
+            return;
+        }
+
         //if the thread that was started to clear cache is still running do nothing till its
         //finished clearing cache. Ideally this flag could be modified by clearCache
         // and should be accessed via a lock but even if it does this test will fail now and
         //hopefully the next time this flag will be set to the correct value.
-        if(mClearingCache) {
+        if (mClearingCache) {
             if(localLOGV) Slog.i(TAG, "Thread already running just skip");
             //make sure the thread is not hung for too long
             long diffTime = System.currentTimeMillis() - mThreadStartTime;
@@ -284,7 +326,7 @@
                         // We tried to clear the cache, but that didn't get us
                         // below the low storage limit.  Tell the user.
                         Slog.i(TAG, "Running low on memory. Sending notification");
-                        sendNotification();
+                        sendNotification(0);
                         mLowMemFlag = true;
                     } else {
                         if (localLOGV) Slog.v(TAG, "Running low on memory " +
@@ -295,13 +337,13 @@
                 mFreeMemAfterLastCacheClear = mFreeMem;
                 if (mLowMemFlag) {
                     Slog.i(TAG, "Memory available. Cancelling notification");
-                    cancelNotification();
+                    cancelNotification(0);
                     mLowMemFlag = false;
                 }
             }
             if (!mLowMemFlag && !mIsBootImageOnDisk && mFreeMem < BOOT_IMAGE_STORAGE_REQUIREMENT) {
                 Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
-                sendNotification();
+                sendNotification(0);
                 mLowMemFlag = true;
             }
             if (mFreeMem < mMemFullThreshold) {
@@ -419,7 +461,7 @@
         }
     };
 
-    private final IBinder mRemoteService = new Binder() {
+    private final Binder mRemoteService = new Binder() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -431,44 +473,157 @@
                 return;
             }
 
-            dumpImpl(pw);
+            dumpImpl(fd, pw, args);
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
         }
     };
 
-    void dumpImpl(PrintWriter pw) {
-        final Context context = getContext();
+    class Shell extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
 
-        pw.println("Current DeviceStorageMonitor state:");
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            dumpHelp(pw);
+        }
+    }
 
-        pw.print("  mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
-        pw.print(" mTotalMemory=");
-        pw.println(Formatter.formatFileSize(context, mTotalMemory));
+    static final int OPTION_FORCE_UPDATE = 1<<0;
+    static final int OPTION_UPDATES_STOPPED = 1<<1;
+    static final int OPTION_STORAGE_LOW = 1<<2;
 
-        pw.print("  mFreeMemAfterLastCacheClear=");
-        pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+    int parseOptions(Shell shell) {
+        String opt;
+        int opts = 0;
+        while ((opt = shell.getNextOption()) != null) {
+            if ("-f".equals(opt)) {
+                opts |= OPTION_FORCE_UPDATE;
+            }
+        }
+        return opts;
+    }
 
-        pw.print("  mLastReportedFreeMem=");
-        pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
-        pw.print(" mLastReportedFreeMemTime=");
-        TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
-        pw.println();
+    int onShellCommand(Shell shell, String cmd) {
+        if (cmd == null) {
+            return shell.handleDefaultCommands(cmd);
+        }
+        PrintWriter pw = shell.getOutPrintWriter();
+        switch (cmd) {
+            case "force-low": {
+                int opts = parseOptions(shell);
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                int seq = mSeq.incrementAndGet();
+                mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+                        opts | OPTION_UPDATES_STOPPED | OPTION_STORAGE_LOW, seq));
+                if ((opts & OPTION_FORCE_UPDATE) != 0) {
+                    pw.println(seq);
+                }
+            } break;
+            case "force-not-low": {
+                int opts = parseOptions(shell);
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                int seq = mSeq.incrementAndGet();
+                mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+                        opts | OPTION_UPDATES_STOPPED, seq));
+                if ((opts & OPTION_FORCE_UPDATE) != 0) {
+                    pw.println(seq);
+                }
+            } break;
+            case "reset": {
+                int opts = parseOptions(shell);
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                int seq = mSeq.incrementAndGet();
+                mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+                        opts, seq));
+                if ((opts & OPTION_FORCE_UPDATE) != 0) {
+                    pw.println(seq);
+                }
+            } break;
+            default:
+                return shell.handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
 
-        pw.print("  mLowMemFlag="); pw.print(mLowMemFlag);
-        pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
-        pw.print(" mIsBootImageOnDisk="); pw.print(mIsBootImageOnDisk);
+    static void dumpHelp(PrintWriter pw) {
+        pw.println("Device storage monitor service (devicestoragemonitor) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  force-low [-f]");
+        pw.println("    Force storage to be low, freezing storage state.");
+        pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
+        pw.println("  force-not-low [-f]");
+        pw.println("    Force storage to not be low, freezing storage state.");
+        pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
+        pw.println("  reset [-f]");
+        pw.println("    Unfreeze storage state, returning to current real values.");
+        pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
+    }
 
-        pw.print("  mClearSucceeded="); pw.print(mClearSucceeded);
-        pw.print(" mClearingCache="); pw.println(mClearingCache);
+    void dumpImpl(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (args == null || args.length == 0 || "-a".equals(args[0])) {
+            final Context context = getContext();
 
-        pw.print("  mMemLowThreshold=");
-        pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
-        pw.print(" mMemFullThreshold=");
-        pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+            pw.println("Current DeviceStorageMonitor state:");
 
-        pw.print("  mMemCacheStartTrimThreshold=");
-        pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
-        pw.print(" mMemCacheTrimToThreshold=");
-        pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
+            pw.print("  mFreeMem=");
+            pw.print(Formatter.formatFileSize(context, mFreeMem));
+            pw.print(" mTotalMemory=");
+            pw.println(Formatter.formatFileSize(context, mTotalMemory));
+
+            pw.print("  mFreeMemAfterLastCacheClear=");
+            pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+
+            pw.print("  mLastReportedFreeMem=");
+            pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
+            pw.print(" mLastReportedFreeMemTime=");
+            TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
+            pw.println();
+
+            if (mUpdatesStopped) {
+                pw.print("  mUpdatesStopped=");
+                pw.print(mUpdatesStopped);
+                pw.print(" mSeq=");
+                pw.println(mSeq.get());
+            } else {
+                pw.print("  mClearSucceeded=");
+                pw.print(mClearSucceeded);
+                pw.print(" mClearingCache=");
+                pw.println(mClearingCache);
+            }
+
+            pw.print("  mLowMemFlag=");
+            pw.print(mLowMemFlag);
+            pw.print(" mMemFullFlag=");
+            pw.println(mMemFullFlag);
+
+            pw.print("  mMemLowThreshold=");
+            pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
+            pw.print(" mMemFullThreshold=");
+            pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+
+            pw.print("  mMemCacheStartTrimThreshold=");
+            pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
+            pw.print(" mMemCacheTrimToThreshold=");
+            pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
+
+            pw.print("  mIsBootImageOnDisk="); pw.println(mIsBootImageOnDisk);
+        } else {
+            Shell shell = new Shell();
+            shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null));
+        }
     }
 
     /**
@@ -476,7 +631,7 @@
     * an error dialog indicating low disk space and launch the Installer
     * application
     */
-    private void sendNotification() {
+    private void sendNotification(int seq) {
         final Context context = getContext();
         if(localLOGV) Slog.i(TAG, "Sending low memory notification");
         //log the event to event log with the amount of free storage(in bytes) left on the device
@@ -514,13 +669,17 @@
         notification.flags |= Notification.FLAG_NO_CLEAR;
         notificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
                 UserHandle.ALL);
-        context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+        Intent broadcast = new Intent(mStorageLowIntent);
+        if (seq != 0) {
+            broadcast.putExtra(EXTRA_SEQUENCE, seq);
+        }
+        context.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
     }
 
     /**
      * Cancels low storage notification and sends OK intent.
      */
-    private void cancelNotification() {
+    private void cancelNotification(int seq) {
         final Context context = getContext();
         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
         NotificationManager mNotificationMgr =
@@ -530,7 +689,11 @@
         mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
 
         context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
-        context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
+        Intent broadcast = new Intent(mStorageOkIntent);
+        if (seq != 0) {
+            broadcast.putExtra(EXTRA_SEQUENCE, seq);
+        }
+        context.sendBroadcastAsUser(broadcast, UserHandle.ALL);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vr/PersistentVrStateListener.java b/services/core/java/com/android/server/vr/PersistentVrStateListener.java
deleted file mode 100644
index bccd5f1..0000000
--- a/services/core/java/com/android/server/vr/PersistentVrStateListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.vr;
-
-/**
- * Listener for state changes to persistent VR mode.
- *
- * @hide Only for use within system server.
- */
-public abstract class PersistentVrStateListener {
-
-  /**
-   * Called when the Persistent VR mode state changes.
-   *
-   * @param enabled {@code true} if persistent VR mode is enabled.
-   */
-    public abstract void onPersistentVrStateChanged(boolean enabled);
-}
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index 210aa44..358861d 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.content.ComponentName;
+import android.service.vr.IPersistentVrStateCallbacks;
 
 /**
  * Service for accessing the VR mode manager.
@@ -101,5 +102,5 @@
     /**
      * Adds listener that reports state changes to persistent VR mode.
      */
-    public abstract void addPersistentVrModeStateListener(PersistentVrStateListener listener);
+    public abstract void addPersistentVrModeStateListener(IPersistentVrStateCallbacks listener);
 }
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index a00115c..5bcdd4c 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -40,6 +40,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.service.vr.IVrListener;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -123,10 +124,10 @@
     private int mCurrentVrModeUser;
     private boolean mWasDefaultGranted;
     private boolean mGuard;
-    private final RemoteCallbackList<IVrStateCallbacks> mRemoteCallbacks =
+    private final RemoteCallbackList<IVrStateCallbacks> mVrStateRemoteCallbacks =
             new RemoteCallbackList<>();
-    private final ArrayList<PersistentVrStateListener> mPersistentVrStateListeners =
-            new ArrayList<>();
+    private final RemoteCallbackList<IPersistentVrStateCallbacks>
+            mPersistentVrStateRemoteCallbacks = new RemoteCallbackList<>();
     private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
     private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
     private VrState mPendingState;
@@ -202,16 +203,16 @@
             switch(msg.what) {
                 case MSG_VR_STATE_CHANGE : {
                     boolean state = (msg.arg1 == 1);
-                    int i = mRemoteCallbacks.beginBroadcast();
+                    int i = mVrStateRemoteCallbacks.beginBroadcast();
                     while (i > 0) {
                         i--;
                         try {
-                            mRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state);
+                            mVrStateRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state);
                         } catch (RemoteException e) {
                             // Noop
                         }
                     }
-                    mRemoteCallbacks.finishBroadcast();
+                    mVrStateRemoteCallbacks.finishBroadcast();
                 } break;
                 case MSG_PENDING_VR_STATE_CHANGE : {
                     synchronized(mLock) {
@@ -222,10 +223,17 @@
                 } break;
                 case MSG_PERSISTENT_VR_MODE_STATE_CHANGE : {
                     boolean state = (msg.arg1 == 1);
-                    for (int i = 0; i < mPersistentVrStateListeners.size(); i++) {
-                        mPersistentVrStateListeners.get(i).onPersistentVrStateChanged(
-                                state);
+                    int i = mPersistentVrStateRemoteCallbacks.beginBroadcast();
+                    while (i > 0) {
+                        i--;
+                        try {
+                            mPersistentVrStateRemoteCallbacks.getBroadcastItem(i)
+                                    .onPersistentVrStateChanged(state);
+                        } catch (RemoteException e) {
+                            // Noop
+                        }
                     }
+                    mPersistentVrStateRemoteCallbacks.finishBroadcast();
                 } break;
                 default :
                     throw new IllegalStateException("Unknown message type: " + msg.what);
@@ -383,6 +391,26 @@
         }
 
         @Override
+        public void registerPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
+            enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+            if (cb == null) {
+                throw new IllegalArgumentException("Callback binder object is null.");
+            }
+
+            VrManagerService.this.addPersistentStateCallback(cb);
+        }
+
+        @Override
+        public void unregisterPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
+            enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+            if (cb == null) {
+                throw new IllegalArgumentException("Callback binder object is null.");
+            }
+
+            VrManagerService.this.removePersistentStateCallback(cb);
+        }
+
+        @Override
         public boolean getVrModeState() {
             return VrManagerService.this.getVrMode();
         }
@@ -414,13 +442,21 @@
             String tab = "  ";
             dumpStateTransitions(pw);
             pw.println("\n\nRemote Callbacks:");
-            int i=mRemoteCallbacks.beginBroadcast(); // create the broadcast item array
+            int i=mVrStateRemoteCallbacks.beginBroadcast(); // create the broadcast item array
             while(i-->0) {
                 pw.print(tab);
-                pw.print(mRemoteCallbacks.getBroadcastItem(i));
+                pw.print(mVrStateRemoteCallbacks.getBroadcastItem(i));
                 if (i>0) pw.println(",");
             }
-            mRemoteCallbacks.finishBroadcast();
+            mVrStateRemoteCallbacks.finishBroadcast();
+            pw.println("\n\nPersistent Vr State Remote Callbacks:");
+            i=mPersistentVrStateRemoteCallbacks.beginBroadcast();
+            while(i-->0) {
+                pw.print(tab);
+                pw.print(mPersistentVrStateRemoteCallbacks.getBroadcastItem(i));
+                if (i>0) pw.println(",");
+            }
+            mPersistentVrStateRemoteCallbacks.finishBroadcast();
             pw.println("\n");
             pw.println("Installed VrListenerService components:");
             int userId = mCurrentVrModeUser;
@@ -443,16 +479,6 @@
                     pw.println(n.flattenToString());
                 }
             }
-            pw.println("Attached persistent mode listeners:");
-            if (mPersistentVrStateListeners == null ||
-                    mPersistentVrStateListeners.size() == 0) {
-                pw.println("None");
-            } else {
-                for (PersistentVrStateListener l : mPersistentVrStateListeners) {
-                    pw.print(tab);
-                    pw.println("listener: " + l);
-                }
-            }
             pw.println("\n");
             pw.println("********* End of VrManagerService Dump *********");
         }
@@ -507,8 +533,8 @@
         }
 
         @Override
-        public void addPersistentVrModeStateListener(PersistentVrStateListener listener) {
-            VrManagerService.this.addPersistentVrModeStateListener(listener);
+        public void addPersistentVrModeStateListener(IPersistentVrStateCallbacks listener) {
+            VrManagerService.this.addPersistentStateCallback(listener);
         }
     }
 
@@ -1084,12 +1110,6 @@
                 (mPersistentVrModeEnabled) ? 1 : 0, 0));
     }
 
-    private void addPersistentVrModeStateListener(PersistentVrStateListener listener) {
-        synchronized (mLock) {
-            mPersistentVrStateListeners.add(listener);
-        }
-    }
-
     private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) {
         synchronized (mLock) {
             return mComponentObserver.isValid(targetPackageName, userId);
@@ -1111,11 +1131,19 @@
      */
 
     private void addStateCallback(IVrStateCallbacks cb) {
-        mRemoteCallbacks.register(cb);
+        mVrStateRemoteCallbacks.register(cb);
     }
 
     private void removeStateCallback(IVrStateCallbacks cb) {
-        mRemoteCallbacks.unregister(cb);
+        mVrStateRemoteCallbacks.unregister(cb);
+    }
+
+    private void addPersistentStateCallback(IPersistentVrStateCallbacks cb) {
+        mPersistentVrStateRemoteCallbacks.register(cb);
+    }
+
+    private void removePersistentStateCallback(IPersistentVrStateCallbacks cb) {
+        mPersistentVrStateRemoteCallbacks.unregister(cb);
     }
 
     private boolean getVrMode() {
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index e3941b9..16edd35 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -161,11 +161,6 @@
         } 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}.
-        mAppToken.resetJustMovedInStack();
     }
 
     public void setDummyAnimation() {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a..bd38be4 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@
     private boolean createSnapshot() {
         final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
                 mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
-            false /* restoreFromDisk */);
+                false /* restoreFromDisk */, false /* reducedResolution */);
 
         if (snapshot == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c20ee97..a474316 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -55,6 +55,7 @@
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.IApplicationToken;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy.StartingSurface;
 
@@ -365,6 +366,13 @@
                 mEnteringAnimation = true;
                 mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token);
             }
+            if (hidden && !delayed) {
+                SurfaceControl.openTransaction();
+                for (int i = mChildren.size() - 1; i >= 0; i--) {
+                    mChildren.get(i).mWinAnimator.hide("immediately hidden");
+                }
+                SurfaceControl.closeTransaction();
+            }
 
             if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
                 // The token is not closing nor opening, so even if there is an animation set, that
@@ -967,19 +975,6 @@
         mService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
-    void resetJustMovedInStack() {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            (mChildren.get(i)).resetJustMovedInStack();
-        }
-    }
-
-    void notifyMovedInStack() {
-        for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
-            final WindowState win = mChildren.get(winNdx);
-            win.notifyMovedInStack();
-        }
-    }
-
     void setAppLayoutChanges(int changes, String reason) {
         if (!mChildren.isEmpty()) {
             final DisplayContent dc = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..01a992f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -179,11 +179,23 @@
     // Mapping from a token IBinder to a WindowToken object on this display.
     private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
 
+    // Initial display metrics.
     int mInitialDisplayWidth = 0;
     int mInitialDisplayHeight = 0;
     int mInitialDisplayDensity = 0;
+
+    /**
+     * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
+     * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
+     * @see WindowManagerService#setForcedDisplaySize(int, int, int)
+     */
     int mBaseDisplayWidth = 0;
     int mBaseDisplayHeight = 0;
+    /**
+     * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
+     * but can be set from Settings or via shell command "adb shell wm density".
+     * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
+     */
     int mBaseDisplayDensity = 0;
     boolean mDisplayScalingDisabled;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
@@ -1511,6 +1523,10 @@
     void updateDisplayInfo() {
         mDisplay.getDisplayInfo(mDisplayInfo);
         mDisplay.getMetrics(mDisplayMetrics);
+
+        // Check if display metrics changed and update base values if needed.
+        updateBaseDisplayMetricsIfNeeded();
+
         for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
             mTaskStackContainers.get(i).updateDisplayInfo(null);
         }
@@ -1526,10 +1542,11 @@
             }
         }
 
-        mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
-        mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
-        mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
-        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+        updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+                mDisplayInfo.logicalDensityDpi);
+        mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+        mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+        mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
     }
 
     void getLogicalDisplayRect(Rect out) {
@@ -1559,6 +1576,50 @@
         }
     }
 
+    /**
+     * If display metrics changed, overrides are not set and it's not just a rotation - update base
+     * values.
+     */
+    private void updateBaseDisplayMetricsIfNeeded() {
+        final int orientation = mDisplayInfo.rotation;
+        final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+        final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth;
+        final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight;
+        int density = mDisplayInfo.logicalDensityDpi;
+
+        boolean displayMetricsChanged = false;
+
+        // Check if display size is not forced and changed in new display info.
+        boolean isDisplaySizeForced = mBaseDisplayWidth != mInitialDisplayWidth
+                || mBaseDisplayHeight != mInitialDisplayHeight;
+        if (!isDisplaySizeForced) {
+            displayMetricsChanged = mBaseDisplayWidth != newWidth
+                    || mBaseDisplayHeight != newHeight;
+        }
+
+        // Check if display density is not forced and changed in new display info.
+        final int forcedDensity = mBaseDisplayDensity != mInitialDisplayDensity
+                ? mBaseDisplayDensity : 0;
+        if (forcedDensity != 0) {
+            density = forcedDensity;
+        } else {
+            displayMetricsChanged |= mBaseDisplayDensity != mDisplayInfo.logicalDensityDpi;
+        }
+
+        if (displayMetricsChanged) {
+            updateBaseDisplayMetrics(newWidth, newHeight, density);
+            mService.reconfigureDisplayLocked(this);
+        }
+    }
+
+    /** Update base (override) display metrics. */
+    void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+        mBaseDisplayWidth = baseWidth;
+        mBaseDisplayHeight = baseHeight;
+        mBaseDisplayDensity = baseDensity;
+        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+    }
+
     void getContentRect(Rect out) {
         out.set(mContentRect);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 99c085f..9e4d60a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -208,10 +208,6 @@
     void positionAt(int position, Rect bounds, Configuration overrideConfig) {
         mStack.positionChildAt(position, this, false /* includingParents */);
         resizeLocked(bounds, overrideConfig, false /* force */);
-
-        for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
-            mChildren.get(activityNdx).notifyMovedInStack();
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 4028336..1ec0201 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@
     /**
      * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
 
         synchronized (mService.mWindowMap) {
             // Try the running cache.
@@ -81,19 +82,23 @@
         if (!restoreFromDisk) {
             return null;
         }
-        return tryRestoreFromDisk(taskId, userId);
+        return tryRestoreFromDisk(taskId, userId, reducedResolution);
     }
 
     /**
      * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
-        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
         if (snapshot == null) {
             return null;
         }
-        synchronized (mService.mWindowMap) {
-            mRetrievalCache.put(taskId, snapshot);
+
+        // Only cache non-reduced snapshots.
+        if (!reducedResolution) {
+            synchronized (mService.mWindowMap) {
+                mRetrievalCache.put(taskId, snapshot);
+            }
         }
         return snapshot;
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba..469a8a7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@
      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
      * MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
-        return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
+        return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
     }
 
     /**
@@ -130,7 +131,7 @@
             return null;
         }
         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
-                top.findMainWindow().mStableInsets);
+                top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 4340822..ec21d25 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.TaskSnapshotPersister.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -59,11 +60,14 @@
      *
      * @param taskId The id of the task to load.
      * @param userId The id of the user the task belonged to.
+     * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId) {
+    TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
         final File protoFile = mPersister.getProtoFile(taskId, userId);
-        final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+        final File bitmapFile = reducedResolution
+                ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+                : mPersister.getBitmapFile(taskId, userId);
         if (!protoFile.exists() || !bitmapFile.exists()) {
             return null;
         }
@@ -84,7 +88,8 @@
                 return null;
             }
             return new TaskSnapshot(buffer, proto.orientation,
-                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+                    reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
             return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c38..f2a92df 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.graphics.Bitmap.CompressFormat.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -47,9 +48,12 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
+    private static final String REDUCED_POSTFIX = "_reduced";
+    static final float REDUCED_SCALE = 0.5f;
     private static final long DELAY_MS = 100;
+    private static final int QUALITY = 95;
     private static final String PROTO_EXTENSION = ".proto";
-    private static final String BITMAP_EXTENSION = ".png";
+    private static final String BITMAP_EXTENSION = ".jpg";
 
     @GuardedBy("mLock")
     private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@
         return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
     }
 
+    File getReducedResolutionBitmapFile(int taskId, int userId) {
+        return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+    }
+
     private boolean createDirectory(int userId) {
         final File dir = getDirectory(userId);
         return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@
     private void deleteSnapshot(int taskId, int userId) {
         final File protoFile = getProtoFile(taskId, userId);
         final File bitmapFile = getBitmapFile(taskId, userId);
+        final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
         protoFile.delete();
         bitmapFile.delete();
+        bitmapReducedFile.delete();
     }
 
     interface DirectoryResolver {
@@ -254,13 +264,20 @@
 
         boolean writeBuffer() {
             final File file = getBitmapFile(mTaskId, mUserId);
+            final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
             final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+            final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+                    (int) (bitmap.getWidth() * REDUCED_SCALE),
+                    (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
             try {
                 FileOutputStream fos = new FileOutputStream(file);
-                bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+                bitmap.compress(JPEG, QUALITY, fos);
                 fos.close();
+                FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+                reduced.compress(JPEG, QUALITY, reducedFos);
+                reducedFos.close();
             } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+                Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
                 return false;
             }
             return true;
@@ -325,8 +342,12 @@
             if (end == -1) {
                 return -1;
             }
+            String name = fileName.substring(0, end);
+            if (name.endsWith(REDUCED_POSTFIX)) {
+                name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+            }
             try {
-                return Integer.parseInt(fileName.substring(0, end));
+                return Integer.parseInt(name);
             } catch (NumberFormatException e) {
                 return -1;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5edb82cfb..dd2689b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@
         return true;
     }
 
-    public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
-        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+    public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+                reducedResolution);
     }
 
     /**
@@ -5304,8 +5305,8 @@
                     if (displayContent.mBaseDisplayWidth != width
                             || displayContent.mBaseDisplayHeight != height) {
                         Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
-                        displayContent.mBaseDisplayWidth = width;
-                        displayContent.mBaseDisplayHeight = height;
+                        displayContent.updateBaseDisplayMetrics(width, height,
+                                displayContent.mBaseDisplayDensity);
                     }
                 } catch (NumberFormatException ex) {
                 }
@@ -5330,8 +5331,7 @@
     // displayContent must not be null
     private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
         Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
-        displayContent.mBaseDisplayWidth = width;
-        displayContent.mBaseDisplayHeight = height;
+        displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -7305,4 +7305,49 @@
         mAppFreezeListeners.remove(listener);
     }
 
+    /**
+     * WARNING: This interrupts surface updates, be careful! Don't
+     * execute within the transaction for longer than you would
+     * execute on an animation thread.
+     * WARNING: This holds the WindowManager lock, so if exec will acquire
+     * the ActivityManager lock, you should hold it BEFORE calling this
+     * otherwise there is a risk of deadlock if another thread holding the AM
+     * lock waits on the WM lock.
+     * WARNING: This method contains locks known to the State of California
+     * to cause Deadlocks and other conditions.
+     *
+     *
+     * Begins a surface transaction with which the AM can batch operations.
+     * All Surface updates performed by the WindowManager following this
+     * will not appear on screen until after the call to
+     * closeSurfaceTransaction.
+     *
+     * ActivityManager can use this to ensure multiple 'commands' will all
+     * be reflected in a single frame. For example when reparenting a window
+     * which was previously hidden due to it's parent properties, we may
+     * need to ensure it is hidden in the same frame that the properties
+     * from the new parent are inherited, otherwise it could be revealed
+     * mistakenly.
+     *
+     *
+     * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout
+     * with something like this but it seems that some existing cases of
+     * deferSurfaceLayout may be a little too broad, in particular the total
+     * enclosure of startActivityUnchecked which could run for quite some time.
+     */
+    public void inSurfaceTransaction(Runnable exec) {
+        // We hold the WindowManger lock to ensure relayoutWindow
+        // does not return while a Surface transaction is opening.
+        // The client depends on us to have resized the surface
+        // by that point (b/36462635)
+
+        synchronized (mWindowMap) {
+            SurfaceControl.openTransaction();
+            try {
+                exec.run();
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ca5d551..d4c8b1f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -517,11 +517,6 @@
     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;
@@ -1998,49 +1993,6 @@
         }
     }
 
-    /**
-     * 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;
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            c.notifyMovedInStack();
-        }
-    }
-
-    /**
-     * 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;
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.resetJustMovedInStack();
-        }
-    }
-
     private final class DeadWindowEventReceiver extends InputEventReceiver {
         DeadWindowEventReceiver(InputChannel inputChannel) {
             super(inputChannel, mService.mH.getLooper());
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 4b71338..48de7e4 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1177,10 +1177,6 @@
 
         w.transformClipRectFromScreenToSurfaceSpace(clipRect);
 
-        // See {@link WindowState#notifyMovedInStack} for why this is necessary.
-        if (w.hasJustMovedInStack() && mLastClipRect.isEmpty() && !clipRect.isEmpty()) {
-            clipRect.setEmpty();
-        }
         return true;
     }
 
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae794..76ce890 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
 #include <utils/Log.h>
 #include <hardware/vibrator.h>
 
+#include <inttypes.h>
 #include <stdio.h>
 
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
 using android::hardware::vibrator::V1_0::IVibrator;
 using android::hardware::vibrator::V1_0::Status;
 
@@ -59,8 +63,8 @@
 {
     if (mHal != nullptr) {
         Status retStatus = mHal->on(timeout_ms);
-        if (retStatus == Status::ERR) {
-            ALOGE("vibratorOn command failed.");
+        if (retStatus != Status::OK) {
+            ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
         }
     } else {
         ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@
 {
     if (mHal != nullptr) {
         Status retStatus = mHal->off();
-        if (retStatus == Status::ERR) {
-            ALOGE("vibratorOff command failed.");
+        if (retStatus != Status::OK) {
+            ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
         }
     } else {
         ALOGW("Tried to stop vibrating but there is no vibrator device.");
     }
 }
 
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+    if (mHal != nullptr) {
+        return mHal->supportsAmplitudeControl();
+    } else {
+        ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+    }
+    return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+    if (mHal != nullptr) {
+        Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+        if (status != Status::OK) {
+            ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+                    static_cast<uint32_t>(status));
+        }
+    } else {
+        ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+    }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+    if (mHal != nullptr) {
+        Status status;
+        uint32_t lengthMs;
+        mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+                [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+                    status = retStatus;
+                    lengthMs = retLengthMs;
+                });
+        if (status == Status::OK) {
+            return lengthMs;
+        } else if (status != Status::UNSUPPORTED_OPERATION) {
+            // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+            // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+            // to the framework waveforms.
+            ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+                    ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+                    static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+        }
+    } else {
+        ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+    }
+    return -1;
+}
+
 static const JNINativeMethod method_table[] = {
     { "vibratorExists", "()Z", (void*)vibratorExists },
     { "vibratorInit", "()V", (void*)vibratorInit },
     { "vibratorOn", "(J)V", (void*)vibratorOn },
-    { "vibratorOff", "()V", (void*)vibratorOff }
+    { "vibratorOff", "()V", (void*)vibratorOff },
+    { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+    { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+    { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
 };
 
 int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
new file mode 100644
index 0000000..a2bc195
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.devicepolicy;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.Log;
+
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Set;
+
+public class CertificateMonitor {
+    protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+    protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
+
+    private final DevicePolicyManagerService mService;
+    private final DevicePolicyManagerService.Injector mInjector;
+    private final Handler mHandler;
+
+    public CertificateMonitor(final DevicePolicyManagerService service,
+            final DevicePolicyManagerService.Injector injector, final Handler handler) {
+        mService = service;
+        mInjector = injector;
+        mHandler = handler;
+
+        // Broadcast filter for changes to the trusted certificate store. Listens on the background
+        // handler to avoid blocking time-critical tasks on the main handler thread.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_STARTED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mInjector.mContext.registerReceiverAsUser(
+                mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+    }
+
+    public String installCaCert(final UserHandle userHandle, byte[] certBuffer) {
+        // Convert certificate data from X509 format to PEM.
+        byte[] pemCert;
+        try {
+            X509Certificate cert = parseCert(certBuffer);
+            pemCert = Credentials.convertToPem(cert);
+        } catch (CertificateException | IOException ce) {
+            Log.e(LOG_TAG, "Problem converting cert", ce);
+            return null;
+        }
+
+        try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
+            return keyChainConnection.getService().installCaCertificate(pemCert);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
+        } catch (InterruptedException e1) {
+            Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
+            Thread.currentThread().interrupt();
+        }
+        return null;
+    }
+
+    public void uninstallCaCerts(final UserHandle userHandle, final String[] aliases) {
+        try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
+            for (int i = 0 ; i < aliases.length; i++) {
+                keyChainConnection.getService().deleteCaCertificate(aliases[i]);
+            }
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
+        } catch (InterruptedException ie) {
+            Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public List<String> getInstalledCaCertificates(UserHandle userHandle)
+            throws RemoteException, RuntimeException {
+        try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
+            return conn.getService().getUserCaAliases().getList();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return null;
+        } catch (AssertionError e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void onCertificateApprovalsChanged(int userId) {
+        mHandler.post(() -> updateInstalledCertificates(UserHandle.of(userId)));
+    }
+
+    /**
+     * Broadcast receiver for changes to the trusted certificate store.
+     */
+    private final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (StorageManager.inCryptKeeperBounce()) {
+                return;
+            }
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+            updateInstalledCertificates(UserHandle.of(userId));
+        }
+    };
+
+    private void updateInstalledCertificates(final UserHandle userHandle) {
+        if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+            return;
+        }
+
+        final List<String> installedCerts;
+        try {
+            installedCerts = getInstalledCaCertificates(userHandle);
+        } catch (RemoteException | RuntimeException e) {
+            Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+            return;
+        }
+        mService.onInstalledCertificatesChanged(userHandle, installedCerts);
+
+        final int pendingCertificateCount =
+                installedCerts.size() - mService.getAcceptedCaCertificates(userHandle).size();
+        if (pendingCertificateCount != 0) {
+            final Notification noti = buildNotification(userHandle, pendingCertificateCount);
+            mInjector.getNotificationManager().notifyAsUser(
+                    LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
+        } else {
+            mInjector.getNotificationManager().cancelAsUser(
+                    LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
+        }
+    }
+
+    private Notification buildNotification(UserHandle userHandle, int pendingCertificateCount) {
+        final Context userContext;
+        try {
+            userContext = mInjector.createContextAsUser(userHandle);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+            return null;
+        }
+
+        final Resources resources = mInjector.getResources();
+        final int smallIconId;
+        final String contentText;
+
+        int parentUserId = userHandle.getIdentifier();
+
+        if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+                    mService.getProfileOwnerName(userHandle.getIdentifier()));
+            smallIconId = R.drawable.stat_sys_certificate_info;
+            parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
+        } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
+            final String ownerName = mService.getDeviceOwnerName();
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+                    mService.getDeviceOwnerName());
+            smallIconId = R.drawable.stat_sys_certificate_info;
+        } else {
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
+            smallIconId = android.R.drawable.stat_sys_warning;
+        }
+
+        // Create an intent to launch an activity showing information about the certificate.
+        Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
+        dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
+        dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
+
+        // The intent should only be allowed to resolve to a system app.
+        ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
+                mInjector.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
+        if (targetInfo != null) {
+            dialogIntent.setComponent(targetInfo.getComponentName());
+        }
+
+        PendingIntent notifyIntent = mInjector.pendingIntentGetActivityAsUser(userContext, 0,
+                dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+                UserHandle.of(parentUserId));
+
+        return new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
+                .setSmallIcon(smallIconId)
+                .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
+                        pendingCertificateCount))
+                .setContentText(contentText)
+                .setContentIntent(notifyIntent)
+                .setShowWhen(false)
+                .setColor(R.color.system_notification_accent_color)
+                .build();
+    }
+
+    private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
+        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
+                certBuffer));
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a916672..ecbd312 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -114,7 +114,6 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -184,7 +183,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -193,9 +191,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.util.ArrayList;
@@ -398,6 +393,7 @@
      */
     boolean mIsWatch;
 
+    private final CertificateMonitor mCertificateMonitor;
     private final SecurityLogMonitor mSecurityLogMonitor;
     private NetworkLogger mNetworkLogger;
 
@@ -530,19 +526,6 @@
     final Handler mHandler;
     final Handler mBackgroundHandler;
 
-    /** Listens on any device, even when mHasFeature == false. */
-    final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (StorageManager.inCryptKeeperBounce()) {
-                return;
-            }
-            final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
-            new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
-                    .execute(userHandle);
-        }
-    };
-
     /** Listens only if mHasFeature == true. */
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -630,25 +613,6 @@
                 handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
             } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
                 clearWipeProfileNotification();
-            } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
-                mBackgroundHandler.post(() ->  {
-                    try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
-                            UserHandle.of(userHandle))) {
-                        final List<String> caCerts =
-                                keyChainConnection.getService().getUserCaAliases().getList();
-                        synchronized (DevicePolicyManagerService.this) {
-                            if (getUserData(userHandle).mOwnerInstalledCaCerts
-                                    .retainAll(caCerts)) {
-                                saveSettingsLocked(userHandle);
-                            }
-                        }
-                    } catch (InterruptedException e) {
-                        Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
-                        Thread.currentThread().interrupt();
-                    } catch (RemoteException e) {
-                        Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
-                    }
-                });
             }
         }
 
@@ -1527,7 +1491,7 @@
     @VisibleForTesting
     static class Injector {
 
-        private final Context mContext;
+        public final Context mContext;
 
         Injector(Context context) {
             mContext = context;
@@ -1720,6 +1684,12 @@
             return "/data/system/";
         }
 
+        PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
+                @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
+            return PendingIntent.getActivityAsUser(
+                    context, requestCode, intent, flags, options, user);
+        }
+
         void registerContentObserver(Uri uri, boolean notifyForDescendents,
                 ContentObserver observer, int userHandle) {
             mContext.getContentResolver().registerContentObserver(uri, notifyForDescendents,
@@ -1810,6 +1780,7 @@
         mLocalService = new LocalService();
         mLockPatternUtils = injector.newLockPatternUtils();
 
+        // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false?
         mSecurityLogMonitor = new SecurityLogMonitor(this);
 
         mHasFeature = mInjector.getPackageManager()
@@ -1818,27 +1789,20 @@
                 .hasSystemFeature(PackageManager.FEATURE_WATCH);
         mBackgroundHandler = BackgroundThread.getHandler();
 
-        // Broadcast filter for changes to the trusted certificate store. These changes get a
-        // separate intent filter so we can listen to them even when device_admin is off.
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_STARTED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
-        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+        // Needed when mHasFeature == false, because it controls the certificate warning text.
+        mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
 
         if (!mHasFeature) {
             // Skip the rest of the initialization
             return;
         }
 
-        filter = new IntentFilter();
+        IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BOOT_COMPLETED);
         filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
-        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
         filter = new IntentFilter();
@@ -3083,33 +3047,43 @@
     }
 
     /**
-     * Remove deleted CA certificates from the "approved" list for a particular user, counting
-     * the number still remaining to approve.
+     * Clean up internal state when the set of installed trusted CA certificates changes.
      *
      * @param userHandle user to check for. This must be a real user and not, for example,
      *        {@link UserHandle#ALL}.
      * @param installedCertificates the full set of certificate authorities currently installed for
      *        {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
      *        correspond to some subset of this.
-     *
-     * @return number of certificates yet to be approved by {@param userHandle}.
      */
-    protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
+    protected void onInstalledCertificatesChanged(final UserHandle userHandle,
             final @NonNull Collection<String> installedCertificates) {
+        if (!mHasFeature) {
+            return;
+        }
         enforceManageUsers();
 
-        if (!mHasFeature) {
-            return installedCertificates.size();
-        } else {
+        synchronized (this) {
             final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
 
-            // Remove deleted certificates. Flush xml if necessary.
-            if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
+            boolean changed = false;
+            changed |= policy.mAcceptedCaCertificates.retainAll(installedCertificates);
+            changed |= policy.mOwnerInstalledCaCerts.retainAll(installedCertificates);
+            if (changed) {
                 saveSettingsLocked(userHandle.getIdentifier());
             }
+        }
+    }
 
-            // Trim approved certificates from the count.
-            return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
+    /**
+     * Internal method used by {@link CertificateMonitor}.
+     */
+    protected Set<String> getAcceptedCaCertificates(final UserHandle userHandle) {
+        if (!mHasFeature) {
+            return Collections.<String> emptySet();
+        }
+        synchronized (this) {
+            final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
+            return policy.mAcceptedCaCertificates;
         }
     }
 
@@ -4690,7 +4664,7 @@
             }
             saveSettingsLocked(userId);
         }
-        new MonitoringCertNotificationTask(this, mInjector).execute(userId);
+        mCertificateMonitor.onCertificateApprovalsChanged(userId);
         return true;
     }
 
@@ -4713,8 +4687,7 @@
                     getUserData(userInfo.id).mAcceptedCaCertificates.clear();
                     saveSettingsLocked(userInfo.id);
                 }
-
-                new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
+                mCertificateMonitor.onCertificateApprovalsChanged(userId);
             }
         }
     }
@@ -4722,79 +4695,47 @@
     @Override
     public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
             throws RemoteException {
-        enforceCanManageCaCerts(admin, callerPackage);
-
-        byte[] pemCert;
-        try {
-            X509Certificate cert = parseCert(certBuffer);
-            pemCert = Credentials.convertToPem(cert);
-        } catch (CertificateException ce) {
-            Log.e(LOG_TAG, "Problem converting cert", ce);
-            return false;
-        } catch (IOException ioe) {
-            Log.e(LOG_TAG, "Problem reading cert", ioe);
+        if (!mHasFeature) {
             return false;
         }
+        enforceCanManageCaCerts(admin, callerPackage);
 
-        final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
+        final String alias;
+
+        final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
         final long id = mInjector.binderClearCallingIdentity();
-        String alias = null;
         try {
-            try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
-                    userHandle)) {
-                alias = keyChainConnection.getService().installCaCertificate(pemCert);
-            } catch (RemoteException e) {
-                Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
+            alias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
+            if (alias == null) {
+                Log.w(LOG_TAG, "Problem installing cert");
+                return false;
             }
-        } catch (InterruptedException e1) {
-            Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
-            Thread.currentThread().interrupt();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
-        if (alias == null) {
-            Log.w(LOG_TAG, "Problem installing cert");
-        } else {
-            synchronized (this) {
-                final int userId = userHandle.getIdentifier();
-                getUserData(userId).mOwnerInstalledCaCerts.add(alias);
-                saveSettingsLocked(userId);
-            }
-            return true;
-        }
-        return false;
-    }
 
-    private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
-        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-        return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
-                certBuffer));
+        synchronized (this) {
+            getUserData(userHandle.getIdentifier()).mOwnerInstalledCaCerts.add(alias);
+            saveSettingsLocked(userHandle.getIdentifier());
+        }
+        return true;
     }
 
     @Override
     public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
+        if (!mHasFeature) {
+            return;
+        }
         enforceCanManageCaCerts(admin, callerPackage);
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        final UserHandle userHandle = UserHandle.of(userId);
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
-                    userHandle)) {
-                for (int i = 0 ; i < aliases.length; i++) {
-                    keyChainConnection.getService().deleteCaCertificate(aliases[i]);
-                }
-            } catch (RemoteException e) {
-                Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
-                return;
-            }
-        } catch (InterruptedException ie) {
-            Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
-            Thread.currentThread().interrupt();
-            return;
+            mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
+
         synchronized (this) {
             if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
                 saveSettingsLocked(userId);
@@ -8192,7 +8133,6 @@
             // Ensure the caller is a DO/PO or a package access delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
                     DELEGATION_PACKAGE_ACCESS);
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
 
             long id = mInjector.binderClearCallingIdentity();
             try {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
deleted file mode 100644
index 1933fe7..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.devicepolicy;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.security.KeyChain.KeyChainConnection;
-import android.util.Log;
-
-import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
-    protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
-    protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
-
-    private final DevicePolicyManagerService mService;
-    private final DevicePolicyManagerService.Injector mInjector;
-
-    public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
-            final DevicePolicyManagerService.Injector injector) {
-        super();
-        mService = service;
-        mInjector = injector;
-    }
-
-    @Override
-    protected Void doInBackground(Integer... params) {
-        int userHandle = params[0];
-
-        if (userHandle == UserHandle.USER_ALL) {
-            for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
-                repostOrClearNotification(userInfo.getUserHandle());
-            }
-        } else {
-            repostOrClearNotification(UserHandle.of(userHandle));
-        }
-        return null;
-    }
-
-    private void repostOrClearNotification(UserHandle userHandle) {
-        if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
-            return;
-        }
-
-        // Call out to KeyChain to check for CAs which are waiting for approval.
-        final int pendingCertificateCount;
-        try {
-            pendingCertificateCount = mService.retainAcceptedCertificates(
-                    userHandle, getInstalledCaCertificates(userHandle));
-        } catch (RemoteException | RuntimeException e) {
-            Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
-            return;
-        }
-
-        if (pendingCertificateCount != 0) {
-            showNotification(userHandle, pendingCertificateCount);
-        } else {
-            mInjector.getNotificationManager().cancelAsUser(
-                    LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
-        }
-    }
-
-    private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
-        // Create a context for the target user.
-        final Context userContext;
-        try {
-            userContext = mInjector.createContextAsUser(userHandle);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
-            return;
-        }
-
-        // Build and show a warning notification
-        int smallIconId;
-        String contentText;
-        int parentUserId = userHandle.getIdentifier();
-        Resources resources = mInjector.getResources();
-        if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
-            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
-                    mService.getProfileOwnerName(userHandle.getIdentifier()));
-            smallIconId = R.drawable.stat_sys_certificate_info;
-            parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
-        } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
-            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
-                    mService.getDeviceOwnerName());
-            smallIconId = R.drawable.stat_sys_certificate_info;
-        } else {
-            contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
-            smallIconId = android.R.drawable.stat_sys_warning;
-        }
-
-        Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
-        dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        // TODO this next line is taken from original notification code in
-        // {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
-        dialogIntent.setPackage("com.android.settings");
-        dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
-        dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
-        PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
-                dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
-                UserHandle.of(parentUserId));
-
-        final Notification noti =
-                new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
-                        .setSmallIcon(smallIconId)
-                        .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
-                                pendingCertificateCount))
-                        .setContentText(contentText)
-                        .setContentIntent(notifyIntent)
-                        .setShowWhen(false)
-                        .setColor(R.color.system_notification_accent_color)
-                        .build();
-
-        mInjector.getNotificationManager().notifyAsUser(
-                LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
-    }
-
-    private List<String> getInstalledCaCertificates(UserHandle userHandle)
-            throws RemoteException, RuntimeException {
-        try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
-            return conn.getService().getUserCaAliases().getList();
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            return null;
-        } catch (AssertionError e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 2413561..5c3a37a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -76,15 +76,28 @@
      * Internally how often should the monitor poll the security logs from logd.
      */
     private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+    /**
+     * Overlap between two subsequent log requests, required to avoid losing out of order events.
+     */
+    private static final long OVERLAP_NANOS = TimeUnit.SECONDS.toNanos(3);
+
 
     @GuardedBy("mLock")
     private Thread mMonitorThread = null;
     @GuardedBy("mLock")
-    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
+    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
     @GuardedBy("mLock")
     private boolean mAllowedToRetrieve = false;
 
     /**
+     * Last events fetched from log to check for overlap between batches. We can leave it empty if
+     * we are sure there will be no overlap anymore, e.g. when we get empty batch.
+     */
+    private final ArrayList<SecurityEvent> mLastEvents = new ArrayList<>();
+    /** Timestamp of the very last event, -1 means request from the beginning of time. */
+    private long mLastEventNanos = -1;
+
+    /**
      * When DO will be allowed to retrieve the log, in milliseconds since boot (as per
      * {@link SystemClock#elapsedRealtime()}). After that it will mark the time to retry broadcast.
      */
@@ -98,7 +111,7 @@
         mLock.lock();
         try {
             if (mMonitorThread == null) {
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -123,7 +136,7 @@
                     Log.e(TAG, "Interrupted while waiting for thread to stop", e);
                 }
                 // Reset state and clear buffer
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -181,7 +194,7 @@
     void discardLogs() {
         mLock.lock();
         mAllowedToRetrieve = false;
-        mPendingLogs = new ArrayList<SecurityEvent>();
+        mPendingLogs = new ArrayList<>();
         mLock.unlock();
         Slog.i(TAG, "Discarded all logs.");
     }
@@ -198,7 +211,7 @@
                 mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
                         + RATE_LIMIT_INTERVAL_MILLISECONDS;
                 List<SecurityEvent> result = mPendingLogs;
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 return result;
             } else {
                 return null;
@@ -208,45 +221,141 @@
         }
     }
 
+    /**
+     * Requests the next (or the first) batch of events from the log with appropriate timestamp.
+     */
+    private void getNextBatch(ArrayList<SecurityEvent> newLogs)
+            throws IOException, InterruptedException {
+        if (mLastEventNanos < 0) {
+            // Non-blocking read that returns all logs immediately.
+            if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
+            SecurityLog.readEvents(newLogs);
+        } else {
+            // If we have last events from the previous batch, request log events with time overlap
+            // with previously retrieved messages to avoid losing events due to reordering in logd.
+            final long startNanos = mLastEvents.isEmpty()
+                    ? mLastEventNanos : Math.max(0, mLastEventNanos - OVERLAP_NANOS);
+            if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + startNanos);
+            // Non-blocking read that returns all logs with timestamps >= startNanos immediately.
+            SecurityLog.readEventsSince(startNanos, newLogs);
+        }
+
+        // Sometimes events may be reordered in logd due to simultaneous readers and writers. In
+        // this case, we have to sort it to make overlap checking work. This is very unlikely.
+        for (int i = 0; i < newLogs.size() - 1; i++) {
+            if (newLogs.get(i).getTimeNanos() > newLogs.get(i+1).getTimeNanos()) {
+                if (DEBUG) Slog.d(TAG, "Got out of order events, sorting.");
+                // Sort using comparator that compares timestamps.
+                newLogs.sort((e1, e2) -> Long.signum(e1.getTimeNanos() - e2.getTimeNanos()));
+                break;
+            }
+        }
+
+        if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
+    }
+
+    /**
+     * Save the last events for overlap checking with the next batch.
+     */
+    private void saveLastEvents(ArrayList<SecurityEvent> newLogs) {
+        mLastEvents.clear();
+        if (newLogs.isEmpty()) {
+            // This can happen if no events were logged yet or the buffer got cleared. In this case
+            // we aren't going to have any overlap next time, leave mLastEvents events empty.
+            return;
+        }
+
+        // Save the last timestamp.
+        mLastEventNanos = newLogs.get(newLogs.size() - 1).getTimeNanos();
+        // Position of the earliest event that has to be saved. Start from the penultimate event,
+        // going backward.
+        int pos = newLogs.size() - 2;
+        while (pos >= 0 && mLastEventNanos - newLogs.get(pos).getTimeNanos() < OVERLAP_NANOS) {
+            pos--;
+        }
+        // We either run past the start of the list or encountered an event that is too old to keep.
+        pos++;
+        mLastEvents.addAll(newLogs.subList(pos, newLogs.size()));
+        if (DEBUG) Slog.d(TAG, mLastEvents.size() + " events saved for overlap check");
+    }
+
+    /**
+     * Merges a new batch into already fetched logs and deals with overlapping and out of order
+     * events.
+     */
+    @GuardedBy("mLock")
+    private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
+        // Reserve capacity so that copying doesn't occur.
+        mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
+        // Run through the first events of the batch to check if there is an overlap with previous
+        // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
+        // compare it in linear time by advancing two pointers, one for each batch.
+        int curPos = 0;
+        int lastPos = 0;
+        // For the first batch mLastEvents will be empty, so no iterations will happen.
+        while (lastPos < mLastEvents.size() && curPos < newLogs.size()) {
+            final SecurityEvent curEvent = newLogs.get(curPos);
+            final long currentNanos = curEvent.getTimeNanos();
+            if (currentNanos > mLastEventNanos) {
+                // We got past the last event of the last batch, no overlap possible anymore.
+                break;
+            }
+            final SecurityEvent lastEvent = mLastEvents.get(lastPos);
+            final long lastNanos = lastEvent.getTimeNanos();
+            if (lastNanos > currentNanos) {
+                // New event older than the last we've seen so far, must be due to reordering.
+                if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+                mPendingLogs.add(curEvent);
+                curPos++;
+            } else if (lastNanos < currentNanos) {
+                if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
+                lastPos++;
+            } else {
+                // Two events have the same timestamp, check if they are the same.
+                if (lastEvent.equals(curEvent)) {
+                    // Actual overlap, just skip the event.
+                    if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
+                } else {
+                    // Wow, what a coincidence, or probably the clock is too coarse.
+                    mPendingLogs.add(curEvent);
+                    if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
+                }
+                lastPos++;
+                curPos++;
+            }
+        }
+        // Save the rest of the new batch.
+        mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+
+        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
+            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
+            mPendingLogs = new ArrayList<>(mPendingLogs.subList(
+                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
+                    mPendingLogs.size()));
+            Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
+        }
+        if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+    }
+
     @Override
     public void run() {
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 
-        ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
-        // The timestamp of the latest log entry that has been read, in nanoseconds
-        long lastLogTimestampNanos = -1;
+        ArrayList<SecurityEvent> newLogs = new ArrayList<>();
         while (!Thread.currentThread().isInterrupted()) {
             try {
                 Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
+                getNextBatch(newLogs);
 
-                if (lastLogTimestampNanos < 0) {
-                    // Non-blocking read that returns all logs immediately.
-                    if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
-                    SecurityLog.readEvents(logs);
-                } else {
-                    if (DEBUG) Slog.d(TAG,
-                            "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
-                    // Non-blocking read that returns all logs >= the  timestamp immediately.
-                    SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
+                mLock.lockInterruptibly();
+                try {
+                    mergeBatchLocked(newLogs);
+                } finally {
+                    mLock.unlock();
                 }
-                if (!logs.isEmpty()) {
-                    if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
-                    mLock.lockInterruptibly();
-                    try {
-                        mPendingLogs.addAll(logs);
-                        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
-                            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
-                            mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
-                                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
-                                    mPendingLogs.size()));
-                            Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
-                        }
-                    } finally {
-                        mLock.unlock();
-                    }
-                    lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
-                    logs.clear();
-                }
+
+                saveLastEvents(newLogs);
+                newLogs.clear();
                 notifyDeviceOwnerIfNeeded();
             } catch (IOException e) {
                 Log.e(TAG, "Failed to read security log", e);
@@ -256,6 +365,15 @@
                 break;
             }
         }
+
+        // Discard previous batch info.
+        mLastEvents.clear();
+        if (mLastEventNanos != -1) {
+            // Make sure we don't read old events if logging is re-enabled. Since mLastEvents is
+            // empty, the next request will be done without overlap, so it is enough to add 1 ns.
+            mLastEventNanos += 1;
+        }
+
         Slog.i(TAG, "MonitorThread exit.");
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2727465..b77000b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -183,8 +183,6 @@
             "com.android.server.search.SearchManagerService$Lifecycle";
     private static final String THERMAL_OBSERVER_CLASS =
             "com.google.android.clockwork.ThermalObserver";
-    private static final String WEAR_BLUETOOTH_SERVICE_CLASS =
-            "com.google.android.clockwork.bluetooth.WearBluetoothService";
     private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
             "com.google.android.clockwork.connectivity.WearConnectivityService";
     private static final String WEAR_TIME_SERVICE_CLASS =
@@ -1394,9 +1392,11 @@
                 traceEnd();
             }
 
-            traceBeginAndSlog("StartCompanionDeviceManager");
-            mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
-            traceEnd();
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
+                traceBeginAndSlog("StartCompanionDeviceManager");
+                mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            }
 
             traceBeginAndSlog("StartRestrictionManager");
             mSystemServiceManager.startService(RestrictionsManagerService.class);
@@ -1479,10 +1479,6 @@
         }
 
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            traceBeginAndSlog("StartWearBluetooth");
-            mSystemServiceManager.startService(WEAR_BLUETOOTH_SERVICE_CLASS);
-            traceEnd();
-
             traceBeginAndSlog("StartWearConnectivityService");
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             traceEnd();
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 472f984..43c38a6 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -69,6 +69,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
+import com.android.server.PreloadsFileCacheExpirationJobService;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.am.ActivityManagerService;
@@ -259,6 +260,7 @@
                         if (!deletePreloadsFolderContents()) {
                             Slog.w(TAG, "Failed to delete preloads folder contents");
                         }
+                        PreloadsFileCacheExpirationJobService.schedule(mInjector.getContext());
                     });
 
                     stopDemoMode();
@@ -443,6 +445,11 @@
                 mInjector.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
+    /**
+     * Deletes contents of {@link Environment#getDataPreloadsDirectory()},
+     * but leave {@link Environment#getDataPreloadsFileCacheDirectory()}
+     * @return true if contents was sucessfully deleted
+     */
     private boolean deletePreloadsFolderContents() {
         final File dir = mInjector.getDataPreloadsDirectory();
         final File[] files = FileUtils.listFilesOrEmpty(dir);
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557..e285669 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@
     private int mPid = 2000;
     private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
 
+    private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+    private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
     private static final long[] CUSTOM_VIBRATION = new long[] {
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@
     private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
-    private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+    private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+    private static final VibrationEffect FALLBACK_VIBRATION =
+            VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
 
     @Before
     public void setUp() {
@@ -108,7 +116,7 @@
         mService.setHandler(mHandler);
         mService.setLights(mLight);
         mService.setScreenOn(false);
-        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
     }
 
     //
@@ -272,18 +280,18 @@
     }
 
     private void verifyNeverVibrate() {
-        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                anyInt(), (AudioAttributes) anyObject());
+        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyVibrate() {
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                eq(-1), (AudioAttributes) anyObject());
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyVibrateLooped() {
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                eq(0), (AudioAttributes) anyObject());
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyStopVibrate() {
@@ -485,8 +493,10 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-       verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
-                    eq(-1), (AudioAttributes) anyObject());
+        VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+                    (AudioAttributes) anyObject());
     }
 
     @Test
@@ -501,7 +511,7 @@
         mService.buzzBeepBlinkLocked(r);
 
         verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
-                eq(-1), (AudioAttributes) anyObject());
+                (AudioAttributes) anyObject());
         verify(mRingtonePlayer, never()).playAsync
                 (anyObject(), anyObject(), anyBoolean(), anyObject());
     }
@@ -667,4 +677,27 @@
         mService.buzzBeepBlinkLocked(s);
         verifyStopVibrate();
     }
+
+    static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+        private final int mRepeatIndex;
+
+        VibrateRepeatMatcher(int repeatIndex) {
+            mRepeatIndex = repeatIndex;
+        }
+
+        @Override
+        public boolean matches(VibrationEffect actual) {
+            if (actual instanceof VibrationEffect.Waveform &&
+                    ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+                return true;
+            }
+            // All non-waveform effects are essentially one shots.
+            return mRepeatIndex == -1;
+        }
+
+        @Override
+        public String toString() {
+            return "repeatIndex=" + mRepeatIndex;
+        }
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index f8a32bb..3dbd803 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -94,10 +94,12 @@
                 new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
 
         NotificationRecord r = getNotificationRecord(channel);
+        int notificationImportance = r.getImportance();
 
         extractor.process(r);
 
-        assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_UNSPECIFIED);
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, r.getUserImportance());
+        assertEquals(notificationImportance, r.getImportance());
     }
 
     @Test
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index b7b3617..ab83b9d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -59,21 +59,23 @@
 
 public class NotificationManagerServiceTest {
     private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
-    private final String pkg = "com.android.server.notification";
+    private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private final int uid = Binder.getCallingUid();
     private NotificationManagerService mNotificationManagerService;
     private INotificationManager mBinderService;
     private IPackageManager mPackageManager = mock(IPackageManager.class);
-    final PackageManager mPackageManagerClient = mock(PackageManager.class);
-    private Context mContext;
+    private final PackageManager mPackageManagerClient = mock(PackageManager.class);
+    private Context mContext = InstrumentationRegistry.getTargetContext();
+    private final String PKG = mContext.getPackageName();
     private HandlerThread mThread;
-    final RankingHelper mRankingHelper = mock(RankingHelper.class);
+    private final RankingHelper mRankingHelper = mock(RankingHelper.class);
+    private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+            TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
 
     @Before
     @Test
     @UiThreadTest
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
         mNotificationManagerService = new NotificationManagerService(mContext);
 
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
@@ -93,13 +95,16 @@
                 mock(NotificationManagerService.NotificationListeners.class);
         when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
                 mockNotificationListeners.new ManagedServiceInfo(null,
-                        new ComponentName(pkg, "test_class"), uid, true, null, 0));
+                        new ComponentName(PKG, "test_class"), uid, true, null, 0));
 
         mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
                 mPackageManagerClient, mockLightsManager, mockNotificationListeners);
 
         // Tests call directly into the Binder.
         mBinderService = mNotificationManagerService.getBinderService();
+
+        mBinderService.createNotificationChannels(
+                PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
     }
 
     public void waitForIdle() throws Exception {
@@ -127,7 +132,7 @@
     private NotificationRecord generateNotificationRecord(NotificationChannel channel,
             Notification.TvExtender extender) {
         if (channel == null) {
-            channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+            channel = mTestNotificationChannel;
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
@@ -135,8 +140,7 @@
         if (extender != null) {
             nb.extend(extender);
         }
-        StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
-                mContext.getPackageName(), 1, "tag", uid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
                 nb.build(), new UserHandle(uid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -256,38 +260,38 @@
     @Test
     @UiThreadTest
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
     }
 
     @Test
     @UiThreadTest
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
-        mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
     }
 
     @Test
     @UiThreadTest
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
         waitForIdle();
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
-        mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
     }
 
@@ -295,7 +299,7 @@
     @UiThreadTest
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
         waitForIdle();
@@ -308,9 +312,9 @@
     @UiThreadTest
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
-        mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -322,9 +326,9 @@
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
-        mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -336,7 +340,7 @@
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
         waitForIdle();
@@ -349,7 +353,7 @@
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
         waitForIdle();
@@ -362,7 +366,7 @@
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
         // Null pkg is how we signal a user switch.
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -377,14 +381,14 @@
     public void testTvExtenderChannelOverride_onTv() throws Exception {
         mNotificationManagerService.setIsTelevision(true);
         mNotificationManagerService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelWithFallback(
+        when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
                         new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
-        verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
+        verify(mRankingHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
     }
 
@@ -393,14 +397,14 @@
     public void testTvExtenderChannelOverride_notOnTv() throws Exception {
         mNotificationManagerService.setIsTelevision(false);
         mNotificationManagerService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelWithFallback(
+        when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
-                new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH));
+                mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
-        verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
-                anyString(), anyInt(), eq("id"), anyBoolean());
+        verify(mRankingHelper, times(1)).getNotificationChannel(
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
     }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 13d6c5d..946044d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -289,6 +289,18 @@
     }
 
     @Test
+    public void testImportance_locked_unspecified_preUpgrade() throws Exception {
+        defaultChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
+        defaultChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+        StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /*defaultLights */);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+        assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
+    }
+
+    @Test
     public void testImportance_upgrade() throws Exception {
         StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index fab8434..5a94018 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -78,12 +78,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RankingHelperTest {
-    @Mock
-    NotificationUsageStats mUsageStats;
-    @Mock
-    RankingHandler handler;
-    @Mock
-    PackageManager mPm;
+    private static final String PKG = "com.android.server.notification";
+    private static final int UID = 0;
+    private static final String UPDATED_PKG = "updatedPkg";
+    private static final int UID2 = 1111111;
+    private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+    @Mock NotificationUsageStats mUsageStats;
+    @Mock RankingHandler mHandler;
+    @Mock PackageManager mPm;
 
     private Notification mNotiGroupGSortA;
     private Notification mNotiGroupGSortB;
@@ -96,11 +99,6 @@
     private NotificationRecord mRecordNoGroup2;
     private NotificationRecord mRecordNoGroupSortA;
     private RankingHelper mHelper;
-    private final String pkg = "com.android.server.notification";
-    private final int uid = 0;
-    private final String pkg2 = "pkg2";
-    private final int uid2 = 1111111;
-    private static final String TEST_CHANNEL_ID = "test_channel_id";
     private AudioAttributes mAudioAttributes;
 
     private Context getContext() {
@@ -108,12 +106,12 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         UserHandle user = UserHandle.ALL;
 
-        mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
-                new String[]{ImportanceExtractor.class.getName()});
+        mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+                new String[] {ImportanceExtractor.class.getName()});
 
         mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
                 .setContentTitle("A")
@@ -170,12 +168,9 @@
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         final ApplicationInfo upgrade = new ApplicationInfo();
         upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
-        try {
-            when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
-            when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
-            when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(uid);
-        } catch (PackageManager.NameNotFoundException e) {
-        }
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+        when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+        when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
     }
 
     private NotificationChannel getDefaultChannel() {
@@ -202,6 +197,14 @@
         return baos;
     }
 
+    private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false);
+    }
+
     private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
         assertEquals(expected.getId(), actual.getId());
         assertEquals(expected.getName(), actual.getName());
@@ -289,34 +292,28 @@
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
         channel2.setLightColor(Color.BLUE);
 
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
 
-        mHelper.setShowBadge(pkg, uid, true);
-        mHelper.setShowBadge(pkg2, uid2, false);
+        mHelper.setShowBadge(PKG, UID, true);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        assertFalse(mHelper.canShowBadge(pkg2, uid2));
-        assertTrue(mHelper.canShowBadge(pkg, uid));
-        assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
         compareChannels(channel2,
-                mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
         assertNotNull(mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false));
 
         List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
         boolean foundNcg = false;
         for (NotificationChannelGroup actual : actualGroups) {
             if (ncg.getId().equals(actual.getId())) {
@@ -350,19 +347,19 @@
                 new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
         channel3.setGroup(ncg.getId());
 
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
-        mHelper.deleteNotificationChannelGroup(pkg, uid, ncg.getId());
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+        mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+        mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, true, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
                 channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
 
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -370,11 +367,11 @@
         parser.nextTag();
         mHelper.readXml(parser, true);
 
-        assertNull(mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
-        assertNull(mHelper.getNotificationChannel(pkg, uid, channel3.getId(), false));
-        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), pkg, uid));
-        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), pkg, uid));
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
     }
 
     @Test
@@ -382,19 +379,15 @@
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
 
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid,false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        final NotificationChannel updated = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
         assertFalse(updated.canBypassDnd());
         assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
@@ -405,34 +398,30 @@
     public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        defaultChannel.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
     }
 
     @Test
     public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
         final String preupgradeXml = "<ranking version=\"1\">\n"
-                + "<package name=\"" + pkg + "\" importance=\""
-                + NotificationManager.IMPORTANCE_HIGH
+                + "<package name=\"" + PKG
+                + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
                 + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
-                + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
-                + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+                + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
                 + Notification.VISIBILITY_PRIVATE + "\" />\n"
                 + "</ranking>";
         XmlPullParser parser = Xml.newPullParser();
@@ -441,30 +430,69 @@
         parser.nextTag();
         mHelper.readXml(parser, false);
 
-        final NotificationChannel updated1 = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        final NotificationChannel updated1 =
+            mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
         assertTrue(updated1.canBypassDnd());
         assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
         assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
                 | NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+                | NotificationChannel.USER_LOCKED_VISIBILITY,
+                updated1.getUserLockedFields());
 
-        final NotificationChannel updated2 = mHelper.getNotificationChannel(
-                pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        // clamped
-        assertEquals(IMPORTANCE_LOW, updated2.getImportance());
-        assertFalse(updated2.canBypassDnd());
-        assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
-        assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
+        // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+        // No Default Channel created for updated packages
+        // assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+        //         NotificationChannel.DEFAULT_CHANNEL_ID, false));
+        assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+    }
+
+    @Test
+    public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertTrue(defaultChannel != null);
+        ByteArrayOutputStream baos =
+                writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos);
+
+        // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+        // Default Channel should be gone.
+        // assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+        //         NotificationChannel.DEFAULT_CHANNEL_ID, false));
+        assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+    }
+
+    @Test
+    public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos);
+
+        // Default Channel should be gone.
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false));
     }
 
     @Test
     public void testCreateChannel_blocked() throws Exception {
-        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
+        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_NONE);
 
-        mHelper.createNotificationChannel(pkg, uid,
-                new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
     }
 
     @Test
@@ -474,16 +502,16 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -494,17 +522,17 @@
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
         channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -515,7 +543,7 @@
         channel.enableLights(false);
         channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
@@ -523,10 +551,10 @@
         channel2.enableVibration(true);
         channel2.setVibrationPattern(new long[]{100});
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -537,17 +565,17 @@
         channel.enableLights(false);
         channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.enableLights(true);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -558,17 +586,17 @@
         channel.setBypassDnd(true);
         channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setBypassDnd(false);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -579,17 +607,17 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -599,16 +627,16 @@
         channel.setShowBadge(true);
         channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setShowBadge(false);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -621,7 +649,7 @@
         channel.setBypassDnd(true);
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
@@ -631,17 +659,15 @@
         channel2.setBypassDnd(false);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
 
-        mHelper.updateNotificationChannel(pkg, uid, channel2);
+        mHelper.updateNotificationChannel(PKG, UID, channel2);
 
         // all fields should be changed
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
-    public void testGetChannelWithFallback() throws Exception {
-        NotificationChannel channel =
-                mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
-        assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
+    public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
     }
 
     @Test
@@ -659,10 +685,10 @@
         }
         channel.lockFields(lockMask);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
 
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -686,10 +712,10 @@
         }
         channel.lockFields(lockMask);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
 
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -709,16 +735,16 @@
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         // Does not return deleted channel
         NotificationChannel response =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
         assertNull(response);
 
         // Returns deleted channel
-        response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+        response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
         compareChannels(channel, response);
         assertTrue(response.isDeleted());
     }
@@ -738,14 +764,14 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
         channelMap.put(channel2.getId(), channel2);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         // Returns only non-deleted channels
         List<NotificationChannel> channels =
-                mHelper.getNotificationChannels(pkg, uid, false).getList();
+                mHelper.getNotificationChannels(PKG, UID, false).getList();
         assertEquals(2, channels.size());   // Default channel + non-deleted channel
         for (NotificationChannel nc : channels) {
             if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -754,7 +780,7 @@
         }
 
         // Returns deleted channels too
-        channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+        channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
         assertEquals(3, channels.size());               // Includes default channel
         for (NotificationChannel nc : channels) {
             if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -771,35 +797,35 @@
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
         NotificationChannel channel3 =
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
-        mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
 
-        assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
-        assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
+        assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+        assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
     }
 
     @Test
     public void testUpdateDeletedChannels() throws Exception {
         NotificationChannel channel =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         try {
-            mHelper.updateNotificationChannel(pkg, uid, channel);
+            mHelper.updateNotificationChannel(PKG, UID, channel);
             fail("Updated deleted channel");
         } catch (IllegalArgumentException e) {
             // :)
         }
 
         try {
-            mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+            mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
             fail("Updated deleted channel");
         } catch (IllegalArgumentException e) {
             // :)
@@ -813,24 +839,24 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.setVibrationPattern(vibration);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         NotificationChannel newChannel = new NotificationChannel(
                 channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
         newChannel.setVibrationPattern(new long[]{100});
 
-        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true);
 
         // No long deleted, using old settings
         compareChannels(channel,
-                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
     }
 
     @Test
     public void testCreateChannel_defaultChannelId() throws Exception {
         try {
-            mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
+            mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
                     NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
             fail("Allowed to create default channel");
         } catch (IllegalArgumentException e) {
@@ -845,26 +871,26 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.setVibrationPattern(vibration);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel newChannel = new NotificationChannel(
                 channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
         newChannel.setVibrationPattern(new long[]{100});
 
-        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true);
 
         // Old settings not overridden
         compareChannels(channel,
-                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
     }
 
     @Test
     public void testCreateChannel_addMissingSound() throws Exception {
         final NotificationChannel channel =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
         assertNotNull(mHelper.getNotificationChannel(
-                pkg, uid, channel.getId(), false).getSound());
+                PKG, UID, channel.getId(), false).getSound());
     }
 
     @Test
@@ -873,9 +899,9 @@
         final NotificationChannel channel = new NotificationChannel("id2", "name2",
                  NotificationManager.IMPORTANCE_DEFAULT);
         channel.setSound(sound, mAudioAttributes);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
         assertEquals(sound, mHelper.getNotificationChannel(
-                pkg, uid, channel.getId(), false).getSound());
+                PKG, UID, channel.getId(), false).getSound());
     }
 
     @Test
@@ -885,13 +911,13 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
 
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
 
-        mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+        mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
 
         // Only default channel remains
-        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
     }
 
     @Test
@@ -907,28 +933,28 @@
                 new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
         groupedAndDeleted.setGroup("totally");
 
-        mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, deleted, true);
-        mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true);
-        mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true);
-        mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+        mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true);
+        mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true);
 
-        mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId());
+        mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
 
-        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid));
-        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid));
+        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
 
-        assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
         compareChannels(groupedAndDeleted,
-                mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true));
+                mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
 
         compareChannels(groupedButNotDeleted,
-                mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
         compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
-                pkg, uid, nonGroupedNonDeletedChannel.getId(), false));
+                PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
 
         // notDeleted
-        assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size());
+        assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
     }
 
     @Test
@@ -936,52 +962,52 @@
         // Deleted
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+        assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
 
         // Not deleted
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
-        assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+        assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
     }
 
     @Test
     public void testOnPackageChanged_packageRemoval_importance() throws Exception {
-        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
     }
 
     @Test
     public void testOnPackageChanged_packageRemoval_groups() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size());
+        assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
     }
 
     @Test
     public void testRecordDefaults() throws Exception {
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
-        assertEquals(true, mHelper.canShowBadge(pkg, uid));
-        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+        assertEquals(true, mHelper.canShowBadge(PKG, UID));
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
     }
 
     @Test
     public void testCreateGroup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
     }
 
     @Test
@@ -990,7 +1016,7 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup("garbage");
         try {
-            mHelper.createNotificationChannel(pkg, uid, channel1, true);
+            mHelper.createNotificationChannel(PKG, UID, channel1, true);
             fail("Created a channel with a bad group");
         } catch (IllegalArgumentException e) {
         }
@@ -999,45 +1025,45 @@
     @Test
     public void testCannotCreateChannel_goodGroup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
         assertEquals(ncg.getId(),
-                mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+                mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
     }
 
     @Test
     public void testGetChannelGroups() throws Exception {
         NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
-        mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
         NotificationChannel channel1a =
                 new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1a.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1a, true);
 
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel2.setGroup(ncg2.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
 
         NotificationChannel channel3 =
                 new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
         assertEquals(3, actual.size());
         for (NotificationChannelGroup group : actual) {
             if (group.getId() == null) {
@@ -1063,19 +1089,19 @@
     @Test
     public void testGetChannelGroups_noSideEffects() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
 
         channel1.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(pkg, uid, channel1);
+        mHelper.updateNotificationChannel(PKG, UID, channel1);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
 
         assertEquals(2, actual.size());
         for (NotificationChannelGroup group : actual) {
@@ -1088,14 +1114,14 @@
     @Test
     public void testCreateChannel_updateName() throws Exception {
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(pkg, uid, nc, true);
-        NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+        mHelper.createNotificationChannel(PKG, UID, nc, true);
+        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
         assertEquals("hello", actual.getName());
 
         nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, nc, true);
+        mHelper.createNotificationChannel(PKG, UID, nc, true);
 
-        actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
         assertEquals("goodbye", actual.getName());
         assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
     }
@@ -1115,7 +1141,7 @@
             String pkgName = "pkg" + i;
             int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
             for (int j = 0; j < numChannels; j++) {
-                mHelper.createNotificationChannel(pkgName, uid,
+                mHelper.createNotificationChannel(pkgName, UID,
                         new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
             }
             expectedChannels.put(pkgName, numChannels);
@@ -1123,7 +1149,7 @@
 
         // delete the first channel of the first package
         String pkg = expectedChannels.keyAt(0);
-        mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
+        mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
         // dump should not include deleted channels
         int count = expectedChannels.get(pkg);
         expectedChannels.put(pkg, count - 1);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 6cca771..a9c69f6 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -563,6 +563,14 @@
     }
 
     @Test
+    public void testSetActiveScorer_requestNetworkScoresPermission() {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mNetworkScoreService.setActiveScorer(null);
+    }
+
+    @Test
     public void testDisableScoring_notActiveScorer_noRequestNetworkScoresPermission() {
         bindToScorer(false /*callerIsScorer*/);
         when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
@@ -577,6 +585,36 @@
     }
 
     @Test
+    public void testDisableScoring_activeScorer_noRequestNetworkScoresPermission() {
+        bindToScorer(true /*callerIsScorer*/);
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        mNetworkScoreService.disableScoring();
+    }
+
+    @Test
+    public void testGetAllValidScorer_noRequestNetworkScoresPermission() {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        try {
+            mNetworkScoreService.getAllValidScorers();
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetAllValidScorer_requestNetworkScoresPermission() {
+        when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mNetworkScoreService.getAllValidScorers();
+    }
+
+    @Test
     public void testRegisterNetworkScoreCache_noRequestNetworkScoresPermission() {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(permission.REQUEST_NETWORK_SCORES), anyString());
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e433b60..308632f 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -24,6 +24,8 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -87,6 +89,17 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+
+/**
+ * Tests for {@link AccountManagerService}.
+ * <p>Run with:<pre>
+ * mmma -j40 frameworks/base/services/tests/servicestests
+ * adb install -r ${OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * adb shell am instrument -w -e class package com.android.server.accounts \
+ * com.android.frameworks.servicestests\
+ * /android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
 public class AccountManagerServiceTest extends AndroidTestCase {
     private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
     private static final long ONE_DAY_IN_MILLISECOND = 86400000;
@@ -103,6 +116,8 @@
 
     @Captor private ArgumentCaptor<Intent> mIntentCaptor;
     @Captor private ArgumentCaptor<Bundle> mBundleCaptor;
+    private int mVisibleAccountsChangedBroadcasts;
+    private int mLoginAccountsChangedBroadcasts;
 
     private static final int LATCH_TIMEOUT_MS = 500;
     private static final String PREN_DB = "pren.db";
@@ -1042,7 +1057,7 @@
         waitForLatch(latch);
         // Verify notification is cancelled
         verify(mMockNotificationManager).cancelNotificationWithTag(
-                anyString(), anyString(), anyInt(), anyInt());
+                anyString(), nullable(String.class), anyInt(), anyInt());
 
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
         Bundle result = mBundleCaptor.getValue();
@@ -1889,7 +1904,7 @@
         waitForLatch(latch);
         // Verify notification is cancelled
         verify(mMockNotificationManager).cancelNotificationWithTag(
-                anyString(), anyString(), anyInt(), anyInt());
+                anyString(), nullable(String.class), anyInt(), anyInt());
 
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
         Bundle result = mBundleCaptor.getValue();
@@ -2446,6 +2461,161 @@
         verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
     }
 
+    @SmallTest
+    public void testRegisterAccountListener() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+
+        mAms.registerAccountListener(
+            null, //accountTypes
+            "testpackage"); // opPackageName
+
+        // Check that two previously registered receivers can be unregistered successfully.
+        mAms.unregisterAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+
+        mAms.unregisterAccountListener(
+             null, //accountTypes
+            "testpackage"); // opPackageName
+    }
+
+    @SmallTest
+    public void testRegisterAccountListenerAndAddAccount() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        // Notification about new account
+        updateBroadcastCounters(2);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+        assertEquals(mLoginAccountsChangedBroadcasts, 1);
+    }
+
+    @SmallTest
+    public void testRegisterAccountListenerAndAddAccountOfDifferentType() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_2},
+            "testpackage"); // opPackageName
+
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.addAccountExplicitly(
+            AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p11", null);
+        // Notification about new account
+
+        updateBroadcastCounters(2);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 0); // broadcast was not sent
+        assertEquals(mLoginAccountsChangedBroadcasts, 2);
+    }
+
+    @SmallTest
+    public void testRegisterAccountListenerWithAddingTwoAccounts() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.unregisterAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+        mAms.addAccountExplicitly(
+            AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p11", null);
+
+        updateBroadcastCounters(3);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+        assertEquals(mLoginAccountsChangedBroadcasts, 2);
+
+        mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS);
+        mAms.registerAccountListener( null /* accountTypes */, "testpackage");
+        mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE);
+
+        updateBroadcastCounters(6);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 2);
+        assertEquals(mLoginAccountsChangedBroadcasts, 4);
+    }
+
+    @SmallTest
+    public void testRegisterAccountListenerForThreePackages() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage1"); // opPackageName
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage2"); // opPackageName
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage3"); // opPackageName
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        updateBroadcastCounters(4);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 3);
+        assertEquals(mLoginAccountsChangedBroadcasts, 1);
+
+        mAms.unregisterAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage3"); // opPackageName
+        // Remove account with 2 active listeners.
+        mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS);
+        updateBroadcastCounters(7);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 5);
+        assertEquals(mLoginAccountsChangedBroadcasts, 2); // 3 add, 2 remove
+
+        // Add account of another type.
+        mAms.addAccountExplicitly(
+            AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS_TYPE_2, "p11", null);
+
+        updateBroadcastCounters(8);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 5);
+        assertEquals(mLoginAccountsChangedBroadcasts, 3);
+    }
+
+    @SmallTest
+    public void testRegisterAccountListenerCredentialsUpdate() throws Exception {
+        unlockSystemUser();
+        mAms.registerAccountListener(
+            new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+            "testpackage"); // opPackageName
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.setPassword(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "pwd");
+        updateBroadcastCounters(4);
+        assertEquals(mVisibleAccountsChangedBroadcasts, 2);
+        assertEquals(mLoginAccountsChangedBroadcasts, 2);
+    }
+
+    @SmallTest
+    public void testUnregisterAccountListenerNotRegistered() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.unregisterAccountListener(
+                new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+                "testpackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    private void updateBroadcastCounters (int expectedBroadcasts){
+        mVisibleAccountsChangedBroadcasts = 0;
+        mLoginAccountsChangedBroadcasts = 0;
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(expectedBroadcasts)).sendBroadcastAsUser(captor.capture(),
+            any(UserHandle.class));
+        for (Intent intent : captor.getAllValues()) {
+            if (AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED. equals(intent.getAction())) {
+                mVisibleAccountsChangedBroadcasts++;
+            }
+            if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION. equals(intent.getAction())) {
+                mLoginAccountsChangedBroadcasts++;
+            }
+        }
+    }
+
     private void waitForLatch(CountDownLatch latch) {
         try {
             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 614680e..d176a0d 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -73,6 +73,8 @@
             new Account(ACCOUNT_NAME_INTERVENE, ACCOUNT_TYPE_1);
     public static final Account ACCOUNT_ERROR =
             new Account(ACCOUNT_NAME_ERROR, ACCOUNT_TYPE_1);
+    public static final Account ACCOUNT_SUCCESS_TYPE_2 =
+            new Account(ACCOUNT_NAME_SUCCESS, ACCOUNT_TYPE_2);
 
     public static final String SESSION_DATA_NAME_1 = "session.data.name.1";
     public static final String SESSION_DATA_VALUE_1 = "session.data.value.1";
@@ -81,4 +83,4 @@
         "com.android.server.accounts.account_manager_service_test.error.message";
 
     private AccountManagerServiceTestFixtures() {}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index b5934ee..e7c91c0 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -17,9 +17,11 @@
 package com.android.server.am;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityManagerInternal;
-import android.support.test.filters.SmallTest;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -43,9 +45,15 @@
  * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
  *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
  */
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActivityManagerInternalTest {
+    private static final int TEST_UID1 = 111;
+    private static final int TEST_UID2 = 112;
+
+    private static final long TEST_PROC_STATE_SEQ1 = 1111;
+    private static final long TEST_PROC_STATE_SEQ2 = 1112;
+    private static final long TEST_PROC_STATE_SEQ3 = 1113;
+
     @Mock private ActivityManagerService.Injector mMockInjector;
 
     private ActivityManagerService mAms;
@@ -58,26 +66,149 @@
         mAmi = mAms.new LocalService();
     }
 
+    @MediumTest
     @Test
-    public void testNotifyNetworkPolicyRulesUpdated() {
-        // For checking there is no crash when there are no active uid records.
-        mAmi.notifyNetworkPolicyRulesUpdated(111, 11);
+    public void testNotifyNetworkPolicyRulesUpdated() throws Exception {
+        // Check there is no crash when there are no active uid records.
+        mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1);
 
-        // Insert active uid records.
-        final UidRecord record1 = addActiveUidRecord(222, 22);
-        final UidRecord record2 = addActiveUidRecord(333, 33);
-        // Notify that network policy rules are updated for uid 222.
-        mAmi.notifyNetworkPolicyRulesUpdated(222, 44);
-        assertEquals("UidRecord for uid 222 should be updated",
-                44L, record1.lastNetworkUpdatedProcStateSeq);
-        assertEquals("UidRecord for uid 333 should not be updated",
-                33L, record2.lastNetworkUpdatedProcStateSeq);
+        // Notify that network policy rules are updated for TEST_UID1 and verify that
+        // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ2, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+                true); // expectNotify
+
+        // Notify that network policy rules are updated for TEST_UID1 with already handled
+        // procStateSeq and verify that there is no notify call.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeq to notify
+                false); // expectNotify
+
+        // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older
+        // than it's UidRecord.curProcStateSeq and verify that there is no notify call.
+        verifyNetworkUpdatedProcStateSeq(
+                TEST_PROC_STATE_SEQ3, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+                TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+                false); // expectNotify
     }
 
-    private UidRecord addActiveUidRecord(int uid, long lastNetworkUpdatedProcStateSeq) {
+    private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq,
+            long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify)
+            throws Exception {
+        final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq,
+                lastNetworkUpdatedProcStateSeq);
+        final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq,
+                lastNetworkUpdatedProcStateSeq);
+
+        final CustomThread thread1 = new CustomThread(record1.lock);
+        thread1.startAndWait("Unexpected state for " + record1);
+        final CustomThread thread2 = new CustomThread(record2.lock);
+        thread2.startAndWait("Unexpected state for " + record2);
+
+        mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq);
+        assertEquals(record1 + " should be updated",
+                expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq);
+        assertEquals(record2 + " should not be updated",
+                lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq);
+
+        if (expectNotify) {
+            thread1.assertTerminated("Unexpected state for " + record1);
+            assertTrue("Threads waiting for network should be notified: " + record1,
+                    thread1.mNotified);
+        } else {
+            thread1.assertWaiting("Unexpected state for " + record1);
+            thread1.interrupt();
+        }
+        thread2.assertWaiting("Unexpected state for " + record2);
+        thread2.interrupt();
+
+        mAms.mActiveUids.clear();
+    }
+
+    private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
+            long lastNetworkUpdatedProcStateSeq) {
         final UidRecord record = new UidRecord(uid);
         record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+        record.curProcStateSeq = curProcStateSeq;
+        record.waitingForNetwork = true;
         mAms.mActiveUids.put(uid, record);
         return record;
     }
+
+    static class CustomThread extends Thread {
+        private static final long WAIT_TIMEOUT_MS = 1000;
+        private static final long WAIT_INTERVAL_MS = 100;
+
+        private final Object mLock;
+        private Runnable mRunnable;
+        boolean mNotified;
+
+        public CustomThread(Object lock) {
+            mLock = lock;
+        }
+
+        public CustomThread(Object lock, Runnable runnable) {
+            super(runnable);
+            mLock = lock;
+            mRunnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            if (mRunnable != null) {
+                mRunnable.run();
+            } else {
+                synchronized (mLock) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupted();
+                    }
+                }
+            }
+            mNotified = !Thread.interrupted();
+        }
+
+        public void startAndWait(String errMsg) throws Exception {
+            startAndWait(errMsg, false);
+        }
+
+        public void startAndWait(String errMsg, boolean timedWaiting) throws Exception {
+            start();
+            final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+            final Thread.State stateToReach = timedWaiting
+                    ? Thread.State.TIMED_WAITING : Thread.State.WAITING;
+            while (getState() != stateToReach
+                    && SystemClock.elapsedRealtime() < endTime) {
+                Thread.sleep(WAIT_INTERVAL_MS);
+            }
+            if (timedWaiting) {
+                assertTimedWaiting(errMsg);
+            } else {
+                assertWaiting(errMsg);
+            }
+        }
+
+        public void assertWaiting(String errMsg) {
+            assertEquals(errMsg, Thread.State.WAITING, getState());
+        }
+
+        public void assertTimedWaiting(String errMsg) {
+            assertEquals(errMsg, Thread.State.TIMED_WAITING, getState());
+        }
+
+        public void assertTerminated(String errMsg) throws Exception {
+            final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+            while (getState() != Thread.State.TERMINATED
+                    && SystemClock.elapsedRealtime() < endTime) {
+                Thread.sleep(WAIT_INTERVAL_MS);
+            }
+            assertEquals(errMsg, Thread.State.TERMINATED, getState());
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 4e9333f..cc5764b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.util.DebugUtils.valueToString;
+import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
 import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
 import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -40,11 +44,14 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.IApplicationThread;
 import android.app.IUidObserver;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -57,6 +64,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.AppOpsService;
 
 import org.junit.After;
@@ -67,6 +75,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -109,6 +118,7 @@
 
     @Mock private AppOpsService mAppOpsService;
 
+    private TestInjector mInjector;
     private ActivityManagerService mAms;
     private HandlerThread mHandlerThread;
     private TestHandler mHandler;
@@ -120,7 +130,8 @@
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new TestHandler(mHandlerThread.getLooper());
-        mAms = new ActivityManagerService(new TestInjector());
+        mInjector = new TestInjector();
+        mAms = new ActivityManagerService(mInjector);
     }
 
     @After
@@ -128,53 +139,127 @@
         mHandlerThread.quit();
     }
 
+    @MediumTest
     @Test
-    public void testIncrementProcStateSeqIfNeeded() {
+    public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception {
         final UidRecord uidRec = new UidRecord(TEST_UID);
+        uidRec.waitingForNetwork = true;
+        mAms.mActiveUids.put(TEST_UID, uidRec);
 
-        assertEquals("Initially global seq counter should be 0", 0, mAms.mProcStateSeqCounter);
-        assertEquals("Initially seq counter in uidRecord should be 0", 0, uidRec.curProcStateSeq);
+        final BatteryStatsImpl batteryStats = Mockito.mock(BatteryStatsImpl.class);
+        final ProcessRecord appRec = new ProcessRecord(batteryStats,
+                new ApplicationInfo(), TAG, TEST_UID);
+        appRec.thread = Mockito.mock(IApplicationThread.class);
+        mAms.mLruProcesses.add(appRec);
+
+        final ProcessRecord appRec2 = new ProcessRecord(batteryStats,
+                new ApplicationInfo(), TAG, TEST_UID + 1);
+        appRec2.thread = Mockito.mock(IApplicationThread.class);
+        mAms.mLruProcesses.add(appRec2);
 
         // Uid state is not moving from background to foreground or vice versa.
-        uidRec.setProcState = PROCESS_STATE_TOP;
-        uidRec.curProcState = PROCESS_STATE_TOP;
-        mAms.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(0, mAms.mProcStateSeqCounter);
-        assertEquals(0, uidRec.curProcStateSeq);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_TOP, // prevState
+                PROCESS_STATE_TOP, // curState
+                0, // expectedGlobalCounter
+                0, // exptectedCurProcStateSeq
+                NETWORK_STATE_NO_CHANGE, // expectedBlockState
+                false); // expectNotify
 
         // Uid state is moving from foreground to background.
-        uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
-        uidRec.setProcState = PROCESS_STATE_SERVICE;
-        mAms.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(1, mAms.mProcStateSeqCounter);
-        assertEquals(1, uidRec.curProcStateSeq);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+                PROCESS_STATE_SERVICE, // curState
+                1, // expectedGlobalCounter
+                1, // exptectedCurProcStateSeq
+                NETWORK_STATE_UNBLOCK, // expectedBlockState
+                true); // expectNotify
 
         // Explicitly setting the seq counter for more verification.
         mAms.mProcStateSeqCounter = 42;
 
         // Uid state is not moving from background to foreground or vice versa.
-        uidRec.setProcState = PROCESS_STATE_IMPORTANT_BACKGROUND;
-        uidRec.curProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-        mAms.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(42, mAms.mProcStateSeqCounter);
-        assertEquals(1, uidRec.curProcStateSeq);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+                PROCESS_STATE_IMPORTANT_FOREGROUND, // curState
+                42, // expectedGlobalCounter
+                1, // exptectedCurProcStateSeq
+                NETWORK_STATE_NO_CHANGE, // expectedBlockState
+                false); // expectNotify
 
         // Uid state is moving from background to foreground.
-        uidRec.setProcState = PROCESS_STATE_LAST_ACTIVITY;
-        uidRec.curProcState = PROCESS_STATE_TOP;
-        mAms.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(43, mAms.mProcStateSeqCounter);
-        assertEquals(43, uidRec.curProcStateSeq);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_LAST_ACTIVITY, // prevState
+                PROCESS_STATE_TOP, // curState
+                43, // expectedGlobalCounter
+                43, // exptectedCurProcStateSeq
+                NETWORK_STATE_BLOCK, // expectedBlockState
+                false); // expectNotify
+
+        // verify waiting threads are not notified.
+        uidRec.waitingForNetwork = false;
+        // Uid state is moving from foreground to background.
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+                PROCESS_STATE_SERVICE, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                NETWORK_STATE_UNBLOCK, // expectedBlockState
+                false); // expectNotify
+
+        // Verify when uid is not restricted, procStateSeq is not incremented.
+        uidRec.waitingForNetwork = true;
+        mInjector.setNetworkRestrictedForUid(false);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+                PROCESS_STATE_TOP, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
+    }
+
+    private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
+            int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState,
+            boolean expectNotify) throws Exception {
+        CustomThread thread = new CustomThread(uidRec.lock);
+        thread.startAndWait("Unexpected state for " + uidRec);
+
+        uidRec.setProcState = prevState;
+        uidRec.curProcState = curState;
+        mAms.incrementProcStateSeqAndNotifyAppsLocked();
+
+        assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter);
+        assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
+
+        for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) {
+            final ProcessRecord app = mAms.mLruProcesses.get(i);
+            // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
+            if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
+                verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+            } else {
+                verifyZeroInteractions(app.thread);
+            }
+            Mockito.reset(app.thread);
+        }
+
+        if (expectNotify) {
+            thread.assertTerminated("Unexpected state for " + uidRec);
+        } else {
+            thread.assertWaiting("Unexpected state for " + uidRec);
+            thread.interrupt();
+        }
     }
 
     @Test
-    public void testShouldIncrementProcStateSeq() {
+    public void testBlockStateForUid() {
         final UidRecord uidRec = new UidRecord(TEST_UID);
+        int expectedBlockState;
 
-        final String error1 = "Seq should be incremented: prevState: %s, curState: %s";
-        final String error2 = "Seq should not be incremented: prevState: %s, curState: %s";
-        Function<String, String> errorMsg = errorTemplate -> {
+        final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
+        Function<Integer, String> errorMsg = (blockState) -> {
             return String.format(errorTemplate,
+                    valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
                     valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
                     valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState));
         };
@@ -182,32 +267,44 @@
         // No change in uid state
         uidRec.setProcState = PROCESS_STATE_RECEIVER;
         uidRec.curProcState = PROCESS_STATE_RECEIVER;
-        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
 
         // Foreground to foreground
         uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
         uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
 
         // Background to background
         uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
         uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY;
-        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
 
         // Background to background
         uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
         uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY;
-        assertFalse(errorMsg.apply(error2), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_NO_CHANGE;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
 
         // Background to foreground
         uidRec.setProcState = PROCESS_STATE_SERVICE;
         uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
-        assertTrue(errorMsg.apply(error1), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_BLOCK;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
 
         // Foreground to background
         uidRec.setProcState = PROCESS_STATE_TOP;
         uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY;
-        assertTrue(errorMsg.apply(error1), mAms.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_UNBLOCK;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
     }
 
     /**
@@ -552,6 +649,81 @@
         }
     }
 
+    @MediumTest
+    @Test
+    public void testWaitForNetworkStateUpdate() throws Exception {
+        // Check there is no crash when there is no UidRecord for myUid
+        mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1);
+
+        // Verify there is no waiting when UidRecord.curProcStateSeq is greater than
+        // the procStateSeq in the request to wait.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify there is no waiting when the procStateSeq in the request to wait is
+        // not dispatched to NPMS.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify there is not waiting when the procStateSeq in the request already has
+        // an updated network state.
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                false); // expectWait
+
+        // Verify waiting for network works
+        verifyWaitingForNetworkStateUpdate(
+                TEST_PROC_STATE_SEQ1, // curProcStateSeq
+                TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+                TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+                TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+                true); // expectWait
+    }
+
+    private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
+            long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
+            final long procStateSeqToWait, boolean expectWait) throws Exception {
+        final UidRecord record = new UidRecord(Process.myUid());
+        record.curProcStateSeq = curProcStateSeq;
+        record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
+        record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+        mAms.mActiveUids.put(Process.myUid(), record);
+
+        CustomThread thread = new CustomThread(record.lock, new Runnable() {
+            @Override
+            public void run() {
+                mAms.waitForNetworkStateUpdate(procStateSeqToWait);
+            }
+        });
+        final String errMsg = "Unexpected state for " + record;
+        if (expectWait) {
+            thread.startAndWait(errMsg, true);
+            thread.assertTimedWaiting(errMsg);
+            synchronized (record.lock) {
+                record.lock.notifyAll();
+            }
+            thread.assertTerminated(errMsg);
+            assertTrue(thread.mNotified);
+            assertFalse(record.waitingForNetwork);
+        } else {
+            thread.start();
+            thread.assertTerminated(errMsg);
+        }
+
+        mAms.mActiveUids.clear();
+    }
+
     private class TestHandler extends Handler {
         private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
         private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
@@ -582,15 +754,26 @@
         }
     }
 
-    private class TestInjector implements Injector {
+    private class TestInjector extends Injector {
+        private boolean mRestricted = true;
+
         @Override
-        public AppOpsService getAppOpsService() {
+        public AppOpsService getAppOpsService(File file, Handler handler) {
             return mAppOpsService;
         }
 
         @Override
-        public Handler getHandler() {
+        public Handler getUiHandler(ActivityManagerService service) {
             return mHandler;
         }
+
+        @Override
+        public boolean isNetworkRestrictedForUid(int uid) {
+            return mRestricted;
+        }
+
+        public void setNetworkRestrictedForUid(boolean restricted) {
+            mRestricted = restricted;
+        }
     }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ca9285b..a6ce1d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -17,13 +17,18 @@
 
 import android.app.IActivityManager;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.media.IAudioService;
 import android.net.IIpConnectivityMetrics;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Looper;
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
@@ -302,6 +307,12 @@
         }
 
         @Override
+        PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
+                Intent intent, int flags, Bundle options, UserHandle user) {
+            return null;
+        }
+
+        @Override
         void registerContentObserver(Uri uri, boolean notifyForDescendents,
                 ContentObserver observer, int userHandle) {
             mContentObservers.put(new Pair<Uri, Integer>(uri, userHandle), observer);
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index d18457b..ce5b8cb 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -33,6 +33,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.RetailDemoModeServiceInternal;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -97,6 +99,7 @@
     private @Mock AudioManager mAudioManager;
     private @Mock WifiManager mWifiManager;
     private @Mock LockPatternUtils mLockPatternUtils;
+    private @Mock JobScheduler mJobScheduler;
     private MockPreloadAppsInstaller mPreloadAppsInstaller;
     private MockContentResolver mContentResolver;
     private MockContactsProvider mContactsProvider;
@@ -110,6 +113,12 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        Context originalContext = InstrumentationRegistry.getContext();
+        when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
+        when(mContext.getResources()).thenReturn(originalContext.getResources());
+        when(mContext.getSystemServiceName(eq(JobScheduler.class))).thenReturn(
+                Context.JOB_SCHEDULER_SERVICE);
+        when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
         mContentResolver = new MockContentResolver(mContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mContactsProvider = new MockContactsProvider(mContext);
@@ -218,6 +227,8 @@
         // verify that the preloaded directory is emptied.
         assertEquals("Preloads directory is not emptied",
                 0, mTestPreloadsDir.list().length);
+        // Verify that the expiration job was scheduled
+        verify(mJobScheduler).schedule(any(JobInfo.class));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 921e0e3..b5826f0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -132,9 +132,10 @@
         sWm.mDisplayEnabled = true;
 
         // Create an app window with token on a display.
-        final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+        final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
+        final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final TestAppWindowToken appWindowToken = new TestAppWindowToken(sDisplayContent);
+        final TestAppWindowToken appWindowToken = new TestAppWindowToken(defaultDisplayContent);
         task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3..290f69a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppRemoved(window.mAppToken);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -77,12 +77,12 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppDied(window.mAppToken);
 
         // Should still be in the retrieval cache.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Trash retrieval cache.
         for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@
 
         // Should not be in cache anymore
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -100,10 +100,27 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onTaskRemoved(window.getTask().mTaskId);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testReduced_notCached() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+
+        // Load it from disk
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                true /* restoreFromDisk */, true /* reducedResolution */));
+
+        // Make sure it's not in the cache now.
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -112,14 +129,14 @@
         mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Load it from disk
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                true /* restoreFromDisk */));
+                true /* restoreFromDisk */, false /* reducedResolution */));
 
         // Make sure it's in the cache now.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b5..4121447c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.Debug;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
 
-    private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
     private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
 
     @Test
@@ -58,9 +58,10 @@
         mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
         assertTrueForFiles(files, File::exists, " must exist");
-        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
         assertNotNull(snapshot);
         assertEquals(TEST_INSETS, snapshot.getContentInsets());
         assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@
         mPersister.onTaskRemovedFromRecents(1, sTestUserId);
         mPersister.waitForQueueEmpty();
         assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
-        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
     }
 
     /**
@@ -105,9 +107,10 @@
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
-        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
         assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
-        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
     }
 
     @Test
@@ -120,10 +123,12 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
         final File[] nonExistsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
         assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
     }
@@ -138,9 +143,11 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb..5e7389d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@
         Canvas c = buffer.lockCanvas();
         c.drawColor(Color.RED);
         buffer.unlockCanvasAndPost(c);
-        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+                false /* reducedResolution */, 1f /* scale */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 65efd9c..ce632ae 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,8 +73,8 @@
     private final static Session sMockSession = mock(Session.class);
     // The default display is removed in {@link #setUp} and then we iterate over all displays to
     // make sure we don't collide with any existing display. If we run into no other display, the
-    // added display should be treated as default.
-    private static int sNextDisplayId = Display.DEFAULT_DISPLAY;
+    // added display should be treated as default. This cannot be the default display
+    private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
     static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
     private static int sNextTaskId = 0;
 
@@ -105,17 +105,23 @@
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
         sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
         sLayersController = new WindowLayersController(sWm);
-        sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
-        if (sDisplayContent != null) {
-            sDisplayContent.removeImmediately();
-        }
+
         // Make sure that display ids don't overlap, so there won't be several displays with same
         // ids among RootWindowContainer children.
         for (DisplayContent dc : sWm.mRoot.mChildren) {
             if (dc.getDisplayId() >= sNextDisplayId) {
                 sNextDisplayId = dc.getDisplayId() + 1;
             }
+
+            // The default display must be preserved as some tests require it to function
+            // (such as policy rotation).
+            if (dc.getDisplayId() != Display.DEFAULT_DISPLAY) {
+                // It is safe to remove these displays as new displays will always be created with
+                // new ids.
+                dc.removeImmediately();
+            }
         }
+
         context.getDisplay().getDisplayInfo(sDisplayInfo);
         sDisplayContent = createNewDisplay();
         sWm.mDisplayEnabled = true;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 199a12a..bc5e4d5 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1897,7 +1897,7 @@
         number = extractNetworkPortionAlt(number);
 
         String emergencyNumbers = "";
-        int slotId = SubscriptionManager.getSlotId(subId);
+        int slotId = SubscriptionManager.getSlotIndex(subId);
 
         // retrieve the list of emergency numbers
         // check read-write ecclist property first
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index dd6f9cb..201f3ad 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -521,14 +521,14 @@
     }
 
     /**
-     * Get the active SubscriptionInfo associated with the slotIdx
-     * @param slotIdx the slot which the subscription is inserted
+     * Get the active SubscriptionInfo associated with the slotIndex
+     * @param slotIndex the slot which the subscription is inserted
      * @return SubscriptionInfo, maybe null if its not active
      */
-    public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx) {
-        if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIdx=" + slotIdx);
-        if (!isValidSlotId(slotIdx)) {
-            logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIdx");
+    public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) {
+        if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex);
+        if (!isValidSlotIndex(slotIndex)) {
+            logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIndex");
             return null;
         }
 
@@ -537,7 +537,7 @@
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIdx,
+                result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
                         mContext.getOpPackageName());
             }
         } catch (RemoteException ex) {
@@ -671,24 +671,24 @@
     /**
      * Add a new SubscriptionInfo to SubscriptionInfo database if needed
      * @param iccId the IccId of the SIM card
-     * @param slotId the slot which the SIM is inserted
+     * @param slotIndex the slot which the SIM is inserted
      * @return the URL of the newly created row or the updated row
      * @hide
      */
-    public Uri addSubscriptionInfoRecord(String iccId, int slotId) {
-        if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
+    public Uri addSubscriptionInfoRecord(String iccId, int slotIndex) {
+        if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotIndex:" + slotIndex);
         if (iccId == null) {
             logd("[addSubscriptionInfoRecord]- null iccId");
         }
-        if (!isValidSlotId(slotId)) {
-            logd("[addSubscriptionInfoRecord]- invalid slotId");
+        if (!isValidSlotIndex(slotIndex)) {
+            logd("[addSubscriptionInfoRecord]- invalid slotIndex");
         }
 
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
                 // FIXME: This returns 1 on success, 0 on error should should we return it?
-                iSub.addSubInfoRecord(iccId, slotId);
+                iSub.addSubInfoRecord(iccId, slotIndex);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -830,15 +830,15 @@
     }
 
     /**
-     * Get slotId associated with the subscription.
-     * @return slotId as a positive integer or a negative value if an error either
+     * Get slotIndex associated with the subscription.
+     * @return slotIndex as a positive integer or a negative value if an error either
      * SIM_NOT_INSERTED or < 0 if an invalid slot index
      * @hide
      */
-    public static int getSlotId(int subId) {
+    public static int getSlotIndex(int subId) {
         if (!isValidSubscriptionId(subId)) {
             if (DBG) {
-                logd("[getSlotId]- fail");
+                logd("[getSlotIndex]- fail");
             }
         }
 
@@ -847,7 +847,7 @@
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                result = iSub.getSlotId(subId);
+                result = iSub.getSlotIndex(subId);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -858,8 +858,8 @@
     }
 
     /** @hide */
-    public static int[] getSubId(int slotId) {
-        if (!isValidSlotId(slotId)) {
+    public static int[] getSubId(int slotIndex) {
+        if (!isValidSlotIndex(slotIndex)) {
             logd("[getSubId]- fail");
             return null;
         }
@@ -869,7 +869,7 @@
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                subId = iSub.getSubId(slotId);
+                subId = iSub.getSubId(slotIndex);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -1155,8 +1155,8 @@
     }
 
     /** @hide */
-    public static boolean isValidSlotId(int slotId) {
-        return slotId >= 0 && slotId < TelephonyManager.getDefault().getSimCount();
+    public static boolean isValidSlotIndex(int slotIndex) {
+        return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSimCount();
     }
 
     /** @hide */
@@ -1179,7 +1179,7 @@
         if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
-        //FIXME this is using phoneId and slotId interchangeably
+        //FIXME this is using phoneId and slotIndex interchangeably
         //Eventually, this should be removed as it is not the slot id
         intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
     }
@@ -1228,9 +1228,9 @@
     }
 
     /**
-     * Returns a constant indicating the state of sim for the slot idx.
+     * Returns a constant indicating the state of sim for the slot index.
      *
-     * @param slotIdx
+     * @param slotIndex
      *
      * {@See TelephonyManager#SIM_STATE_UNKNOWN}
      * {@See TelephonyManager#SIM_STATE_ABSENT}
@@ -1244,13 +1244,13 @@
      *
      * {@hide}
      */
-    public static int getSimStateForSlotIdx(int slotIdx) {
+    public static int getSimStateForSlotIndex(int slotIndex) {
         int simState = TelephonyManager.SIM_STATE_UNKNOWN;
 
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                simState = iSub.getSimStateForSlotIdx(slotIdx);
+                simState = iSub.getSimStateForSlotIndex(slotIndex);
             }
         } catch (RemoteException ex) {
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4dda766..0eaa359 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -918,15 +918,15 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
-     * @param slotId of which deviceID is returned
+     * @param slotIndex of which deviceID is returned
      */
     /** {@hide} */
-    public String getDeviceSoftwareVersion(int slotId) {
+    public String getDeviceSoftwareVersion(int slotIndex) {
         ITelephony telephony = getITelephony();
         if (telephony == null) return null;
 
         try {
-            return telephony.getDeviceSoftwareVersionForSlot(slotId, getOpPackageName());
+            return telephony.getDeviceSoftwareVersionForSlot(slotIndex, getOpPackageName());
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -961,15 +961,15 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
-     * @param slotId of which deviceID is returned
+     * @param slotIndex of which deviceID is returned
      */
-    public String getDeviceId(int slotId) {
-        // FIXME this assumes phoneId == slotId
+    public String getDeviceId(int slotIndex) {
+        // FIXME this assumes phoneId == slotIndex
         try {
             IPhoneSubInfo info = getSubscriberInfo();
             if (info == null)
                 return null;
-            return info.getDeviceIdForPhone(slotId, mContext.getOpPackageName());
+            return info.getDeviceIdForPhone(slotIndex, mContext.getOpPackageName());
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -996,17 +996,17 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
-     * @param slotId of which deviceID is returned
+     * @param slotIndex of which deviceID is returned
      *
      * @hide
      */
     @SystemApi
-    public String getImei(int slotId) {
+    public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
         if (telephony == null) return null;
 
         try {
-            return telephony.getImeiForSlot(slotId, getOpPackageName());
+            return telephony.getImeiForSlot(slotIndex, getOpPackageName());
         } catch (RemoteException ex) {
             return null;
         } catch (NullPointerException ex) {
@@ -1026,11 +1026,11 @@
     /**
      * Returns the NAI. Return null if NAI is not available.
      *
-     *  @param slotId of which Nai is returned
+     *  @param slotIndex of which Nai is returned
      */
     /** {@hide}*/
-    public String getNai(int slotId) {
-        int[] subId = SubscriptionManager.getSubId(slotId);
+    public String getNai(int slotIndex) {
+        int[] subId = SubscriptionManager.getSubId(slotIndex);
         try {
             IPhoneSubInfo info = getSubscriberInfo();
             if (info == null)
@@ -1226,23 +1226,23 @@
      *
      * @hide
      */
-    public int getCurrentPhoneTypeForSlot(int slotId) {
+    public int getCurrentPhoneTypeForSlot(int slotIndex) {
         try{
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getActivePhoneTypeForSlot(slotId);
+                return telephony.getActivePhoneTypeForSlot(slotIndex);
             } else {
                 // This can happen when the ITelephony interface is not up yet.
-                return getPhoneTypeFromProperty(slotId);
+                return getPhoneTypeFromProperty(slotIndex);
             }
         } catch (RemoteException ex) {
             // This shouldn't happen in the normal case, as a backup we
             // read from the system property.
-            return getPhoneTypeFromProperty(slotId);
+            return getPhoneTypeFromProperty(slotIndex);
         } catch (NullPointerException ex) {
             // This shouldn't happen in the normal case, as a backup we
             // read from the system property.
-            return getPhoneTypeFromProperty(slotId);
+            return getPhoneTypeFromProperty(slotIndex);
         }
     }
 
@@ -1962,17 +1962,17 @@
     /**
      * @return true if a ICC card is present for a subscription
      *
-     * @param slotId for which icc card presence is checked
+     * @param slotIndex for which icc card presence is checked
      */
     /** {@hide} */
-    // FIXME Input argument slotId should be of type int
-    public boolean hasIccCard(int slotId) {
+    // FIXME Input argument slotIndex should be of type int
+    public boolean hasIccCard(int slotIndex) {
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return false;
-            return telephony.hasIccCardUsingSlotId(slotId);
+            return telephony.hasIccCardUsingSlotIndex(slotIndex);
         } catch (RemoteException ex) {
             // Assume no ICC card if remote exception which shouldn't happen
             return false;
@@ -1997,31 +1997,31 @@
      * @see #SIM_STATE_CARD_RESTRICTED
      */
     public int getSimState() {
-        int slotIdx = getDefaultSim();
-        // slotIdx may be invalid due to sim being absent. In that case query all slots to get
+        int slotIndex = getDefaultSim();
+        // slotIndex may be invalid due to sim being absent. In that case query all slots to get
         // sim state
-        if (slotIdx < 0) {
+        if (slotIndex < 0) {
             // query for all slots and return absent if all sim states are absent, otherwise
             // return unknown
             for (int i = 0; i < getPhoneCount(); i++) {
                 int simState = getSimState(i);
                 if (simState != SIM_STATE_ABSENT) {
-                    Rlog.d(TAG, "getSimState: default sim:" + slotIdx + ", sim state for " +
-                            "slotIdx=" + i + " is " + simState + ", return state as unknown");
+                    Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", sim state for " +
+                            "slotIndex=" + i + " is " + simState + ", return state as unknown");
                     return SIM_STATE_UNKNOWN;
                 }
             }
-            Rlog.d(TAG, "getSimState: default sim:" + slotIdx + ", all SIMs absent, return " +
+            Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", all SIMs absent, return " +
                     "state as absent");
             return SIM_STATE_ABSENT;
         }
-        return getSimState(slotIdx);
+        return getSimState(slotIndex);
     }
 
     /**
      * Returns a constant indicating the state of the device SIM card in a slot.
      *
-     * @param slotIdx
+     * @param slotIndex
      *
      * @see #SIM_STATE_UNKNOWN
      * @see #SIM_STATE_ABSENT
@@ -2034,8 +2034,8 @@
      * @see #SIM_STATE_CARD_IO_ERROR
      * @see #SIM_STATE_CARD_RESTRICTED
      */
-    public int getSimState(int slotIdx) {
-        int simState = SubscriptionManager.getSimStateForSlotIdx(slotIdx);
+    public int getSimState(int slotIndex) {
+        int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
         return simState;
     }
 
@@ -3210,12 +3210,12 @@
      *
      * @hide
      */
-    public int getCallStateForSlot(int slotId) {
+    public int getCallStateForSlot(int slotIndex) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return CALL_STATE_IDLE;
-            return telephony.getCallStateForSlot(slotId);
+            return telephony.getCallStateForSlot(slotIndex);
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return CALL_STATE_IDLE;
@@ -4033,7 +4033,7 @@
 
     /** {@hide} */
     public int getDefaultSim() {
-        return SubscriptionManager.getSlotId(SubscriptionManager.getDefaultSubscriptionId());
+        return SubscriptionManager.getSlotIndex(SubscriptionManager.getDefaultSubscriptionId());
     }
 
     /**
@@ -4398,7 +4398,7 @@
      * feature or {@link null} if the service is not available. If an ImsServiceController is
      * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
      * feature updates.
-     * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
+     * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for.
      * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
      * @param callback Listener that will send updates to ImsManager when there are updates to
      * ImsServiceController.
@@ -4406,12 +4406,12 @@
      * it is unavailable.
      * @hide
      */
-    public IImsServiceController getImsServiceControllerAndListen(int slotId, @Feature int feature,
+    public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature,
             IImsServiceFeatureListener callback) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getImsServiceControllerAndListen(slotId, feature, callback);
+                return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback);
             }
         } catch (RemoteException e) {
             Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
@@ -5641,7 +5641,7 @@
     /**
      * Set SIM card power state. Request is equivalent to inserting or removing the card.
      *
-     * @param slotId SIM slot id
+     * @param slotIndex SIM slot id
      * @param powerUp True if powering up the SIM, otherwise powering down
      *
      * <p>Requires Permission:
@@ -5649,11 +5649,11 @@
      *
      * @hide
      **/
-    public void setSimPowerStateForSlot(int slotId, boolean powerUp) {
+    public void setSimPowerStateForSlot(int slotIndex, boolean powerUp) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.setSimPowerStateForSlot(slotId, powerUp);
+                telephony.setSimPowerStateForSlot(slotIndex, powerUp);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
@@ -6237,7 +6237,7 @@
     }
 
     /**
-     * Set the allowed carrier list for slotId
+     * Set the allowed carrier list for slotIndex
      * Require system privileges. In the future we may add this to carrier APIs.
      *
      * <p>Requires Permission:
@@ -6251,11 +6251,11 @@
      * @hide
      */
     @SystemApi
-    public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
+    public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.setAllowedCarriers(slotId, carriers);
+                return service.setAllowedCarriers(slotIndex, carriers);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
@@ -6266,7 +6266,7 @@
     }
 
     /**
-     * Get the allowed carrier list for slotId.
+     * Get the allowed carrier list for slotIndex.
      * Require system privileges. In the future we may add this to carrier APIs.
      *
      * <p>Requires Permission:
@@ -6280,11 +6280,11 @@
      * @hide
      */
     @SystemApi
-    public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
+    public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.getAllowedCarriers(slotId);
+                return service.getAllowedCarriers(slotIndex);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index f6aef08..71f2c6b 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,12 +52,12 @@
     SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage);
 
     /**
-     * Get the active SubscriptionInfo associated with the slotIdx
-     * @param slotIdx the slot which the subscription is inserted
+     * Get the active SubscriptionInfo associated with the slotIndex
+     * @param slotIndex the slot which the subscription is inserted
      * @param callingPackage The package maing the call.
      * @return SubscriptionInfo, maybe null if its not active
      */
-    SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx, String callingPackage);
+    SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
 
     /**
      * Get the SubscriptionInfo(s) of the active subscriptions. The records will be sorted
@@ -96,10 +96,10 @@
     /**
      * Add a new SubscriptionInfo to subinfo database if needed
      * @param iccId the IccId of the SIM card
-     * @param slotId the slot which the SIM is inserted
+     * @param slotIndex the slot which the SIM is inserted
      * @return the URL of the newly created row or the updated row
      */
-    int addSubInfoRecord(String iccId, int slotId);
+    int addSubInfoRecord(String iccId, int slotIndex);
 
     /**
      * Set SIM icon tint color by simInfo index
@@ -142,9 +142,9 @@
      */
     int setDataRoaming(int roaming, int subId);
 
-    int getSlotId(int subId);
+    int getSlotIndex(int subId);
 
-    int[] getSubId(int slotId);
+    int[] getSubId(int slotIndex);
 
     int getDefaultSubId();
 
@@ -177,10 +177,10 @@
     String getSubscriptionProperty(int subId, String propKey, String callingPackage);
 
     /**
-     * Get the SIM state for the slot idx
+     * Get the SIM state for the slot index
      * @return SIM state as the ordinal of IccCardConstants.State
      */
-    int getSimStateForSlotIdx(int slotIdx);
+    int getSimStateForSlotIndex(int slotIndex);
 
     boolean isActiveSubId(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 40d1dbb..9d12c24 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -381,7 +381,7 @@
     /**
      * Returns the call state for a slot.
      */
-     int getCallStateForSlot(int slotId);
+     int getCallStateForSlot(int slotIndex);
 
      int getDataActivity();
      int getDataState();
@@ -397,9 +397,9 @@
      * Returns the current active phone type as integer for particular slot.
      * Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE
      * and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE
-     * @param slotId - slot to query.
+     * @param slotIndex - slot to query.
      */
-    int getActivePhoneTypeForSlot(int slotId);
+    int getActivePhoneTypeForSlot(int slotIndex);
 
     /**
      * Returns the CDMA ERI icon index to display
@@ -573,10 +573,10 @@
 
     /**
      * Return true if an ICC card is present for a subId.
-     * @param slotId user preferred slotId.
+     * @param slotIndex user preferred slotIndex.
      * Return true if an ICC card is present
      */
-    boolean hasIccCardUsingSlotId(int slotId);
+    boolean hasIccCardUsingSlotIndex(int slotIndex);
 
     /**
      * Return if the current radio is LTE on CDMA. This
@@ -777,7 +777,7 @@
      *  requested as well as registering the ImsServiceController for callbacks using the
      *  IImsServiceFeatureListener interface.
      */
-    IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+    IImsServiceController getImsServiceControllerAndListen(int slotIndex, int feature,
                 IImsServiceFeatureListener callback);
 
     /**
@@ -1085,22 +1085,22 @@
     /**
      * Returns the IMEI for the given slot.
      *
-     * @param slotId - device slot.
+     * @param slotIndex - device slot.
      * @param callingPackage The package making the call.
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
-    String getImeiForSlot(int slotId, String callingPackage);
+    String getImeiForSlot(int slotIndex, String callingPackage);
 
     /**
      * Returns the device software version.
      *
-     * @param slotId - device slot.
+     * @param slotIndex - device slot.
      * @param callingPackage The package making the call.
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
-    String getDeviceSoftwareVersionForSlot(int slotId, String callingPackage);
+    String getDeviceSoftwareVersionForSlot(int slotIndex, String callingPackage);
 
     /**
      * Returns the subscription ID associated with the specified PhoneAccount.
@@ -1226,22 +1226,22 @@
     List<TelephonyHistogram> getTelephonyHistograms();
 
     /**
-     * Set the allowed carrier list for slotId
+     * Set the allowed carrier list for slotIndex
      * Require system privileges. In the future we may add this to carrier APIs.
      *
      * @return The number of carriers set successfully. Should match length of
      * carriers on success.
      */
-    int setAllowedCarriers(int slotId, in List<CarrierIdentifier> carriers);
+    int setAllowedCarriers(int slotIndex, in List<CarrierIdentifier> carriers);
 
     /**
-     * Get the allowed carrier list for slotId.
+     * Get the allowed carrier list for slotIndex.
      * Require system privileges. In the future we may add this to carrier APIs.
      *
      * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
      * means all carriers are allowed.
      */
-    List<CarrierIdentifier> getAllowedCarriers(int slotId);
+    List<CarrierIdentifier> getAllowedCarriers(int slotIndex);
 
     /**
      * Action set from carrier signalling broadcast receivers to enable/disable metered apns
@@ -1288,11 +1288,11 @@
 
     /**
      * Set SIM card power state. Request is equivalent to inserting or removing the card.
-     * @param slotId SIM slot id
+     * @param slotIndex SIM slot id
      * @param powerUp True if powering up the SIM, otherwise powering down
      * @hide
      * */
-    void setSimPowerStateForSlot(int slotId, boolean powerUp);
+    void setSimPowerStateForSlot(int slotIndex, boolean powerUp);
 
     /**
      * Returns a list of Forbidden PLMNs from the specified SIM App
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index 15ed810..6bf22a0 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -16,10 +16,19 @@
 
 package com.android.internal.telephony.gsm;
 
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+
+import android.content.Context;
+import android.content.res.Resources;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.util.Pair;
 
+import com.android.internal.R;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsConstants;
 
@@ -55,23 +64,49 @@
     private GsmSmsCbMessage() { }
 
     /**
+     * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
+     * so we have to show the pre-built messages to the user.
+     *
+     * @param context Device context
+     * @param category ETWS message category defined in SmsCbConstants
+     * @return ETWS text message in string. Return an empty string if no match.
+     */
+    private static String getEtwsPrimaryMessage(Context context, int category) {
+        final Resources r = context.getResources();
+        switch (category) {
+            case ETWS_WARNING_TYPE_EARTHQUAKE:
+                return r.getString(R.string.etws_primary_default_message_earthquake);
+            case ETWS_WARNING_TYPE_TSUNAMI:
+                return r.getString(R.string.etws_primary_default_message_tsunami);
+            case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+                return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
+            case ETWS_WARNING_TYPE_TEST_MESSAGE:
+                return r.getString(R.string.etws_primary_default_message_test);
+            case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+                return r.getString(R.string.etws_primary_default_message_others);
+            default:
+                return "";
+        }
+    }
+
+    /**
      * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
      *
      * @param pdus PDU bytes
      */
-    public static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
-            byte[][] pdus) throws IllegalArgumentException {
+    public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
+                                                  SmsCbLocation location, byte[][] pdus)
+            throws IllegalArgumentException {
         if (header.isEtwsPrimaryNotification()) {
             // ETSI TS 23.041 ETWS Primary Notification message
             // ETWS primary message only contains 4 fields including serial number,
             // message identifier, warning type, and warning security information.
-            // There is no field for the content/text. We hardcode "ETWS" in the
-            // text body so the user won't see an empty dialog without any text.
-            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
-                    header.getGeographicalScope(), header.getSerialNumber(),
-                    location, header.getServiceCategory(),
-                    null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
-                    header.getEtwsInfo(), header.getCmasInfo());
+            // There is no field for the content/text so we get the text from the resources.
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
+                    header.getSerialNumber(), location, header.getServiceCategory(), null,
+                    getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
+                    SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
+                    header.getCmasInfo());
         } else {
             String language = null;
             StringBuilder sb = new StringBuilder();
@@ -91,19 +126,6 @@
     }
 
     /**
-     * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
-     * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
-     *
-     * @param location the location (geographical scope) for the message
-     * @param pdus PDU bytes
-     */
-    public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
-            throws IllegalArgumentException {
-        SmsCbHeader header = new SmsCbHeader(pdus[0]);
-        return createSmsCbMessage(header, location, pdus);
-    }
-
-    /**
      * Parse and unpack the body text according to the encoding in the DCS.
      * After completing successfully this method will have assigned the body
      * text into mBody, and optionally the language code into mLanguage
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94..2757296 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.VibrationEffect;
 import android.test.suitebuilder.annotation.SmallTest;
 
 /**
@@ -48,7 +49,9 @@
      */
     public void testVibrate() throws RemoteException {
         try {
-            mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+            final VibrationEffect effect =
+                    VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+            mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
                     new Binder());
             fail("vibrate did not throw SecurityException as expected");
         } catch (SecurityException e) {
@@ -57,23 +60,6 @@
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
-     * int, android.os.IBinder)} requires permissions.
-     * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
-     */
-    public void testVibratePattern() throws RemoteException {
-        try {
-            mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
-                    AudioManager.STREAM_ALARM, new Binder());
-            fail("vibratePattern did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
      * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
      * <p>Tests permission:
      *   {@link android.Manifest.permission#VIBRATE}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 11328dc..e118889 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -174,6 +174,12 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+        // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+        return null;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
         Typeface_Delegate delegate = sManager.getDelegate(native_instance);
         if (delegate == null) {