Merge "Allow the Instant App installer to be dynamic"
diff --git a/Android.mk b/Android.mk
index 03b2533..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,10 @@
     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
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := stream
 LOCAL_PROTOC_FLAGS := \
@@ -988,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)
@@ -1189,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 aab29be..6ecf213 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -608,6 +608,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderCerts = 16844128; // 0x1010560
     field public static final int fontProviderPackage = 16844122; // 0x101055a
     field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844095; // 0x101053f
@@ -1179,6 +1180,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
@@ -4167,6 +4169,7 @@
     field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
     field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
     field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+    field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
     field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
     field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
     field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -4843,6 +4846,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);
@@ -5503,6 +5507,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method public java.lang.String getDescription();
     method public java.lang.String getGroup();
     method public java.lang.String getId();
     method public int getImportance();
@@ -5512,6 +5517,7 @@
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public void setBypassDnd(boolean);
+    method public void setDescription(java.lang.String);
     method public void setGroup(java.lang.String);
     method public void setImportance(int);
     method public void setLightColor(int);
@@ -5705,7 +5711,7 @@
     method public android.app.RemoteAction getUserAction();
     method public java.lang.CharSequence getUserMessage();
     method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context);
+    method public void showAsNotification(android.content.Context, java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
   }
@@ -6748,6 +6754,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
@@ -6775,6 +6782,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);
@@ -7939,10 +7947,10 @@
 
   public final class AdvertisingSet {
     method public void enableAdvertising(boolean, int);
-    method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+    method public void setPeriodicAdvertisingEnable(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -7951,8 +7959,8 @@
     ctor public AdvertisingSetCallback();
     method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
-    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
-    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
     method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
     method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -7976,6 +7984,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
@@ -8003,6 +8012,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);
   }
@@ -9478,7 +9488,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";
@@ -10620,6 +10630,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";
@@ -13230,7 +13241,7 @@
     method public void setFilterBitmap(boolean);
     method public void setFlags(int);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method public void setHinting(int);
     method public void setLetterSpacing(float);
     method public void setLinearText(boolean);
@@ -31555,6 +31566,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);
@@ -31654,13 +31675,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 {
@@ -32000,6 +32033,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();
@@ -32041,6 +32075,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);
@@ -36837,9 +36872,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 {
@@ -39945,7 +39982,6 @@
     method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
-    method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45093,7 +45129,7 @@
     method public android.view.ViewPropertyAnimator animate();
     method public void announceForAccessibility(java.lang.CharSequence);
     method public boolean autofill(android.view.autofill.AutofillValue);
-    method public boolean autofill(int, android.view.autofill.AutofillValue);
+    method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
     method protected boolean awakenScrollBars();
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
@@ -46308,7 +46344,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 +46352,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 +46375,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);
@@ -51263,7 +51299,7 @@
     method public void setExtractedText(android.view.inputmethod.ExtractedText);
     method public void setFilters(android.text.InputFilter[]);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method protected boolean setFrame(int, int, int, int);
     method public void setFreezesText(boolean);
     method public void setGravity(int);
diff --git a/api/removed.txt b/api/removed.txt
index 148f3f1..04c9c35 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -4,6 +4,10 @@
     method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
   }
 
+  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+    method public deprecated void showAsNotification(android.content.Context);
+  }
+
 }
 
 package android.app.admin {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e6717c..2303433 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -721,6 +721,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderCerts = 16844128; // 0x1010560
     field public static final int fontProviderPackage = 16844122; // 0x101055a
     field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844095; // 0x101053f
@@ -1296,6 +1297,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
@@ -4310,6 +4312,7 @@
     field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
     field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
     field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+    field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
     field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
     field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
     field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5002,10 +5005,8 @@
     ctor public InstantAppResolverService();
     method public final void attachBaseContext(android.content.Context);
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onGetInstantAppIntentFilter(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
-    method public void onGetInstantAppResolveInfo(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
-    field public static final java.lang.String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
-    field public static final java.lang.String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
+    method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
+    method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
   }
 
   public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -5014,6 +5015,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);
@@ -5690,6 +5692,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method public java.lang.String getDescription();
     method public java.lang.String getGroup();
     method public java.lang.String getId();
     method public int getImportance();
@@ -5702,6 +5705,7 @@
     method public boolean isDeleted();
     method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
     method public void setBypassDnd(boolean);
+    method public void setDescription(java.lang.String);
     method public void setGroup(java.lang.String);
     method public void setImportance(int);
     method public void setLightColor(int);
@@ -5717,16 +5721,6 @@
     method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
     field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
-    field public static final int[] LOCKABLE_FIELDS;
-    field public static final int USER_LOCKED_ALLOWED = 64; // 0x40
-    field public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 256; // 0x100
-    field public static final int USER_LOCKED_IMPORTANCE = 4; // 0x4
-    field public static final int USER_LOCKED_LIGHTS = 8; // 0x8
-    field public static final int USER_LOCKED_PRIORITY = 1; // 0x1
-    field public static final int USER_LOCKED_SHOW_BADGE = 128; // 0x80
-    field public static final int USER_LOCKED_SOUND = 32; // 0x20
-    field public static final int USER_LOCKED_VIBRATION = 16; // 0x10
-    field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
   }
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -5908,7 +5902,7 @@
     method public android.app.RemoteAction getUserAction();
     method public java.lang.CharSequence getUserMessage();
     method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context);
+    method public void showAsNotification(android.content.Context, java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
   }
@@ -7184,6 +7178,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
@@ -7211,6 +7206,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);
@@ -8414,10 +8410,10 @@
 
   public final class AdvertisingSet {
     method public void enableAdvertising(boolean, int);
-    method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+    method public void setPeriodicAdvertisingEnable(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -8426,8 +8422,8 @@
     ctor public AdvertisingSetCallback();
     method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
-    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
-    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
     method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
     method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -8451,6 +8447,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
@@ -8478,6 +8475,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);
   }
@@ -10015,7 +10013,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";
@@ -11278,6 +11276,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";
@@ -13965,7 +13964,7 @@
     method public void setFilterBitmap(boolean);
     method public void setFlags(int);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method public void setHinting(int);
     method public void setLetterSpacing(float);
     method public void setLinearText(boolean);
@@ -34318,6 +34317,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);
@@ -34494,13 +34503,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 {
@@ -34852,6 +34873,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();
@@ -34893,6 +34915,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);
@@ -39873,9 +39896,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 {
@@ -43362,7 +43387,6 @@
     method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
-    method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method public void setDataEnabled(boolean);
     method public void setDataEnabled(int, boolean);
@@ -48558,7 +48582,7 @@
     method public android.view.ViewPropertyAnimator animate();
     method public void announceForAccessibility(java.lang.CharSequence);
     method public boolean autofill(android.view.autofill.AutofillValue);
-    method public boolean autofill(int, android.view.autofill.AutofillValue);
+    method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
     method protected boolean awakenScrollBars();
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
@@ -49773,7 +49797,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();
@@ -49782,11 +49805,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);
@@ -49805,6 +49828,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);
@@ -55092,7 +55116,7 @@
     method public void setExtractedText(android.view.inputmethod.ExtractedText);
     method public void setFilters(android.text.InputFilter[]);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method protected boolean setFrame(int, int, int, int);
     method public void setFreezesText(boolean);
     method public void setGravity(int);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index bd535d2..640dc81 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -4,6 +4,10 @@
     method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
   }
 
+  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+    method public deprecated void showAsNotification(android.content.Context);
+  }
+
 }
 
 package android.app.admin {
diff --git a/api/test-current.txt b/api/test-current.txt
index f91bbb9..80e1002 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -608,6 +608,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderCerts = 16844128; // 0x1010560
     field public static final int fontProviderPackage = 16844122; // 0x101055a
     field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844095; // 0x101053f
@@ -1179,6 +1180,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
@@ -4177,6 +4179,7 @@
     field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
     field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
     field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+    field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
     field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
     field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
     field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -4853,6 +4856,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);
@@ -5513,6 +5517,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method public java.lang.String getDescription();
     method public java.lang.String getGroup();
     method public java.lang.String getId();
     method public int getImportance();
@@ -5522,6 +5527,7 @@
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public void setBypassDnd(boolean);
+    method public void setDescription(java.lang.String);
     method public void setGroup(java.lang.String);
     method public void setImportance(int);
     method public void setLightColor(int);
@@ -5716,7 +5722,7 @@
     method public android.app.RemoteAction getUserAction();
     method public java.lang.CharSequence getUserMessage();
     method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context);
+    method public void showAsNotification(android.content.Context, java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
   }
@@ -6775,6 +6781,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
@@ -6802,6 +6809,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);
@@ -7966,10 +7974,10 @@
 
   public final class AdvertisingSet {
     method public void enableAdvertising(boolean, int);
-    method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+    method public void setPeriodicAdvertisingEnable(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -7978,8 +7986,8 @@
     ctor public AdvertisingSetCallback();
     method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
-    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
-    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
     method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
     method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
     method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -8003,6 +8011,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
@@ -8030,6 +8039,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);
   }
@@ -9508,7 +9518,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";
@@ -10656,6 +10666,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";
@@ -13268,7 +13279,7 @@
     method public void setFilterBitmap(boolean);
     method public void setFlags(int);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method public void setHinting(int);
     method public void setLetterSpacing(float);
     method public void setLinearText(boolean);
@@ -31678,6 +31689,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);
@@ -31779,13 +31800,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 {
@@ -32125,6 +32158,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();
@@ -32166,6 +32200,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);
@@ -36983,9 +37018,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 {
@@ -40136,7 +40173,6 @@
     method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
-    method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45455,7 +45491,7 @@
     method public android.view.ViewPropertyAnimator animate();
     method public void announceForAccessibility(java.lang.CharSequence);
     method public boolean autofill(android.view.autofill.AutofillValue);
-    method public boolean autofill(int, android.view.autofill.AutofillValue);
+    method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
     method protected boolean awakenScrollBars();
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
@@ -46677,7 +46713,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 +46721,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 +46744,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);
@@ -51641,7 +51677,7 @@
     method public void setExtractedText(android.view.inputmethod.ExtractedText);
     method public void setFilters(android.text.InputFilter[]);
     method public void setFontFeatureSettings(java.lang.String);
-    method public void setFontVariationSettings(java.lang.String);
+    method public boolean setFontVariationSettings(java.lang.String);
     method protected boolean setFrame(int, int, int, int);
     method public void setFreezesText(boolean);
     method public void setGravity(int);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 148f3f1..04c9c35 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -4,6 +4,10 @@
     method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
   }
 
+  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+    method public deprecated void showAsNotification(android.content.Context);
+  }
+
 }
 
 package android.app.admin {
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/Activity.java b/core/java/android/app/Activity.java
index 37a11ec..07540f3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7193,6 +7193,7 @@
         final View root = getWindow().getDecorView();
         final int itemCount = ids.size();
         int numApplied = 0;
+        ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
 
         for (int i = 0; i < itemCount; i++) {
             final AutofillId id = ids.get(i);
@@ -7203,19 +7204,37 @@
                 Log.w(TAG, "autofill(): no View with id " + viewId);
                 continue;
             }
-            final boolean wasApplied;
             if (id.isVirtual()) {
-                wasApplied = view.autofill(id.getVirtualChildId(), value);
+                final int parentId = id.getViewId();
+                if (virtualValues == null) {
+                    // Most likely there will be just one view with virtual children.
+                    virtualValues = new ArrayMap<>(1);
+                }
+                SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+                if (valuesByParent == null) {
+                    // We don't know the size yet, but usually it will be just a few fields...
+                    valuesByParent = new SparseArray<>(5);
+                    virtualValues.put(view, valuesByParent);
+                }
+                valuesByParent.put(id.getVirtualChildId(), value);
             } else {
-                wasApplied = view.autofill(value);
-            }
-
-            if (wasApplied) {
-                numApplied++;
+                if (view.autofill(value)) {
+                    numApplied++;
+                }
             }
         }
 
-        LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+        if (virtualValues != null) {
+            for (int i = 0; i < virtualValues.size(); i++) {
+                final View parent = virtualValues.keyAt(i);
+                final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+                if (parent.autofill(childrenValues)) {
+                    numApplied += childrenValues.size();
+                }
+            }
+        }
+
+        final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
         log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
         log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
         mMetricsLogger.write(log);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 043e0ab..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>() {
@@ -4060,6 +4094,10 @@
      * thread can be a VR thread in a process at a time, and that thread may be subject to
      * restrictions on the amount of time it can run.
      *
+     * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+     * method will return to normal operation, and calling this method will do nothing while
+     * persistent VR mode is enabled.
+     *
      * To reset the VR thread for an application, a tid of 0 can be passed.
      *
      * @see android.os.Process#myTid()
@@ -4074,6 +4112,31 @@
     }
 
     /**
+     * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+     * beyond a single process. It requires holding the
+     * {@link android.Manifest.permission#RESTRICTED_VR_ACCESS} permission. Only one thread can be a
+     * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+     * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+     * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+     * persistent VR thread loses its new scheduling priority; this method must be called again to
+     * set the persistent thread.
+     *
+     * To reset the persistent VR thread, a tid of 0 can be passed.
+     *
+     * @see android.os.Process#myTid()
+     * @param tid tid of the VR thread
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+    public static void setPersistentVrThread(int tid) {
+        try {
+            getService().setPersistentVrThread(tid);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
      * The AppTask allows you to manage your own application's tasks.
      * See {@link android.app.ActivityManager#getAppTasks()}
      */
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b36b664..dbcdecc 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -234,4 +234,11 @@
      * @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
      */
     public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+    /**
+     * Called after the network policy rules are updated by
+     * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+     * {@param procStateSeq}.
+     */
+    public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 30f7646..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 {
@@ -5062,7 +5107,9 @@
 
         // Perform updates.
         r.overrideConfig = data.overrideConfig;
-        final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl();
+        final ViewRootImpl viewRoot = r.activity.mDecor != null
+            ? r.activity.mDecor.getViewRootImpl() : null;
+
         if (movedToDifferentDisplay) {
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
                     + r.activityInfo.name + ", displayId=" + displayId
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09e7595..cbd7b9d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,8 @@
     public static final int OP_READ_PHONE_NUMBER = 65;
     /** @hide Request package installs through package installer */
     public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
-    /** @hide Enter picture-in-picture when hidden. */
-    public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
+    /** @hide Enter picture-in-picture. */
+    public static final int OP_PICTURE_IN_PICTURE = 67;
     /** @hide Instant app start foreground service. */
     public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
     /** @hide Answer incoming phone calls */
@@ -355,6 +355,9 @@
             = "android:get_accounts";
     public static final String OPSTR_READ_PHONE_NUMBER
             = "android:read_phone_number";
+    /** Access to picture-in-picture. */
+    public static final String OPSTR_PICTURE_IN_PICTURE
+            = "android:picture_in_picture";
     /** @hide */
     public static final String OPSTR_INSTANT_APP_START_FOREGROUND
             = "android:instant_app_start_foreground";
@@ -486,7 +489,7 @@
             OP_AUDIO_ACCESSIBILITY_VOLUME,
             OP_READ_PHONE_NUMBER,
             OP_REQUEST_INSTALL_PACKAGES,
-            OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+            OP_PICTURE_IN_PICTURE,
             OP_INSTANT_APP_START_FOREGROUND,
             OP_ANSWER_PHONE_CALLS
     };
@@ -563,7 +566,7 @@
             null, // OP_AUDIO_ACCESSIBILITY_VOLUME
             OPSTR_READ_PHONE_NUMBER,
             null, // OP_REQUEST_INSTALL_PACKAGES
-            null,
+            OPSTR_PICTURE_IN_PICTURE,
             OPSTR_INSTANT_APP_START_FOREGROUND,
             OPSTR_ANSWER_PHONE_CALLS,
     };
@@ -640,7 +643,7 @@
             "AUDIO_ACCESSIBILITY_VOLUME",
             "READ_PHONE_NUMBER",
             "REQUEST_INSTALL_PACKAGES",
-            "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
+            "PICTURE_IN_PICTURE",
             "INSTANT_APP_START_FOREGROUND",
             "ANSWER_PHONE_CALLS",
     };
@@ -948,7 +951,7 @@
             AppOpsManager.MODE_ALLOWED,  // OP_AUDIO_ACCESSIBILITY_VOLUME
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_DEFAULT,  // OP_REQUEST_INSTALL_PACKAGES
-            AppOpsManager.MODE_ALLOWED,  // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            AppOpsManager.MODE_ALLOWED,  // OP_PICTURE_IN_PICTURE
             AppOpsManager.MODE_DEFAULT,  // OP_INSTANT_APP_START_FOREGROUND
             AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
     };
@@ -1028,7 +1031,7 @@
             false, // OP_AUDIO_ACCESSIBILITY_VOLUME
             false,
             false, // OP_REQUEST_INSTALL_PACKAGES
-            false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            false, // OP_PICTURE_IN_PICTURE
             false,
             false, // ANSWER_PHONE_CALLS
     };
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/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java
index 445d3bd..bbd8ab3 100644
--- a/core/java/android/app/EphemeralResolverService.java
+++ b/core/java/android/app/EphemeralResolverService.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.pm.EphemeralResolveInfo;
 import android.content.pm.InstantAppResolveInfo;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -30,8 +31,10 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -42,6 +45,9 @@
 @Deprecated
 @SystemApi
 public abstract class EphemeralResolverService extends InstantAppResolverService {
+    private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+    private static final String TAG = "PackageManager";
+
     /**
      * Called to retrieve resolve info for ephemeral applications.
      *
@@ -79,7 +85,12 @@
     }
 
     @Override
-    void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
+    void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+            InstantAppResolutionCallback callback) {
+        if (DEBUG_EPHEMERAL) {
+            Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;"
+                    + " prefix: " + Arrays.toString(digestPrefix));
+        }
         final List<EphemeralResolveInfo> response = onGetEphemeralResolveInfo(digestPrefix);
         final int responseSize = response == null ? 0 : response.size();
         final List<InstantAppResolveInfo> resultList = new ArrayList<>(responseSize);
@@ -90,8 +101,12 @@
     }
 
     @Override
-    void _onGetInstantAppIntentFilter(int[] digestPrefix, String hostName,
-            InstantAppResolutionCallback callback) {
+    void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
+            String hostName, InstantAppResolutionCallback callback) {
+        if (DEBUG_EPHEMERAL) {
+            Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;"
+                    + " prefix: " + Arrays.toString(digestPrefix));
+        }
         final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName);
         final List<InstantAppResolveInfo> resultList = new ArrayList<>(1);
         resultList.add(response.getInstantAppResolveInfo());
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 77edaea..d940857 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,11 +600,17 @@
     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.
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/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 04e321f..805d8c0 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -21,8 +21,8 @@
 /** @hide */
 oneway interface IInstantAppResolver {
     void getInstantAppResolveInfoList(in int[] digestPrefix,
-            int sequence, IRemoteCallback callback);
+            String token, int sequence, IRemoteCallback callback);
 
     void getInstantAppIntentFilterList(in int[] digestPrefix,
-            int sequence, String hostName, IRemoteCallback callback);
+            String token, String hostName, IRemoteCallback callback);
 }
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 5768d1a..47817a7 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -25,7 +25,10 @@
     void onTaskStackChanged();
 
     /** Called whenever an Activity is moved to the pinned stack from another stack. */
-    void onActivityPinned();
+    void onActivityPinned(String packageName);
+
+    /** Called whenever an Activity is moved from the pinned stack to another stack. */
+    void onActivityUnpinned();
 
     /**
      * Called whenever IActivityManager.startActivity is called on an activity that is already
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 1ce30b2..2bdfa99 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -29,6 +29,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 
+import com.android.internal.os.SomeArgs;
+
 import java.util.List;
 
 /**
@@ -37,10 +39,10 @@
  */
 @SystemApi
 public abstract class InstantAppResolverService extends Service {
+    /** @hide */
     public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
+    /** @hide */
     public static final String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
-    static final String EXTRA_PREFIX = "android.app.PREFIX";
-    static final String EXTRA_HOSTNAME = "android.app.HOSTNAME";
     Handler mHandler;
 
     /**
@@ -49,7 +51,7 @@
      * @param digestPrefix The hash prefix of the instant app's domain.
      */
     public void onGetInstantAppResolveInfo(
-            int digestPrefix[], InstantAppResolutionCallback callback) {
+            int digestPrefix[], String token, InstantAppResolutionCallback callback) {
         throw new IllegalStateException("Must define");
     }
 
@@ -59,7 +61,7 @@
      * @param digestPrefix The hash prefix of the instant app's domain.
      */
     public void onGetInstantAppIntentFilter(
-            int digestPrefix[], InstantAppResolutionCallback callback) {
+            int digestPrefix[], String token, InstantAppResolutionCallback callback) {
         throw new IllegalStateException("Must define");
     }
 
@@ -81,25 +83,26 @@
         return new IInstantAppResolver.Stub() {
             @Override
             public void getInstantAppResolveInfoList(
-                    int digestPrefix[], int sequence, IRemoteCallback callback) {
-                final Message msg = mHandler.obtainMessage(
-                        ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, callback);
-                final Bundle data = new Bundle();
-                data.putIntArray(EXTRA_PREFIX, digestPrefix);
-                msg.setData(data);
-                msg.sendToTarget();
+                    int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
+                final SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callback;
+                args.arg2 = digestPrefix;
+                args.arg3 = token;
+                mHandler.obtainMessage(
+                                ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
+                        .sendToTarget();
             }
 
             @Override
             public void getInstantAppIntentFilterList(
-                    int digestPrefix[], int sequence, String hostName, IRemoteCallback callback) {
-                final Message msg = mHandler.obtainMessage(
-                        ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, sequence, 0, callback);
-                final Bundle data = new Bundle();
-                data.putString(EXTRA_HOSTNAME, hostName);
-                data.putIntArray(EXTRA_PREFIX, digestPrefix);
-                msg.setData(data);
-                msg.sendToTarget();
+                    int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
+                final SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callback;
+                args.arg2 = digestPrefix;
+                args.arg3 = token;
+                args.arg4 = hostName;
+                mHandler.obtainMessage(
+                        ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
             }
         };
     }
@@ -117,8 +120,8 @@
 
         public void onInstantAppResolveInfo(List<InstantAppResolveInfo> resolveInfo) {
             final Bundle data = new Bundle();
-            data.putInt(EXTRA_SEQUENCE, mSequence);
             data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+            data.putInt(EXTRA_SEQUENCE, mSequence);
             try {
                 mCallback.sendResult(data);
             } catch (RemoteException e) {
@@ -127,13 +130,14 @@
     }
 
     @Deprecated
-    void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
-        onGetInstantAppResolveInfo(digestPrefix, callback);
+    void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+            InstantAppResolutionCallback callback) {
+        onGetInstantAppResolveInfo(digestPrefix, token, callback);
     }
     @Deprecated
-    void _onGetInstantAppIntentFilter(int digestPrefix[], String hostName,
+    void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
             InstantAppResolutionCallback callback) {
-        onGetInstantAppIntentFilter(digestPrefix, callback);
+        onGetInstantAppIntentFilter(digestPrefix, token, callback);
     }
 
     private final class ServiceHandler extends Handler {
@@ -150,21 +154,25 @@
             final int action = message.what;
             switch (action) {
                 case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
-                    final IRemoteCallback callback = (IRemoteCallback) message.obj;
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+                    final int[] digestPrefix = (int[]) args.arg2;
+                    final String token = (String) args.arg3;
                     final int sequence = message.arg1;
-                    final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
                     _onGetInstantAppResolveInfo(
-                            digestPrefix, new InstantAppResolutionCallback(sequence, callback));
+                            digestPrefix, token,
+                            new InstantAppResolutionCallback(sequence, callback));
                 } break;
 
                 case MSG_GET_INSTANT_APP_INTENT_FILTER: {
-                    final IRemoteCallback callback = (IRemoteCallback) message.obj;
-                    final int sequence = message.arg1;
-                    final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
-                    final String hostName = message.getData().getString(EXTRA_HOSTNAME);
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+                    final int[] digestPrefix = (int[]) args.arg2;
+                    final String token = (String) args.arg3;
+                    final String hostName = (String) args.arg4;
                     _onGetInstantAppIntentFilter(
-                            digestPrefix, hostName,
-                            new InstantAppResolutionCallback(sequence, callback));
+                            digestPrefix, token, hostName,
+                            new InstantAppResolutionCallback(-1 /*sequence*/, callback));
                 } break;
 
                 default: {
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/Notification.java b/core/java/android/app/Notification.java
index aee9d386..8d76930 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1099,7 +1099,7 @@
      * represent this notification.
      */
     public static final int BADGE_ICON_LARGE = 2;
-    private int mBadgeIcon = BADGE_ICON_LARGE;
+    private int mBadgeIcon = BADGE_ICON_NONE;
 
     /**
      * Structure to encapsulate a named action that can be shown as part of this notification.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 29c4520..92216d1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -20,9 +20,6 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
 import android.annotation.SystemApi;
 import android.content.Intent;
 import android.media.AudioAttributes;
@@ -46,8 +43,15 @@
      */
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
+    /**
+     * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
+     * limit.
+     */
+    private static final int MAX_TEXT_LENGTH = 1000;
+
     private static final String TAG_CHANNEL = "channel";
     private static final String ATT_NAME = "name";
+    private static final String ATT_DESC = "desc";
     private static final String ATT_ID = "id";
     private static final String ATT_DELETED = "deleted";
     private static final String ATT_PRIORITY = "priority";
@@ -69,56 +73,46 @@
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_PRIORITY = 0x00000001;
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_VISIBILITY = 0x00000002;
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_LIGHTS = 0x00000008;
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_VIBRATION = 0x00000010;
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_SOUND = 0x00000020;
 
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_ALLOWED = 0x00000040;
 
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
 
     /**
      * @hide
      */
-    @SystemApi
     public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 0x00000100;
 
     /**
      * @hide
      */
-    @SystemApi
     public static final int[] LOCKABLE_FIELDS = new int[] {
             USER_LOCKED_PRIORITY,
             USER_LOCKED_VISIBILITY,
@@ -140,7 +134,8 @@
     private static final boolean DEFAULT_SHOW_BADGE = true;
 
     private final String mId;
-    private CharSequence mName;
+    private String mName;
+    private String mDesc;
     private int mImportance = DEFAULT_IMPORTANCE;
     private boolean mBypassDnd;
     private int mLockscreenVisibility = DEFAULT_VISIBILITY;
@@ -158,19 +153,19 @@
     /**
      * Creates a notification channel.
      *
-     * @param id The id of the channel. Must be unique per package.
-     * @param name The user visible name of the channel. Unchangeable once created; use this
-     *             constructor if the channel represents a user-defined category that does not
-     *             need to be translated. You can rename this channel when the system
+     * @param id The id of the channel. Must be unique per package. The value may be truncated if
+     *           it is too long.
+     * @param name The user visible name of the channel. You can rename this channel when the system
      *             locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
-     *             broadcast.
+     *             broadcast. The recommended maximum length is 40 characters; the value may be
+     *             truncated if it is too long.
      * @param importance The importance of the channel. This controls how interruptive notifications
      *                   posted to this channel are. See e.g.
      *                   {@link NotificationManager#IMPORTANCE_DEFAULT}.
      */
     public NotificationChannel(String id, CharSequence name, int importance) {
-        this.mId = id;
-        this.mName = name;
+        this.mId = getTrimmedString(id);
+        this.mName = name != null ? getTrimmedString(name.toString()) : null;
         this.mImportance = importance;
     }
 
@@ -180,7 +175,16 @@
         } else {
             mId = null;
         }
-        mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        if (in.readByte() != 0) {
+            mName = in.readString();
+        } else {
+            mName = null;
+        }
+        if (in.readByte() != 0) {
+            mDesc = in.readString();
+        } else {
+            mDesc = null;
+        }
         mImportance = in.readInt();
         mBypassDnd = in.readByte() != 0;
         mLockscreenVisibility = in.readInt();
@@ -212,7 +216,18 @@
         } else {
             dest.writeByte((byte) 0);
         }
-        TextUtils.writeToParcel(mName, dest, flags);
+        if (mName != null) {
+            dest.writeByte((byte) 1);
+            dest.writeString(mName);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        if (mDesc != null) {
+            dest.writeByte((byte) 1);
+            dest.writeString(mDesc);
+        } else {
+            dest.writeByte((byte) 0);
+        }
         dest.writeInt(mImportance);
         dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
         dest.writeInt(mLockscreenVisibility);
@@ -257,46 +272,33 @@
         mDeleted = deleted;
     }
 
+    // Modifiable by apps post channel creation
+
     /**
-     * Sets the name of this channel.
+     * Sets the user visible name of this channel.
+     *
+     * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
+     * long.
      */
     public void setName(CharSequence name) {
-        mName = name;
-    }
-
-    // Modifiable by a notification ranker.
-
-    /**
-     * Sets whether or not notifications posted to this channel can interrupt the user in
-     * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
-     *
-     * Only modifiable by the system and notification ranker.
-     */
-    public void setBypassDnd(boolean bypassDnd) {
-        this.mBypassDnd = bypassDnd;
+        mName = name != null ? getTrimmedString(name.toString()) : null;
     }
 
     /**
-     * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
-     * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+     * Sets the user visible description of this channel.
      *
-     * Only modifiable by the system and notification ranker.
+     * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
+     * long.
      */
-    public void setLockscreenVisibility(int lockscreenVisibility) {
-        this.mLockscreenVisibility = lockscreenVisibility;
+    public void setDescription(String description) {
+        mDesc = getTrimmedString(description);
     }
 
-    /**
-     * Sets the level of interruption of this notification channel.
-     *
-     * Only modifiable by the system and notification ranker.
-     *
-     * @param importance the amount the user should be interrupted by notifications from this
-     *                   channel. See e.g.
-     *                   {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
-     */
-    public void setImportance(int importance) {
-        this.mImportance = importance;
+    private String getTrimmedString(String input) {
+        if (input != null && input.length() > MAX_TEXT_LENGTH) {
+            return input.substring(0, MAX_TEXT_LENGTH);
+        }
+        return input;
     }
 
     // Modifiable by apps on channel creation.
@@ -385,6 +387,43 @@
     }
 
     /**
+     * Sets the level of interruption of this notification channel.
+     *
+     * Only modifiable before the channel is submitted to
+     * {@link NotificationManager#notify(String, int, Notification)}.
+     *
+     * @param importance the amount the user should be interrupted by notifications from this
+     *                   channel. See e.g.
+     *                   {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
+     */
+    public void setImportance(int importance) {
+        this.mImportance = importance;
+    }
+
+    // Modifiable by a notification ranker.
+
+    /**
+     * Sets whether or not notifications posted to this channel can interrupt the user in
+     * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+     *
+     * Only modifiable by the system and notification ranker.
+     */
+    public void setBypassDnd(boolean bypassDnd) {
+        this.mBypassDnd = bypassDnd;
+    }
+
+    /**
+     * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
+     * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+     *
+     * Only modifiable by the system and notification ranker.
+     */
+    public void setLockscreenVisibility(int lockscreenVisibility) {
+        this.mLockscreenVisibility = lockscreenVisibility;
+    }
+
+
+    /**
      * Returns the id of this channel.
      */
     public String getId() {
@@ -394,11 +433,18 @@
     /**
      * Returns the user visible name of this channel.
      */
-    public @Nullable CharSequence getName() {
+    public CharSequence getName() {
         return mName;
     }
 
     /**
+     * Returns the user visible description of this channel.
+     */
+    public String getDescription() {
+        return mDesc;
+    }
+
+    /**
      * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
      * notifications posted to this channel.
      */
@@ -507,6 +553,7 @@
     @SystemApi
     public void populateFromXml(XmlPullParser parser) {
         // Name, id, and importance are set in the constructor.
+        setDescription(parser.getAttributeValue(null, ATT_DESC));
         setBypassDnd(Notification.PRIORITY_DEFAULT
                 != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
@@ -531,6 +578,9 @@
         if (getName() != null) {
             out.attribute(null, ATT_NAME, getName().toString());
         }
+        if (getDescription() != null) {
+            out.attribute(null, ATT_DESC, getDescription());
+        }
         if (getImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(
                     null, ATT_IMPORTANCE, Integer.toString(getImportance()));
@@ -588,6 +638,7 @@
         JSONObject record = new JSONObject();
         record.put(ATT_ID, getId());
         record.put(ATT_NAME, getName());
+        record.put(ATT_DESC, getDescription());
         if (getImportance() != DEFAULT_IMPORTANCE) {
             record.put(ATT_IMPORTANCE,
                     NotificationListenerService.Ranking.importanceToString(getImportance()));
@@ -718,6 +769,10 @@
         if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
+        if (getDescription() != null ? !getDescription().equals(that.getDescription())
+                : that.getDescription() != null) {
+            return false;
+        }
         if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
             return false;
         }
@@ -734,6 +789,7 @@
     public int hashCode() {
         int result = getId() != null ? getId().hashCode() : 0;
         result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
         result = 31 * result + getImportance();
         result = 31 * result + (mBypassDnd ? 1 : 0);
         result = 31 * result + getLockscreenVisibility();
@@ -755,6 +811,7 @@
         return "NotificationChannel{" +
                 "mId='" + mId + '\'' +
                 ", mName=" + mName +
+                ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") +
                 ", mImportance=" + mImportance +
                 ", mBypassDnd=" + mBypassDnd +
                 ", mLockscreenVisibility=" + mLockscreenVisibility +
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 2b0cd04..852af8a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -40,6 +40,12 @@
  */
 public final class NotificationChannelGroup implements Parcelable {
 
+    /**
+     * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at
+     * this limit.
+     */
+    private static final int MAX_TEXT_LENGTH = 1000;
+
     private static final String TAG_GROUP = "channelGroup";
     private static final String ATT_NAME = "name";
     private static final String ATT_ID = "id";
@@ -51,14 +57,16 @@
     /**
      * Creates a notification channel group.
      *
-     * @param id The id of the group. Must be unique per package.
+     * @param id The id of the group. Must be unique per package.  the value may be truncated if
+     *           it is too long.
      * @param name The user visible name of the group. You can rename this group when the system
      *             locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
-     *             broadcast.
+     *             broadcast. <p>The recommended maximum length is 40 characters; the value may be
+     *             truncated if it is too long.
      */
     public NotificationChannelGroup(String id, CharSequence name) {
-        this.mId = id;
-        this.mName = name;
+        this.mId = getTrimmedString(id);
+        this.mName = name != null ? getTrimmedString(name.toString()) : null;
     }
 
     protected NotificationChannelGroup(Parcel in) {
@@ -71,6 +79,13 @@
         in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
     }
 
+    private String getTrimmedString(String input) {
+        if (input != null && input.length() > MAX_TEXT_LENGTH) {
+            return input.substring(0, MAX_TEXT_LENGTH);
+        }
+        return input;
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (mId != null) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0379970..097df31 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -421,8 +421,12 @@
     /**
      * Creates a notification channel that notifications can be posted to.
      *
-     * This can also be used to restore a deleted channel and to rename an existing channel. All
-     * other fields are ignored for channels that already exist.
+     * This can also be used to restore a deleted channel and to update an existing channel's
+     * name and description. The name and description should only be changed if the locale changes
+     * or in response to the user renaming this channel. For example, if a user has a channel
+     * named 'John Doe' that represents messages from a 'John Doe', and 'John Doe' changes his name
+     * to 'John Smith,' the channel can be renamed to match.
+     * All other fields are ignored for channels that already exist.
      *
      * @param channel  the channel to create.  Note that the created channel may differ from this
      *                 value. If the provided channel is malformed, a RemoteException will be
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 540d1cd..8612f18 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -16,8 +16,9 @@
 
 package android.app;
 
+import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -31,7 +32,15 @@
  * <p>
  * This exception is only appropriate where there is a concrete action the user
  * can take to recover and make forward progress, such as confirming or entering
- * authentication credentials.
+ * authentication credentials, or granting access.
+ * <p>
+ * If the receiving app is actively involved with the user, it should present
+ * the contained recovery details to help the user make forward progress. The
+ * {@link #showAsDialog(Activity)} and
+ * {@link #showAsNotification(Context, String)} methods are provided as a
+ * convenience, but receiving apps are encouraged to use
+ * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more
+ * natural way if relevant.
  * <p class="note">
  * Note: legacy code that receives this exception may treat it as a general
  * {@link SecurityException}, and thus there is no guarantee that the messages
@@ -66,7 +75,10 @@
      *            {@link Activity#setResult(int)} before finishing to
      *            communicate the final status of the recovery. For example,
      *            apps that observe {@link Activity#RESULT_OK} may choose to
-     *            immediately retry their operation.
+     *            immediately retry their operation. If this exception was
+     *            thrown from a {@link ContentProvider}, you should also send
+     *            any relevant {@link ContentResolver#notifyChange} events to
+     *            trigger reloading of data.
      */
     public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
             RemoteAction userAction) {
@@ -101,6 +113,20 @@
         return mUserAction;
     }
 
+    /** @removed */
+    @Deprecated
+    public void showAsNotification(Context context) {
+        final NotificationManager nm = context.getSystemService(NotificationManager.class);
+
+        // Create a channel per-sender, since we don't want one poorly behaved
+        // remote app to cause all of our notifications to be blocked
+        final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
+        nm.createNotificationChannel(new NotificationChannel(channelId, TAG,
+                NotificationManager.IMPORTANCE_DEFAULT));
+
+        showAsNotification(context, channelId);
+    }
+
     /**
      * Convenience method that will show a very simple notification populated
      * with the details from this exception.
@@ -114,23 +140,20 @@
      * <p>
      * This method will only display the most recent exception from any single
      * remote UID; notifications from older exceptions will always be replaced.
+     *
+     * @param channelId the {@link NotificationChannel} to use, which must have
+     *            been already created using
+     *            {@link NotificationManager#createNotificationChannel}.
      */
-    public void showAsNotification(Context context) {
+    public void showAsNotification(Context context, String channelId) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
-
-        // Create a channel per-sender, since we don't want one poorly behaved
-        // remote app to cause all of our notifications to be blocked
-        final String tag = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
-        nm.createNotificationChannel(new NotificationChannel(tag, TAG,
-                NotificationManager.IMPORTANCE_DEFAULT));
-
-        final Notification.Builder builder = new Notification.Builder(context, tag)
+        final Notification.Builder builder = new Notification.Builder(context, channelId)
                 .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
                 .setContentTitle(mUserAction.getTitle())
                 .setContentText(mUserMessage)
                 .setContentIntent(mUserAction.getActionIntent())
                 .setCategory(Notification.CATEGORY_ERROR);
-        nm.notify(tag, 0, builder.build());
+        nm.notify(TAG, mUserAction.getActionIntent().getCreatorUid(), builder.build());
     }
 
     /**
@@ -164,7 +187,13 @@
         ft.commitAllowingStateLoss();
     }
 
-    /** {@hide} */
+    /**
+     * Implementation detail for
+     * {@link RecoverableSecurityException#showAsDialog(Activity)}; needs to
+     * remain static to be recreated across orientation changes.
+     *
+     * @hide
+     */
     public static class LocalDialog extends DialogFragment {
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index a07e11e..57fc874 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -31,7 +31,11 @@
     }
 
     @Override
-    public void onActivityPinned() throws RemoteException {
+    public void onActivityPinned(String packageName) throws RemoteException {
+    }
+
+    @Override
+    public void onActivityUnpinned() throws RemoteException {
     }
 
     @Override
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/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index c281c7f..652a1c6 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -55,13 +55,13 @@
                                 in AdvertiseData periodicData, in int timeout, in IAdvertisingSetCallback callback);
     void stopAdvertisingSet(in IAdvertisingSetCallback callback);
 
-    void enableAdverisingSet(in int advertiserId, in boolean enable, in int timeout);
+    void enableAdvertisingSet(in int advertiserId, in boolean enable, in int timeout);
     void setAdvertisingData(in int advertiserId, in AdvertiseData data);
     void setScanResponseData(in int advertiserId, in AdvertiseData data);
     void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters);
     void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters);
     void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data);
-    void periodicAdvertisingEnable(in int advertiserId, in boolean enable);
+    void setPeriodicAdvertisingEnable(in int advertiserId, in boolean enable);
 
     void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
     void unregisterSync(in IPeriodicAdvertisingCallback callback);
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 5524a2b..7355b0d 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -65,7 +65,7 @@
      */
     public void enableAdvertising(boolean enable, int timeout) {
         try {
-            gatt.enableAdverisingSet(this.advertiserId, enable, timeout);
+            gatt.enableAdvertisingSet(this.advertiserId, enable, timeout);
         } catch (RemoteException e) {
             Log.e(TAG, "remote exception - ", e);
         }
@@ -143,9 +143,9 @@
      * Used to enable/disable periodic advertising. This method returns immediately, the operation
      * status is delivered through {@code callback.onPeriodicAdvertisingEnable()}.
      */
-    public void periodicAdvertisingEnable(boolean enable) {
+    public void setPeriodicAdvertisingEnable(boolean enable) {
         try {
-            gatt.periodicAdvertisingEnable(this.advertiserId, enable);
+            gatt.setPeriodicAdvertisingEnable(this.advertiserId, enable);
         } catch (RemoteException e) {
             Log.e(TAG, "remote exception - ", e);
         }
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
index ceed8d9..8d2b82a 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -62,9 +62,10 @@
      * null, and status will be set to proper error code.
      *
      * @param advertisingSet The advertising set that was started or null if error.
+     * @param txPower tx power that will be used for this set.
      * @param status Status of the operation.
      */
-    public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {}
+    public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {}
 
     /**
      * Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
@@ -106,10 +107,11 @@
      * indicating result of the operation.
      *
      * @param advertisingSet The advertising set.
+     * @param txPower tx power that will be used for this set.
      * @param status Status of the operation.
      */
     public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
-                                               int status) {}
+                                               int txPower, int status) {}
 
     /**
      * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
@@ -133,7 +135,7 @@
                                              int status) {}
 
     /**
-     * Callback triggered in response to {@link AdvertisingSet#periodicAdvertisingEnable}
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnable}
      * indicating result of the operation.
      *
      * @param advertisingSet The advertising set.
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 67fd1c8..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,12 +405,13 @@
 
     IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
         return new IAdvertisingSetCallback.Stub() {
-            public void onAdvertisingSetStarted(int advertiserId, int status) {
+            @Override
+            public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
                 handler.post(new Runnable() {
                     @Override
                     public void run() {
                         if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
-                            callback.onAdvertisingSetStarted(null, status);
+                            callback.onAdvertisingSetStarted(null, 0, status);
                             mCallbackWrappers.remove(callback);
                             return;
                         }
@@ -413,11 +419,12 @@
                         AdvertisingSet advertisingSet =
                             new AdvertisingSet(advertiserId, mBluetoothManager);
                         mAdvertisingSets.put(advertiserId, advertisingSet);
-                        callback.onAdvertisingSetStarted(advertisingSet, status);
+                        callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
                     }
                 });
             }
 
+            @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,16 +470,18 @@
                 });
             }
 
-            public void onAdvertisingParametersUpdated(int advertiserId, int status) {
+            @Override
+            public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
                 handler.post(new Runnable() {
                     @Override
                     public void run() {
                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
-                        callback.onAdvertisingParametersUpdated(advertisingSet, status);
+                        callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
                     }
                 });
             }
 
+            @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/bluetooth/le/IAdvertisingSetCallback.aidl b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
index 4b0a111..e6a09f1 100644
--- a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
+++ b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
@@ -20,12 +20,12 @@
  * @hide
  */
 oneway interface IAdvertisingSetCallback {
-  void onAdvertisingSetStarted(in int advertiserId, in int status);
+  void onAdvertisingSetStarted(in int advertiserId, in int tx_power, in int status);
   void onAdvertisingSetStopped(in int advertiserId);
   void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status);
   void onAdvertisingDataSet(in int advertiserId, in int status);
   void onScanResponseDataSet(in int advertiserId, in int status);
-  void onAdvertisingParametersUpdated(in int advertiserId, in int status);
+  void onAdvertisingParametersUpdated(in int advertiserId, in int tx_power, in int status);
   void onPeriodicAdvertisingParametersUpdated(in int advertiserId, in int status);
   void onPeriodicAdvertisingDataSet(in int advertiserId, in int status);
   void onPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in int status);
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 56f5d44..bb844a3 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,6 +23,7 @@
 import android.provider.OneTimeUseBuilder;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,7 +48,7 @@
     private AssociationRequest(
             boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {
         this.mSingleDevice = singleDevice;
-        this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);
+        this.mDeviceFilters = CollectionUtils.emptyIfNull(deviceFilters);
     }
 
     private AssociationRequest(Parcel in) {
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 0f16b7b..1d8df7f 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -31,6 +31,7 @@
 import android.provider.OneTimeUseBuilder;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -53,8 +54,8 @@
             List<ParcelUuid> serviceUuidMasks) {
         mNamePattern = namePattern;
         mAddress = address;
-        mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids);
-        mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks);
+        mServiceUuids = CollectionUtils.emptyIfNull(serviceUuids);
+        mServiceUuidMasks = CollectionUtils.emptyIfNull(serviceUuidMasks);
     }
 
     private BluetoothDeviceFilter(Parcel in) {
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/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index fb280a1..7a0158a 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,8 +39,6 @@
 import java.util.List;
 
 /**
- * <p><strong>TODO Update the overview to how to use the O new features.</strong></p>
- *
  * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users
  * with quick access to activities other than an app's main activity in the currently-active
  * launcher.  For example,
@@ -51,20 +49,20 @@
  * <h3>Static Shortcuts and Dynamic Shortcuts</h3>
  *
  * <p>
- * There are two ways to publish shortcuts: static shortcuts and dynamic shortcuts.
+ * There are several different types of shortcuts:
  *
  * <ul>
- * <li>Static shortcuts are declared in a resource
- * XML file, which is referenced in the publisher app's <code>AndroidManifest.xml</code> file.
- * Static shortcuts are published when an app is installed,
- * and the details of these shortcuts change when an app is upgraded with an updated XML
- * file.
- * Static shortcuts are immutable, and their
- * definitions, such as icons and labels, cannot be changed dynamically without upgrading the
- * publisher app.
+ * <li><p>Static shortcuts are declared in a resource XML file, which is referenced in the publisher
+ * app's <code>AndroidManifest.xml</code> file. These shortcuts are visually associated with an
+ * app's launcher icon.
+ * <p>Static shortcuts are published when an app is installed, and the details of these shortcuts
+ * change when an app is upgraded with an updated XML file. Static shortcuts are immutable, and
+ * their definitions, such as icons and labels, cannot be changed dynamically without upgrading the
+ * publisher app.</li>
  *
- * <li>Dynamic shortcuts are published at runtime using this class's APIs.
- * Apps can publish, update, and remove dynamic shortcuts at runtime.
+ * <li>Dynamic shortcuts are published at runtime using this class's APIs. These shortcuts are
+ * visually associated with an app's launcher icon. Apps can publish, update, and remove dynamic
+ * shortcuts at runtime.
  * </ul>
  *
  * <p>Only main activities&mdash;activities that handle the {@code MAIN} action and the
@@ -72,10 +70,8 @@
  * If an app has multiple main activities, these activities have different sets
  * of shortcuts.
  *
- * <p>Static shortcuts and dynamic shortcuts are shown in the currently active launcher when
- * the user long-presses on an app's launcher icon.
- *
- * <p class="note"><strong>Note: </strong>The actual gesture may be different
+ * <p>Static shortcuts and dynamic shortcuts are shown in a supported launcher when the user
+ * long-presses on an app's launcher icon. Note that the actual gesture may be different
  * depending on the launcher app.
  *
  * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
@@ -84,18 +80,19 @@
  *
  * <h3>Pinning Shortcuts</h3>
  *
- * <p>
- * Launcher apps allow users to <em>pin</em> shortcuts so they're easier to access.  Both static
- * and dynamic shortcuts can be pinned.
- * Pinned shortcuts <b>cannot</b> be removed by publisher
- * apps; they're removed only when the user removes them,
- * when the publisher app is uninstalled, or when the
- * user performs the <strong>clear data</strong> action on the publisher app from the device's Settings
- * app.
+ * <p>Apps running in the foreground can also <em>pin</em> shortcuts at runtime, subject to user
+ * permission, using this class's APIs. Each pinned shortcut is a copy of a static shortcut or a
+ * dynamic shortcut. Although users can pin a shortcut multiple times, the system calls the pinning
+ * API only once to complete the pinning process. Unlike static and dynamic shortcuts, pinned
+ * shortcuts appear as separate icons, visually distinct from the app's launcher icon, in the
+ * launcher. There is no limit to the number of pinned shortcuts that an app can create.
  *
- * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be
- * started.  See the following sections for details.
+ * <p>Pinned shortcuts <strong>cannot</strong> be removed by publisher apps. They're removed only
+ * when the user removes them, when the publisher app is uninstalled, or when the user performs the
+ * clear data action on the publisher app from the device's <b>Settings</b> app.
  *
+ * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be started.
+ * See the following sections for details.
  *
  * <h3>Updating and Disabling Shortcuts</h3>
  *
@@ -126,7 +123,7 @@
  *
  *     <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
  *     8 shortcuts, when, for example, the chat peers' icons have changed.
- * </ul>
+ * </ol>
  * The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods
  * can also be used
  * to update existing shortcuts with the same IDs, but they <b>cannot</b> be used
@@ -135,22 +132,20 @@
  *
  *
  * <h4>Disabling Static Shortcuts</h4>
- * When an app is upgraded and the new version
+ * <p>When an app is upgraded and the new version
  * no longer uses a static shortcut that appeared in the previous version, this deprecated
- * shortcut will no longer be published as a static shortcut.
+ * shortcut isn't published as a static shortcut.
  *
  * <p>If the deprecated shortcut is pinned, then the pinned shortcut will remain on the launcher,
- * but it will be disabled automatically.
- * Note that, in this case, the pinned shortcut is no longer a static shortcut, but it's
- * still <b>immutable</b>. Therefore, it cannot be updated using this class's APIs.
- *
+ * but it's disabled automatically. When a pinned shortcut is disabled, this class's APIs cannot
+ * update it.
  *
  * <h4>Disabling Dynamic Shortcuts</h4>
  * Sometimes pinned shortcuts become obsolete and may not be usable.  For example, a pinned shortcut
  * to a group chat becomes unusable when the associated group chat is deleted.  In cases like this,
  * apps should use {@link #disableShortcuts(List)}, which removes the specified dynamic
  * shortcuts and also makes any specified pinned shortcuts un-launchable.
- * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disabled shortcuts
+ * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disable shortcuts
  * and show users a custom error message when they attempt to launch the disabled shortcuts.
  *
  *
@@ -278,6 +273,104 @@
  *shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
  * </pre>
  *
+ * <h3>Publishing Pinned Shortcuts</h3>
+ *
+ * <p>Apps can pin an existing shortcut (either static or dynamic) or an entirely new shortcut to a
+ * supported launcher programatically using {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
+ * You pass two arguments into this method:
+ *
+ * <ul>
+ *   <li>A {@link ShortcutInfo} object &ndash; If the shortcut already exists, this object should
+ *   contain only the shortcut's ID. Otherwise, the new {@link ShortcutInfo} object must contain an
+ *   ID, an intent, and a short label for the new shortcut.
+ *   <li><p>A {@link android.app.PendingIntent} object &ndash; This intent represents the callback
+ *   that your app receives if the shortcut is successfully pinned to the device's launcher.
+ *   <p><b>Note:</b> If the user doesn't allow the shortcut to be pinned to the launcher, the
+ *   pinning process fails, and the {@link Intent} object that is passed into this
+ *   {@link android.app.PendingIntent} object isn't executed.
+ * </ul>
+ *
+ * The following code snippet shows how to pin a single shortcut that already exists and is enabled:
+ *
+ * <pre>
+ *ShortcutManager mShortcutManager =
+ *        context.getSystemService(ShortcutManager.class);
+ *
+ *if (mShortcutManager.isRequestPinShortcutSupported()) {
+ *
+ *    // This example defines a new shortcut; that is, this shortcut hasn't
+ *    // been published before.
+ *    ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder()
+ *            .setIcon(myIcon)
+ *            .setShortLabel("My awesome shortcut")
+ *            .setIntent(myIntent)
+ *            .build();
+ *
+ *    PendingIntent resultPendingIntent = null;
+ *
+ *    // Create the following Intent and PendingIntent objects only if your app
+ *    // needs to be notified that the user allowed the shortcut to be pinned.
+ *    // Use a boolean value, such as "appNeedsNotifying", to define this behavior.
+ *    if (appNeedsNotifying) {
+ *        // We assume here that the app has implemented a method called
+ *        // createShortcutResultIntent() that returns a broadcast intent.
+ *        Intent pinnedShortcutCallbackIntent =
+ *                createShortcutResultIntent(pinShortcutInfo);
+ *
+ *        // Configure the intent so that your app's broadcast receiver gets
+ *        // the callback successfully.
+ *        PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
+ *                pinnedShortcutCallbackIntent);
+ *
+ *        resultPendingIntent = successCallback.getIntentSender();
+ *    }
+ *
+ *    mShortcutManager.requestPinShortcut(pinShortcutInfo, resultPendingIntent);
+ *}
+ * </pre>
+ *
+ * <p class="note"><strong>Note:</strong> As you add logic in your app to make requests to pin
+ * shortcuts, keep in mind that not all launchers support pinning of shortcuts. To determine whether
+ * your app can complete this process on a particular device, check the return value of
+ * {@link #isRequestPinShortcutSupported()}. Based on this return value, you might decide to hide
+ * the option in your app that allows users to pin a shortcut.
+ *
+ * <h4>Custom Activity for Pinning Shortcuts</h4>
+ *
+ * <p>You can also create a specialized activity that helps users create shortcuts, complete with
+ * custom options and a confirmation button. In your app's manifest file, add
+ * {@link Intent#ACTION_CREATE_SHORTCUT} to the activity's <code>&lt;intent-filter&gt;</code>
+ * element, as shown in the following snippet:
+ *
+ * <pre>
+ *&lt;manifest&gt;
+ *    ...
+ *    &lt;application&gt;
+ *        &lt;activity android:name="com.example.MyCustomPromptToPinShortcut" ... &gt;
+ *            &lt;intent-filter
+ *                    action android:name="android.intent.action.ACTION_CREATE_SHORTCUT"&gt;
+ *            ...
+ *            &lt;/intent-filter&gt;
+ *        &lt;/activity&gt;
+ *        ...
+ *    &lt;/application&gt;
+ *&lt;/manifest&gt;
+ * </pre>
+ *
+ * <p>When you use this specialized activity in your app, the following sequence of steps takes
+ * place:</p>
+ *
+ * <ol>
+ *   <li>The user attempts to create a shortcut, triggering the system to start the specialized
+ *   activity.</li>
+ *   <li>The user sets options for the shortcut.</li>
+ *   <li>The user selects the confirmation button, allowing your app to create the shortcut using
+ *   the {@link #createShortcutResultIntent(ShortcutInfo)} method. This method returns an
+ *   {@link Intent}, which your app relays back to the previously-executing activity using
+ *   {@link Activity#setResult(int)}.</li>
+ *   <li>Your app calls {@link Activity#finish()} on the activity used for creating the customized
+ *   shortcut.</li>
+ * </ol>
  *
  * <h3>Shortcut Intents</h3>
  * <p>
@@ -825,7 +918,7 @@
     }
 
     /**
-     * Return {@code TRUE} if the default launcher supports
+     * Return {@code TRUE} if the app is running on a device whose default launcher supports
      * {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
      */
     public boolean isRequestPinShortcutSupported() {
@@ -839,29 +932,30 @@
 
     /**
      * Request to create a pinned shortcut.  The default launcher will receive this request and
-     * ask the user for approval.  If the user approves it, the shortcut will be created and
-     * {@code resultIntent} will be sent.  Otherwise, no responses will be sent to the caller.
+     * ask the user for approval.  If the user approves it, the shortcut will be created, and
+     * {@code resultIntent} will be sent. If a request is denied by the user, however, no response
+     * will be sent to the caller.
      *
-     * <p>When a request is denied by the user, the caller app will not get any response.
+     * <p>Only apps with a foreground activity or a foreground service can call this method.
+     * Otherwise, it'll throw {@link IllegalStateException}.
      *
-     * <p>Only apps with a foreground activity or a foreground service can call it.  Otherwise
-     * it'll throw {@link IllegalStateException}.
+     * <p>It's up to the launcher to decide how to handle previous pending requests when the same
+     * package calls this API multiple times in a row. One possible strategy is to ignore any
+     * previous requests.
      *
-     * <p>It's up to the launcher how to handle previous pending requests when the same package
-     * calls this API multiple times in a row.  It may ignore the previous requests,
-     * for example.
+     * @param shortcut Shortcut to pin.  If an app wants to pin an existing (either static
+     *     or dynamic) shortcut, then it only needs to have an ID. Although other fields don't have
+     *     to be set, the target shortcut must be enabled.
      *
-     * @param shortcut New shortcut to pin.  If an app wants to pin an existing (either dynamic
-     *     or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
-     *     be set, in which case, the target shortcut must be enabled.
-     *     If it's a new shortcut, all the mandatory fields, such as a short label, must be
+     *     <p>If it's a new shortcut, all the mandatory fields, such as a short label, must be
      *     set.
      * @param resultIntent If not null, this intent will be sent when the shortcut is pinned.
-     *    Use {@link android.app.PendingIntent#getIntentSender()} to create a {@link IntentSender}.
+     *    Use {@link android.app.PendingIntent#getIntentSender()} to create an {@link IntentSender}.
      *
      * @return {@code TRUE} if the launcher supports this feature.  Note the API will return without
      *    waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
-     *    the shortcut is pinned.  {@code FALSE} if the launcher doesn't support this feature.
+     *    the shortcut was pinned successfully.  {@code FALSE} if the launcher doesn't support this
+     *    feature.
      *
      * @see #isRequestPinShortcutSupported()
      * @see IntentSender
@@ -869,7 +963,7 @@
      *
      * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
      * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
-     * service or when the user is locked.
+     * service, or the device is locked.
      */
     public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
             @Nullable IntentSender resultIntent) {
@@ -882,16 +976,17 @@
     }
 
     /**
-     * Returns an Intent which can be used by the default launcher to pin {@param shortcut}.
-     * This should be used by an Activity to set result in response to
-     * {@link Intent#ACTION_CREATE_SHORTCUT}.
+     * Returns an Intent which can be used by the default launcher to pin a shortcut containing the
+     * given {@link ShortcutInfo}. This method should be used by an Activity to set a result in
+     * response to {@link Intent#ACTION_CREATE_SHORTCUT}.
      *
      * @param shortcut New shortcut to pin.  If an app wants to pin an existing (either dynamic
      *     or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
      *     be set, in which case, the target shortcut must be enabled.
      *     If it's a new shortcut, all the mandatory fields, such as a short label, must be
      *     set.
-     * @return The intent that should be set as the result for the calling activity or null.
+     * @return The intent that should be set as the result for the calling activity, or
+     *     <code>null</code> if the current launcher doesn't support shortcuts.
      *
      * @see Intent#ACTION_CREATE_SHORTCUT
      *
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index af953e6..a044804 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -245,9 +245,12 @@
      *
      * @param resId the resource id of the string array
      */
-    final CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+    final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
         synchronized (this) {
             final int[] rawInfoArray = getArrayStringInfo(resId);
+            if (rawInfoArray == null) {
+                return null;
+            }
             final int rawInfoArrayLen = rawInfoArray.length;
             final int infoArrayLen = rawInfoArrayLen / 2;
             int block;
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 091cc26..0edbc70 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -26,6 +26,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -44,12 +45,14 @@
         private final @NonNull String mProviderAuthority;
         private final @NonNull String mProviderPackage;
         private final @NonNull String mQuery;
+        private final @Nullable List<List<String>> mCerts;
 
         public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
-                @NonNull String query) {
+                @NonNull String query, @Nullable List<List<String>> certs) {
             mProviderAuthority = authority;
             mProviderPackage = pkg;
             mQuery = query;
+            mCerts = certs;
         }
 
         public @NonNull String getAuthority() {
@@ -63,6 +66,10 @@
         public @NonNull String getQuery() {
             return mQuery;
         }
+
+        public @Nullable List<List<String>> getCerts() {
+            return mCerts;
+        }
     }
 
     // A class represents font element in xml file which points a file in resource.
@@ -144,12 +151,33 @@
         String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
         String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
         String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
+        int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
         array.recycle();
         if (authority != null && providerPackage != null && query != null) {
             while (parser.next() != XmlPullParser.END_TAG) {
                 skip(parser);
             }
-            return new ProviderResourceEntry(authority, providerPackage, query);
+            List<List<String>> certs = null;
+            if (certsId != 0) {
+                TypedArray typedArray = resources.obtainTypedArray(certsId);
+                if (typedArray.length() > 0) {
+                    certs = new ArrayList<>();
+                    boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
+                    if (isArrayOfArrays) {
+                        for (int i = 0; i < typedArray.length(); i++) {
+                            int certId = typedArray.getResourceId(i, 0);
+                            String[] certsArray = resources.getStringArray(certId);
+                            List<String> certsList = Arrays.asList(certsArray);
+                            certs.add(certsList);
+                        }
+                    } else {
+                        String[] certsArray = resources.getStringArray(certsId);
+                        List<String> certsList = Arrays.asList(certsArray);
+                        certs.add(certsList);
+                    }
+                }
+            }
+            return new ProviderResourceEntry(authority, providerPackage, query, certs);
         }
         List<FontFileResourceEntry> fonts = new ArrayList<>();
         while (parser.next() != XmlPullParser.END_TAG) {
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/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
index 4faff62..63ccaae 100644
--- a/core/java/android/net/ConnectivityMetricsEvent.java
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -76,7 +76,16 @@
 
     @Override
     public String toString() {
-        // TODO: add transports, netId, ifname
-        return String.format("ConnectivityMetricsEvent(%tT.%tL): %s", timestamp, timestamp, data);
+        StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
+        buffer.append(String.format("%tT.%tL", timestamp, timestamp));
+        // TODO: add transports
+        if (netId != 0) {
+            buffer.append(", ").append(netId);
+        }
+        if (ifname != null) {
+            buffer.append(", ").append(ifname);
+        }
+        buffer.append("): ").append(data.toString());
+        return buffer.toString();
     }
 }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4dd8ce9..8665b9c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -423,8 +423,10 @@
      */
     public static final int TRANSPORT_WIFI_AWARE = 5;
 
-    private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
-    private static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+    /** @hide */
+    public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+    /** @hide */
+    public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
 
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
@@ -476,6 +478,17 @@
     }
 
     /**
+     * Gets all the transports set on this {@code NetworkCapability} instance.
+     *
+     * @return a bit field composed of up bits at indexes defined by
+     * {@code NetworkCapabilities.TRANSPORT_*} values for this instance.
+     * @hide
+     */
+    public long getTransports() {
+        return mTransportTypes;
+    }
+
+    /**
      * Tests for the presence of a transport on this instance.
      *
      * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 7e30ab5..c5b78a5 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -31,25 +31,21 @@
     /** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */
     public static final String RENEWING_BOUND = "RenewingBoundState";
 
-    public final String ifName;
     public final String msg;
     public final int durationMs;
 
-    public DhcpClientEvent(String ifName, String msg, int durationMs) {
-        this.ifName = ifName;
+    public DhcpClientEvent(String msg, int durationMs) {
         this.msg = msg;
         this.durationMs = durationMs;
     }
 
     private DhcpClientEvent(Parcel in) {
-        this.ifName = in.readString();
         this.msg = in.readString();
         this.durationMs = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(ifName);
         out.writeString(msg);
         out.writeInt(durationMs);
     }
@@ -61,7 +57,7 @@
 
     @Override
     public String toString() {
-        return String.format("DhcpClientEvent(%s, %s, %dms)", ifName, msg, durationMs);
+        return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
     public static final Parcelable.Creator<DhcpClientEvent> CREATOR
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index f34ffdf..8b77197 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -54,7 +54,6 @@
     public static final int RECEIVE_ERROR              = makeErrorCode(MISC_ERROR, 2);
     public static final int PARSING_ERROR              = makeErrorCode(MISC_ERROR, 3);
 
-    public final String ifName;
     // error code byte format (MSB to LSB):
     // byte 0: error type
     // byte 1: error subtype
@@ -62,19 +61,16 @@
     // byte 3: optional code
     public final int errorCode;
 
-    public DhcpErrorEvent(String ifName, int errorCode) {
-        this.ifName = ifName;
+    public DhcpErrorEvent(int errorCode) {
         this.errorCode = errorCode;
     }
 
     private DhcpErrorEvent(Parcel in) {
-        this.ifName = in.readString();
         this.errorCode = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(ifName);
         out.writeInt(errorCode);
     }
 
@@ -104,7 +100,7 @@
 
     @Override
     public String toString() {
-        return String.format("DhcpErrorEvent(%s, %s)", ifName, Decoder.constants.get(errorCode));
+        return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
     }
 
     final static class Decoder {
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 79094c0..ac727ca 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -99,6 +99,33 @@
 
     /**
      * Log an IpConnectivity event.
+     * @param ifname the network interface associated with the event.
+     * @param data is a Parcelable instance representing the event.
+     * @return true if the event was successfully logged.
+     */
+    public boolean log(String ifname, Parcelable data) {
+        ConnectivityMetricsEvent ev = makeEv(data);
+        ev.ifname = ifname;
+        return log(ev);
+    }
+
+    /**
+     * Log an IpConnectivity event.
+     * @param netid the id of the network associated with the event.
+     * @param transports the current transports of the network associated with the event, as defined
+     * in NetworkCapabilities.
+     * @param data is a Parcelable instance representing the event.
+     * @return true if the event was successfully logged.
+     */
+    public boolean log(int netid, long transports, Parcelable data) {
+        ConnectivityMetricsEvent ev = makeEv(data);
+        ev.netId = netid;
+        ev.transports = transports;
+        return log(ev);
+    }
+
+    /**
+     * Log an IpConnectivity event.
      * @param data is a Parcelable instance representing the event.
      * @return true if the event was successfully logged.
      */
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 50dda7c..f5aea73 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -47,25 +47,21 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
 
-    public final String ifName;
     public final @EventType int eventType;
     public final long durationMs;
 
-    public IpManagerEvent(String ifName, @EventType int eventType, long duration) {
-        this.ifName = ifName;
+    public IpManagerEvent(@EventType int eventType, long duration) {
         this.eventType = eventType;
         this.durationMs = duration;
     }
 
     private IpManagerEvent(Parcel in) {
-        this.ifName = in.readString();
         this.eventType = in.readInt();
         this.durationMs = in.readLong();
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(ifName);
         out.writeInt(eventType);
         out.writeLong(durationMs);
     }
@@ -88,8 +84,8 @@
 
     @Override
     public String toString() {
-        return String.format("IpManagerEvent(%s, %s, %dms)",
-                ifName, Decoder.constants.get(eventType), durationMs);
+        return String.format("IpManagerEvent(%s, %dms)",
+                Decoder.constants.get(eventType), durationMs);
     }
 
     final static class Decoder {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d69e806..019c2c5 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -41,7 +41,6 @@
     /** Neighbor unreachable notification from kernel, IP provisioning is also lost. */
     public static final int PROVISIONING_LOST_ORGANIC = 5 << 8;
 
-    public final String ifName;
     // eventType byte format (MSB to LSB):
     // byte 0: unused
     // byte 1: unused
@@ -49,19 +48,16 @@
     // byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
     public final int eventType;
 
-    public IpReachabilityEvent(String ifName, int eventType) {
-        this.ifName = ifName;
+    public IpReachabilityEvent(int eventType) {
         this.eventType = eventType;
     }
 
     private IpReachabilityEvent(Parcel in) {
-        this.ifName = in.readString();
         this.eventType = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(ifName);
         out.writeInt(eventType);
     }
 
@@ -97,7 +93,7 @@
         int hi = eventType & 0xff00;
         int lo = eventType & 0x00ff;
         String eventName = Decoder.constants.get(hi);
-        return String.format("IpReachabilityEvent(%s, %s:%02x)", ifName, eventName, lo);
+        return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
     }
 
     final static class Decoder {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 70c6e84..1ad0929 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -48,26 +48,19 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReturnCode {}
 
-    public final int netId;
-    public final long durationMs;
+    public long durationMs;
     // probeType byte format (MSB to LSB):
     // byte 0: unused
     // byte 1: unused
     // byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
     // byte 3: PROBE_* constant
-    public final int probeType;
-    public final @ReturnCode int returnCode;
+    public int probeType;
+    public @ReturnCode int returnCode;
 
-    public ValidationProbeEvent(
-            int netId, long durationMs, int probeType, @ReturnCode int returnCode) {
-        this.netId = netId;
-        this.durationMs = durationMs;
-        this.probeType = probeType;
-        this.returnCode = returnCode;
+    public ValidationProbeEvent() {
     }
 
     private ValidationProbeEvent(Parcel in) {
-        netId = in.readInt();
         durationMs = in.readLong();
         probeType = in.readInt();
         returnCode = in.readInt();
@@ -75,7 +68,6 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(netId);
         out.writeLong(durationMs);
         out.writeInt(probeType);
         out.writeInt(returnCode);
@@ -111,7 +103,7 @@
 
     @Override
     public String toString() {
-        return String.format("ValidationProbeEvent(%d, %s:%d %s, %dms)", netId,
+        return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
                 getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
     }
 
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 7906707..15bd175 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -26,6 +26,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Modifier;
+import java.util.function.Supplier;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -247,6 +248,36 @@
     public static final native void restoreCallingIdentity(long token);
 
     /**
+     * Convenience method for running the provided action enclosed in
+     * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
+     *
+     * @hide
+     */
+    public static final void withCleanCallingIdentity(Runnable action) {
+        long callingIdentity = clearCallingIdentity();
+        try {
+            action.run();
+        } finally {
+            restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
+     *
+     * @hide
+     */
+    public static final <T> T withCleanCallingIdentity(Supplier<T> action) {
+        long callingIdentity = clearCallingIdentity();
+        try {
+            return action.get();
+        } finally {
+            restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
      * Sets the native thread-local StrictMode policy mask.
      *
      * <p>The StrictMode settings are kept in two places: a Java-level
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index c432519..a4d5c6f 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * Handy class for starting a new thread that has a looper. The looper can then be 
  * used to create handler classes. Note that start() must still be called.
@@ -24,6 +27,7 @@
     int mPriority;
     int mTid = -1;
     Looper mLooper;
+    private @Nullable Handler mHandler;
 
     public HandlerThread(String name) {
         super(name);
@@ -86,6 +90,18 @@
     }
 
     /**
+     * @return a shared {@link Handler} associated with this thread
+     * @hide
+     */
+    @NonNull
+    public Handler getThreadHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler(getLooper());
+        }
+        return mHandler;
+    }
+
+    /**
      * Quits the handler thread's looper.
      * <p>
      * Causes the handler thread's looper to terminate without processing any
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/FontsContract.java b/core/java/android/provider/FontsContract.java
index 84443e9..fd9d4db 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -15,7 +15,6 @@
  */
 package android.provider;
 
-import android.app.ActivityThread;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
@@ -42,9 +41,10 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Utility class to deal with Font ContentProviders.
@@ -207,11 +207,12 @@
             return info;
         }
 
-        Set<byte[]> signatures;
+        List<byte[]> signatures;
         try {
             PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
                     PackageManager.GET_SIGNATURES);
-            signatures = convertToSet(packageInfo.signatures);
+            signatures = convertToByteArrayList(packageInfo.signatures);
+            Collections.sort(signatures, sByteArrayComparator);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Can't find content provider " + providerAuthority, e);
             receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
@@ -219,8 +220,10 @@
         }
         List<List<byte[]>> requestCertificatesList = request.getCertificates();
         for (int i = 0; i < requestCertificatesList.size(); ++i) {
-            final Set<byte[]> requestCertificates = convertToSet(requestCertificatesList.get(i));
-            if (signatures.equals(requestCertificates)) {
+            // Make a copy so we can sort it without modifying the incoming data.
+            List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i));
+            Collections.sort(requestSignatures, sByteArrayComparator);
+            if (equalsByteArrayList(signatures, requestSignatures)) {
                 return info;
             }
         }
@@ -229,20 +232,38 @@
         return null;
     }
 
-    private Set<byte[]> convertToSet(Signature[] signatures) {
-        Set<byte[]> shas = new HashSet<>();
+    private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
+        if (l.length != r.length) {
+            return l.length - r.length;
+        }
+        for (int i = 0; i < l.length; ++i) {
+            if (l[i] != r[i]) {
+                return l[i] - r[i];
+            }
+        }
+        return 0;
+    };
+
+    private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
+        if (signatures.size() != requestSignatures.size()) {
+            return false;
+        }
+        for (int i = 0; i < signatures.size(); ++i) {
+            if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private List<byte[]> convertToByteArrayList(Signature[] signatures) {
+        List<byte[]> shas = new ArrayList<>();
         for (int i = 0; i < signatures.length; ++i) {
             shas.add(signatures[i].toByteArray());
         }
         return shas;
     }
 
-    private Set<byte[]> convertToSet(List<byte[]> certs) {
-        Set<byte[]> shas = new HashSet<>();
-        shas.addAll(certs);
-        return shas;
-    }
-
     /** @hide */
     @VisibleForTesting
     public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
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/provider/Settings.java b/core/java/android/provider/Settings.java
index 7b84f689..146d2d3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6788,6 +6788,13 @@
         public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
 
         /**
+         * Control the color temperature of Night Display, represented in Kelvin.
+         * @hide
+         */
+        public static final String NIGHT_DISPLAY_COLOR_TEMPERATURE =
+                "night_display_color_temperature";
+
+        /**
          * Custom time when Night display is scheduled to activate.
          * Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
          * @hide
@@ -7022,6 +7029,7 @@
             INCALL_POWER_BUTTON_BEHAVIOR,
             NIGHT_DISPLAY_CUSTOM_START_TIME,
             NIGHT_DISPLAY_CUSTOM_END_TIME,
+            NIGHT_DISPLAY_COLOR_TEMPERATURE,
             NIGHT_DISPLAY_AUTO_MODE,
             NIGHT_DISPLAY_ACTIVATED,
             SYNC_PARENT_SOUNDS,
@@ -7077,6 +7085,8 @@
 
             INSTANT_APP_SETTINGS.add(DEFAULT_INPUT_METHOD);
             INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS);
+
+            INSTANT_APP_SETTINGS.add(ANDROID_ID);
         }
 
         /**
@@ -10285,6 +10295,7 @@
             INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
             INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
             INSTANT_APP_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+            INSTANT_APP_SETTINGS.add(AIRPLANE_MODE_ON);
         }
 
         /**
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..fce06d6 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/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 824e035..f559d42 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -479,6 +479,8 @@
                             "SurfaceView - " + viewRoot.getTitle().toString(),
                             mSurfaceWidth, mSurfaceHeight, mFormat,
                             mSurfaceFlags);
+                } else if (mSurfaceControl == null) {
+                    return;
                 }
 
                 boolean realSizeChanged = false;
@@ -625,7 +627,7 @@
                     }
                 }
             } catch (Exception ex) {
-                Log.e(TAG, "Exception from relayout", ex);
+                Log.e(TAG, "Exception configuring surface", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
@@ -662,7 +664,7 @@
                                 mScreenRect.right, mScreenRect.bottom));
                         setParentSpaceRectangle(mScreenRect, -1);
                     } catch (Exception ex) {
-                        Log.e(TAG, "Exception from relayout", ex);
+                        Log.e(TAG, "Exception configuring surface", ex);
                     }
                 }
             }
@@ -762,7 +764,7 @@
                             mScreenRect.right, mScreenRect.bottom));
                     setParentSpaceRectangle(mScreenRect, frameNumber);
                 } catch (Exception ex) {
-                    Log.e(TAG, "Exception from relayout", ex);
+                    Log.e(TAG, "Exception configuring surface", ex);
                 }
             }
             mRTLastReportedPosition.setEmpty();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 583dad4..5269296 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5033,7 +5033,7 @@
 
                             try {
                                 rawHints = a.getTextArray(attr);
-                            } catch (NullPointerException e) {
+                            } catch (Resources.NotFoundException e) {
                                 rawString = getResources().getString(resId);
                             }
                         } else {
@@ -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());
@@ -7379,7 +7382,7 @@
      * <p>When implementing this method, subclasses must follow the rules below:
      *
      * <ol>
-     * <li>Also implement {@link #autofill(int, AutofillValue)} to autofill the virtual
+     * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
      * children.
      * <li>Call
      * {@link android.view.autofill.AutofillManager#notifyViewEntered} and
@@ -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();
@@ -7448,24 +7454,23 @@
     }
 
     /**
-     * Automatically fills the content of a virtual view with the {@code value}
+     * Automatically fills the content of a virtual views.
      *
      * <p>See {@link #autofill(AutofillValue)} and
      * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
      *
-     * @param value value to be autofilled.
-     * @param virtualId id identifying the virtual child inside the custom view.
+     * @param values map of values to be autofilled, keyed by virtual child id.
      *
      * @return {@code true} if the view was successfully autofilled, {@code false} otherwise
      */
-    public boolean autofill(@SuppressWarnings("unused") int virtualId,
-            @SuppressWarnings("unused") AutofillValue value) {
+    public boolean autofill(
+            @NonNull @SuppressWarnings("unused") SparseArray<AutofillValue>values) {
         return false;
     }
 
     /**
      * Describes the autofill type that should be used on calls to
-     * {@link #autofill(AutofillValue)} and {@link #autofill(int, AutofillValue)}.
+     * {@link #autofill(AutofillValue)} and {@link #autofill(SparseArray)}.
      *
      * <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it (and
      * {@link #autofill(AutofillValue)} to support the Autofill Framework.
@@ -7672,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();
             }
@@ -7708,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);
@@ -17893,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);
         }
@@ -17921,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 4168756..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(int, AutofillValue)}.
-     * @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
-     *            {@link View#autofill(int, AutofillValue)}.
-     * @param flags currently {@code 0}.
-     *
-     * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+     * @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)}.
      */
-    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/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 5d136dc..1d1fcc9 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -677,7 +677,6 @@
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         drawThumb(canvas);
-
     }
 
     @Override
@@ -703,9 +702,9 @@
     }
 
     /**
-     * Draw the tick marks.
+     * @hide
      */
-    void drawTickMarks(Canvas canvas) {
+    protected void drawTickMarks(Canvas canvas) {
         if (mTickMark != null) {
             final int count = getMax() - getMin();
             if (count > 1) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index ec2adfb..cabf8ea 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -851,6 +851,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean getMirrorForRtl() {
+        return mMirrorForRtl;
+    }
+
+    /**
      * Applies the progress tints in order of increasing specificity.
      */
     private void applyProgressTints() {
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/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 52e8ec8..f2a7f25 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3808,23 +3808,28 @@
      * @param fontVariationSettings font variation settings. You can pass null or empty string as
      *                              no variation settings.
      *
+     * @return true if the given settings is effective to at least one font file underlying this
+     *         TextView. This function also returns true for empty settings string. Otherwise
+     *         returns false.
+     *
      * @see #getFontVariationSettings()
      * @see Paint#getFontVariationSettings() Paint.getFontVariationSettings()
      */
-    public void setFontVariationSettings(@Nullable String fontVariationSettings) {
+    public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
         final String existingSettings = mTextPaint.getFontVariationSettings();
         if (fontVariationSettings == existingSettings
                 || (fontVariationSettings != null
                         && fontVariationSettings.equals(existingSettings))) {
-            return;
+            return true;
         }
-        mTextPaint.setFontVariationSettings(fontVariationSettings);
+        boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
 
-        if (mLayout != null) {
+        if (effective && mLayout != null) {
             nullLayouts();
             requestLayout();
             invalidate();
         }
+        return effective;
     }
 
     /**
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
index 68afe02..d19f1ec 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -46,7 +46,6 @@
     private static final String TAG = "NightDisplayController";
     private static final boolean DEBUG = false;
 
-    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
     public @interface AutoMode {}
@@ -233,6 +232,65 @@
                 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
     }
 
+    /**
+     * Returns the color temperature (in Kelvin) to tint the display when activated.
+     */
+    public int getColorTemperature() {
+        int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(),
+                Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId);
+        if (colorTemperature == -1) {
+            if (DEBUG) {
+                Slog.d(TAG, "Using default value for setting: "
+                    + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE);
+            }
+            colorTemperature = getDefaultColorTemperature();
+        }
+        final int minimumTemperature = getMinimumColorTemperature();
+        final int maximumTemperature = getMaximumColorTemperature();
+        if (colorTemperature < minimumTemperature) {
+            colorTemperature = minimumTemperature;
+        } else if (colorTemperature > maximumTemperature) {
+            colorTemperature = maximumTemperature;
+        }
+
+        return colorTemperature;
+    }
+
+    /**
+     * Sets the current temperature.
+     *
+     * @param colorTemperature the temperature, in Kelvin.
+     * @return {@code true} if new temperature was set successfully.
+     */
+    public boolean setColorTemperature(int colorTemperature) {
+        return Secure.putIntForUser(mContext.getContentResolver(),
+            Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId);
+    }
+
+    /**
+     * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
+     */
+    public int getMinimumColorTemperature() {
+        return mContext.getResources().getInteger(
+                R.integer.config_nightDisplayColorTemperatureMin);
+    }
+
+    /**
+     * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated.
+     */
+    public int getMaximumColorTemperature() {
+        return mContext.getResources().getInteger(
+                R.integer.config_nightDisplayColorTemperatureMax);
+    }
+
+    /**
+     * Returns the default color temperature (in Kelvin) to tint the display when activated.
+     */
+    public int getDefaultColorTemperature() {
+        return mContext.getResources().getInteger(
+                R.integer.config_nightDisplayColorTemperatureDefault);
+    }
+
     private void onSettingChanged(@NonNull String setting) {
         if (DEBUG) {
             Slog.d(TAG, "onSettingChanged: " + setting);
@@ -252,6 +310,9 @@
                 case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
                     mCallback.onCustomEndTimeChanged(getCustomEndTime());
                     break;
+                case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
+                    mCallback.onColorTemperatureChanged(getColorTemperature());
+                    break;
             }
         }
     }
@@ -278,6 +339,8 @@
                         false /* notifyForDescendants */, mContentObserver, mUserId);
                 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
                         false /* notifyForDescendants */, mContentObserver, mUserId);
+                cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
+                        false /* notifyForDescendants */, mContentObserver, mUserId);
             }
         }
     }
@@ -417,5 +480,12 @@
          * @param endTime the local time to automatically deactivate Night display
          */
         default void onCustomEndTimeChanged(LocalTime endTime) {}
+
+        /**
+         * Callback invoked when the color temperature changes.
+         *
+         * @param colorTemperature the color temperature to tint the screen
+         */
+        default void onColorTemperatureChanged(int colorTemperature) {}
     }
 }
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/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d0fbe7c..f4dd5a6 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -31,7 +31,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Function;
 
 /**
  * ArrayUtils contains some methods that you can call to find out
@@ -237,35 +236,6 @@
         return false;
     }
 
-    @NonNull
-    public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
-        if (isEmpty(list)) return Collections.emptyList();
-        ArrayList<T> result = null;
-        for (int i = 0; i < list.size(); i++) {
-            final Object item = list.get(i);
-            if (c.isInstance(item)) {
-                result = add(result, (T) item);
-            }
-        }
-        return emptyIfNull(result);
-    }
-
-    public static <T> boolean any(@Nullable List<T> items,
-            java.util.function.Predicate<T> predicate) {
-        return find(items, predicate) != null;
-    }
-
-    @Nullable
-    public static <T> T find(@Nullable List<T> items,
-            java.util.function.Predicate<T> predicate) {
-        if (isEmpty(items)) return null;
-        for (int i = 0; i < items.size(); i++) {
-            final T item = items.get(i);
-            if (predicate.test(item)) return item;
-        }
-        return null;
-    }
-
     public static long total(@Nullable long[] array) {
         long total = 0;
         if (array != null) {
@@ -504,29 +474,6 @@
         }
     }
 
-    public static int size(@Nullable Collection<?> cur) {
-        return cur != null ? cur.size() : 0;
-    }
-
-    public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
-            Function<? super I, ? extends O> f) {
-        if (cur == null || cur.isEmpty()) return Collections.emptyList();
-        final ArrayList<O> result = new ArrayList<>();
-        for (int i = 0; i < cur.size(); i++) {
-            result.add(f.apply(cur.get(i)));
-        }
-        return result;
-    }
-
-    /**
-     * Returns the given list, or an immutable empty list if the provided list is null
-     *
-     * @see Collections#emptyList
-     */
-    public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
-        return cur == null ? Collections.emptyList() : cur;
-    }
-
     public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
         return (cur != null) ? cur.contains(val) : false;
     }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
new file mode 100644
index 0000000..287f68c
--- /dev/null
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Utility methods for dealing with (typically {@link Nullable}) {@link Collection}s
+ *
+ * Unless a method specifies otherwise, a null value for a collection is treated as an empty
+ * collection of that type.
+ */
+public class CollectionUtils {
+    private CollectionUtils() { /* cannot be instantiated */ }
+
+    /**
+     * Returns a list of items from the provided list that match the given condition.
+     *
+     * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
+     * {@link Stream} instance
+     */
+    public static @NonNull <T> List<T> filter(@Nullable List<T> list,
+            java.util.function.Predicate<? super T> predicate) {
+        ArrayList<T> result = null;
+        for (int i = 0; i < size(list); i++) {
+            final T item = list.get(i);
+            if (predicate.test(item)) {
+                result = ArrayUtils.add(result, item);
+            }
+        }
+        return emptyIfNull(result);
+    }
+
+    /**
+     * Returns a list of items resulting from applying the given function to each element of the
+     * provided list.
+     *
+     * The resulting list will have the same {@link #size} as the input one.
+     *
+     * This is similar to {@link Stream#map} but without the overhead of creating an intermediate
+     * {@link Stream} instance
+     */
+    public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
+            Function<? super I, ? extends O> f) {
+        if (cur == null || cur.isEmpty()) return Collections.emptyList();
+        final ArrayList<O> result = new ArrayList<>();
+        for (int i = 0; i < cur.size(); i++) {
+            result.add(f.apply(cur.get(i)));
+        }
+        return result;
+    }
+
+    /**
+     * Returns the given list, or an immutable empty list if the provided list is null
+     *
+     * This can be used to guaranty null-safety without paying the price of extra allocations
+     *
+     * @see Collections#emptyList
+     */
+    public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
+        return cur == null ? Collections.emptyList() : cur;
+    }
+
+    /**
+     * Returns the size of the given list, or 0 if the list is null
+     */
+    public static int size(@Nullable Collection<?> cur) {
+        return cur != null ? cur.size() : 0;
+    }
+
+    /**
+     * Returns the elements of the given list that are of type {@code c}
+     */
+    public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
+        if (ArrayUtils.isEmpty(list)) return Collections.emptyList();
+        ArrayList<T> result = null;
+        for (int i = 0; i < list.size(); i++) {
+            final Object item = list.get(i);
+            if (c.isInstance(item)) {
+                result = ArrayUtils.add(result, (T) item);
+            }
+        }
+        return emptyIfNull(result);
+    }
+
+    /**
+     * Returns whether there exists at least one element in the list for which
+     * condition {@code predicate} is true
+     */
+    public static <T> boolean any(@Nullable List<T> items,
+            java.util.function.Predicate<T> predicate) {
+        return find(items, predicate) != null;
+    }
+
+    /**
+     * Returns the first element from the list for which
+     * condition {@code predicate} is true, or null if there is no such element
+     */
+    public static @Nullable <T> T find(@Nullable List<T> items,
+            java.util.function.Predicate<T> predicate) {
+        if (ArrayUtils.isEmpty(items)) return null;
+        for (int i = 0; i < items.size(); i++) {
+            final T item = items.get(i);
+            if (predicate.test(item)) return item;
+        }
+        return null;
+    }
+}
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 78e8797..4a9a2c5 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -92,6 +92,13 @@
     // ro.boottime.init.mount_all. + postfix for mount_all duration
     private static final String[] MOUNT_DURATION_PROPS_POSTFIX =
             new String[] { "early", "default", "late" };
+    // for reboot, fs shutdown time is recorded in last_kmsg.
+    private static final String[] LAST_KMSG_FILES =
+            new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" };
+    // first: fs shutdown time in ms, second: umount status defined in init/reboot.h
+    private static final String LAST_SHUTDOWN_TIME_PATTERN =
+            "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)";
+    private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h
 
     @Override
     public void onReceive(final Context context, Intent intent) {
@@ -213,8 +220,11 @@
         } else {
             if (db != null) db.addText("SYSTEM_RESTART", headers);
         }
-        addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
+        // log always available fs_stat last so that logcat collecting tools can wait until
+        // fs_stat to get all file system metrics.
+        logFsShutdownTime();
         logFsMountTime();
+        addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
 
         // Scan existing tombstones (in case any new ones appeared)
         File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -323,7 +333,6 @@
         if (fileTime <= 0) return;  // File does not exist
 
         String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
-        StringBuilder sb = new StringBuilder();
         Pattern pattern = Pattern.compile(FS_STAT_PATTERN);
         for (String line : log.split("\n")) { // should check all lines
             if (line.contains("FILE SYSTEM WAS MODIFIED")) {
@@ -355,6 +364,45 @@
         }
     }
 
+    private static void logFsShutdownTime() {
+        File f = null;
+        for (String fileName : LAST_KMSG_FILES) {
+            File file = new File(fileName);
+            if (!file.exists()) continue;
+            f = file;
+            break;
+        }
+        if (f == null) { // no last_kmsg
+            return;
+        }
+
+        final int maxReadSize = 16*1024;
+        // last_kmsg can be very big, so only parse the last part
+        String lines;
+        try {
+            lines = FileUtils.readTextFile(f, -maxReadSize, null);
+        } catch (IOException e) {
+            Slog.w(TAG, "cannot read last msg", e);
+            return;
+        }
+        Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
+        Matcher matcher = pattern.matcher(lines);
+        if (matcher.find()) {
+            MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+                    Integer.parseInt(matcher.group(1)));
+            MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+                    Integer.parseInt(matcher.group(2)));
+            Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
+        } else { // not found
+            // This can happen when a device has too much kernel log after file system unmount
+            // ,exceeding maxReadSize. And having that much kernel logging can affect overall
+            // performance as well. So it is better to fix the kernel to reduce the amount of log.
+            MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+                    UMOUNT_STATUS_NOT_AVAILABLE);
+            Slog.w(TAG, "boot_fs_shutdown, string not found");
+        }
+    }
+
     private static void handleFsckFsStat(Matcher match) {
         String partition = match.group(1);
         int stat;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index af5fca2..95b2593 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -286,7 +286,6 @@
     libhwbinder \
     libvintf \
     libnativewindow \
-    libtextclassifier \
 
 LOCAL_SHARED_LIBRARIES += \
     libhwui \
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/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 0cdc74f..d0b07d0 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -91,6 +91,24 @@
     Typeface::setDefault(face);
 }
 
+static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
+    Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+    const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags();
+    const size_t length = tagSet.size();
+    if (length == 0) {
+        return nullptr;
+    }
+    std::vector<jint> tagVec(length);
+    int index = 0;
+    for (const auto& tag : tagSet) {
+        tagVec[index++] = tag;
+    }
+    std::sort(tagVec.begin(), tagVec.end());
+    const jintArray result = env->NewIntArray(length);
+    env->SetIntArrayRegion(result, 0, length, tagVec.data());
+    return result;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
@@ -103,6 +121,7 @@
     { "nativeCreateFromArray",    "([J)J",
                                            (void*)Typeface_createFromArray },
     { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
+    { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index fd9e714..ee74ef0 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -128,8 +128,13 @@
         if (callbacks.onDestroy != NULL) {
             callbacks.onDestroy(this);
         }
-        if (env != NULL && clazz != NULL) {
+        if (env != NULL) {
+          if (clazz != NULL) {
             env->DeleteGlobalRef(clazz);
+          }
+          if (javaAssetManager != NULL) {
+            env->DeleteGlobalRef(javaAssetManager);
+          }
         }
         if (messageQueue != NULL && mainWorkRead >= 0) {
             messageQueue->getLooper()->removeFd(mainWorkRead);
@@ -170,6 +175,10 @@
     int mainWorkRead;
     int mainWorkWrite;
     sp<MessageQueue> messageQueue;
+
+    // Need to hold on to a reference here in case the upper layers destroy our
+    // AssetManager.
+    jobject javaAssetManager;
 };
 
 void android_NativeActivity_finish(ANativeActivity* activity) {
@@ -345,6 +354,7 @@
 
     code->sdkVersion = sdkVersion;
 
+    code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
     code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
 
     if (obbDir != NULL) {
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/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 448cd2e..a165621 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -20,17 +20,17 @@
     android:id="@+id/notification_header"
     android:orientation="horizontal"
     android:layout_width="wrap_content"
-    android:layout_height="48dp"
+    android:layout_height="@dimen/notification_header_height"
     android:clipChildren="false"
-    android:paddingTop="10dp"
-    android:paddingBottom="11dp"
+    android:paddingTop="@dimen/notification_header_padding_top"
+    android:paddingBottom="@dimen/notification_header_padding_bottom"
     android:layout_marginBottom="5dp"
     android:paddingStart="@dimen/notification_content_margin_start"
     android:paddingEnd="16dp">
     <com.android.internal.widget.CachingIconView
         android:id="@+id/icon"
-        android:layout_width="18dp"
-        android:layout_height="18dp"
+        android:layout_width="@dimen/notification_header_icon_size"
+        android:layout_height="@dimen/notification_header_icon_size"
         android:layout_marginEnd="3dp"
         />
     <TextView
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4432e3c..e1068ea 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. -->
@@ -8565,6 +8569,7 @@
         <attr name="fontProviderAuthority" format="string" />
         <attr name="fontProviderPackage" format="string" />
         <attr name="fontProviderQuery" format="string" />
+        <attr name="fontProviderCerts" format="reference" />
     </declare-styleable>
 
     <!-- @hide -->
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 385f256..68e766e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -804,6 +804,16 @@
          Represented as milliseconds from midnight (e.g. 21600000 == 6am). -->
     <integer name="config_defaultNightDisplayCustomEndTime">21600000</integer>
 
+    <!-- Minimum color temperature, in Kelvin, supported by Night display. -->
+    <integer name="config_nightDisplayColorTemperatureMin">2596</integer>
+
+    <!-- Default color temperature, in Kelvin, to tint the screen when Night display is
+         activated. -->
+    <integer name="config_nightDisplayColorTemperatureDefault">2850</integer>
+
+    <!-- Maximum color temperature, in Kelvin, supported by Night display. -->
+    <integer name="config_nightDisplayColorTemperatureMax">4082</integer>
+
     <!-- Indicate whether to allow the device to suspend when the screen is off
          due to the proximity sensor.  This resource should only be set to true
          if the sensor HAL correctly handles the proximity sensor as a wake-up source.
@@ -1031,7 +1041,7 @@
 
     <!-- Control the behavior when the user long presses the home button.
             0 - Nothing
-            1 - Recent apps view in SystemUI
+            1 - Launch all apps intent
             2 - Launch assist intent
          This needs to match the constants in
          policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2498,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.
@@ -2678,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/dimens.xml b/core/res/res/values/dimens.xml
index d0127a3..9205839 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -174,9 +174,20 @@
     <!-- height of the content margin on the bottom -->
     <dimen name="notification_content_margin_bottom">16dp</dimen>
 
+    <!-- height of the notification header (for icon and package name) -->
+    <dimen name="notification_header_height">48dp</dimen>
+
     <!-- The height of the background for a notification header on a group -->
     <dimen name="notification_header_background_height">45.5dp</dimen>
 
+    <!-- The top padding for the notification header -->
+    <dimen name="notification_header_padding_top">10dp</dimen>
+    <!-- The bottom padding for the notification header -->
+    <dimen name="notification_header_padding_bottom">11dp</dimen>
+
+    <!-- size (width and height) of the icon in the notification header -->
+    <dimen name="notification_header_icon_size">18dp</dimen>
+
     <!-- Height of a small notification in the status bar -->
     <dimen name="notification_min_height">92dp</dimen>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f965c69..baad148 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2808,6 +2808,8 @@
         <public name="recycleEnabled"/>
         <public name="isStatic" />
         <public name="isFeatureSplit" />
+        <public name="singleLineTitle" />
+        <public name="fontProviderCerts" />
     </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 7201eae..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" />
@@ -2767,6 +2769,9 @@
   <java-symbol type="integer" name="config_defaultNightDisplayAutoMode" />
   <java-symbol type="integer" name="config_defaultNightDisplayCustomStartTime" />
   <java-symbol type="integer" name="config_defaultNightDisplayCustomEndTime" />
+  <java-symbol type="integer" name="config_nightDisplayColorTemperatureDefault" />
+  <java-symbol type="integer" name="config_nightDisplayColorTemperatureMin" />
+  <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
 
   <!-- Default first user restrictions -->
   <java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -2916,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/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7b8c229..5669189 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -115,6 +115,9 @@
     <!-- accessibility test permissions -->
     <uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
 
+    <!-- vr test permissions -->
+    <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+
     <application android:theme="@style/Theme" android:supportsRtl="true">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml
new file mode 100644
index 0000000..7a753c3
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fontProviderAuthority="com.example.test.fontprovider"
+        android:fontProviderQuery="MyRequestedFont"
+        android:fontProviderPackage="com.example.test.fontprovider.package"
+        android:fontProviderCerts="@array/certarray">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml
new file mode 100644
index 0000000..b834771
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fontProviderAuthority="com.example.test.fontprovider"
+        android:fontProviderQuery="MyRequestedFont"
+        android:fontProviderPackage="com.example.test.fontprovider.package"
+        android:fontProviderCerts="@array/certs1">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/res/values/arrays.xml b/core/tests/coretests/res/values/arrays.xml
index f76da85..7a25707 100644
--- a/core/tests/coretests/res/values/arrays.xml
+++ b/core/tests/coretests/res/values/arrays.xml
@@ -31,4 +31,19 @@
         <item>2 days</item>
         <item>1 week</item>
     </string-array>
+
+    <string-array name="certs1">
+        <item>123456789</item>
+        <item>987654321</item>
+    </string-array>
+
+    <string-array name="certs2">
+        <item>abcdefg</item>
+        <item>gfedcba</item>
+    </string-array>
+
+    <array name="certarray">
+        <item>@array/certs1</item>
+        <item>@array/certs2</item>
+    </array>
 </resources>
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 82f4690..5e426e8 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -15,21 +15,20 @@
  */
 package android.content.res;
 
-import static junit.framework.Assert.assertNull;
+import static android.content.res.FontResourcesParser.FamilyResourceEntry;
+import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
+import static android.content.res.FontResourcesParser.FontFileResourceEntry;
+import static android.content.res.FontResourcesParser.ProviderResourceEntry;
+
+import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-import static android.content.res.FontResourcesParser.FamilyResourceEntry;
-import static android.content.res.FontResourcesParser.ProviderResourceEntry;
-import static android.content.res.FontResourcesParser.FontFileResourceEntry;
-import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
-
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.text.FontConfig;
 
 import com.android.frameworks.coretests.R;
 
@@ -97,4 +96,50 @@
         assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage());
         assertEquals("MyRequestedFont", providerEntry.getQuery());
     }
+
+    @Test
+    public void testParseDownloadableFont_singleCerts() throws IOException, XmlPullParserException {
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontsinglecerts);
+
+        FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
+
+        assertNotNull(result);
+        assertTrue(result instanceof ProviderResourceEntry);
+        ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
+        assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
+        assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
+        assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
+        List<List<String>> certList = providerResourceEntry.getCerts();
+        assertNotNull(certList);
+        assertEquals(1, certList.size());
+        List<String> certs = certList.get(0);
+        assertEquals(2, certs.size());
+        assertEquals("123456789", certs.get(0));
+        assertEquals("987654321", certs.get(1));
+    }
+
+    @Test
+    public void testParseDownloadableFont_multipleCerts() throws IOException, XmlPullParserException {
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontmulticerts);
+
+        FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
+
+        assertNotNull(result);
+        assertTrue(result instanceof ProviderResourceEntry);
+        ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
+        assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
+        assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
+        assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
+        List<List<String>> certList = providerResourceEntry.getCerts();
+        assertNotNull(certList);
+        assertEquals(2, certList.size());
+        List<String> certs1 = certList.get(0);
+        assertEquals(2, certs1.size());
+        assertEquals("123456789", certs1.get(0));
+        assertEquals("987654321", certs1.get(1));
+        List<String> certs2 = certList.get(1);
+        assertEquals(2, certs2.size());
+        assertEquals("abcdefg", certs2.get(0));
+        assertEquals("gfedcba", certs2.get(1));
+    }
 }
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/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
new file mode 100644
index 0000000..920988b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.app.ActivityManager;
+import android.app.VrManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * Tests ActivityManager#setPersistentVrThread and ActivityManager#setVrThread's
+ * interaction with persistent VR mode.
+ */
+public class SetPersistentVrThreadTest extends ActivityInstrumentationTestCase2<TestVrActivity> {
+    private TestVrActivity mActivity;
+    private ActivityManager mActivityManager;
+    private VrManager mVrManager;
+    private Context mContext;
+    private String mOldVrListener;
+    private ComponentName mRequestedComponent;
+    private static final int SCHED_OTHER = 0;
+    private static final int SCHED_FIFO = 1;
+    private static final int SCHED_RESET_ON_FORK = 0x40000000;
+    public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+    private static final String TAG = "VrSetPersistentFIFOThreadTest";
+
+    public SetPersistentVrThreadTest() {
+        super(TestVrActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mVrManager = (VrManager) mContext.getSystemService(Context.VR_SERVICE);
+
+        mRequestedComponent = new ComponentName(mContext,
+                TestVrActivity.TestVrListenerService.class);
+        mOldVrListener = Settings.Secure.getString(mContext.getContentResolver(),
+                ENABLED_VR_LISTENERS);
+        Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+                mRequestedComponent.flattenToString());
+        mActivity = getActivity();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            setPersistentVrModeEnabled(false);
+        } catch (Throwable e) {
+            // pass
+        }
+        Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+                mOldVrListener);
+        super.tearDown();
+    }
+
+    private void setPersistentVrModeEnabled(boolean enable) throws Throwable {
+        mVrManager.setPersistentVrModeEnabled(enable);
+        // Allow the system time to send out callbacks for persistent VR mode.
+        Thread.sleep(200);
+    }
+
+    @SmallTest
+    public void testSetPersistentVrThreadAPISuccess() throws Throwable {
+        if (!mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+            return;
+        }
+
+        int vr_thread = 0, policy = 0;
+        vr_thread = Process.myTid();
+
+        setPersistentVrModeEnabled(true);
+        mActivityManager.setPersistentVrThread(vr_thread);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+        // Check that the thread loses priority when persistent mode is disabled.
+        setPersistentVrModeEnabled(false);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals(SCHED_OTHER, policy);
+
+        // Check that disabling VR mode when in persistent mode does not affect the persistent
+        // thread.
+        mActivity.setVrModeEnabled(true, mRequestedComponent);
+        Thread.sleep(200);
+        setPersistentVrModeEnabled(true);
+        Thread.sleep(200);
+        mActivityManager.setPersistentVrThread(vr_thread);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+        mActivity.setVrModeEnabled(false, mRequestedComponent);
+        Thread.sleep(200);
+        assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+        setPersistentVrModeEnabled(false);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals(SCHED_OTHER, policy);
+    }
+
+    @SmallTest
+    public void testSetPersistentVrThreadAPIFailure() throws Throwable {
+        if (!mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+            return;
+        }
+        int vr_thread = 0, policy = 0;
+        vr_thread = Process.myTid();
+        mActivityManager.setPersistentVrThread(vr_thread);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals(SCHED_OTHER, policy);
+    }
+
+    @SmallTest
+    public void testSetVrThreadAPIFailsInPersistentMode() throws Throwable {
+        if (!mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+            return;
+        }
+        int vr_thread = 0, policy = 0;
+        mActivity.setVrModeEnabled(true, mRequestedComponent);
+        vr_thread = Process.myTid();
+
+        setPersistentVrModeEnabled(true);
+        mActivityManager.setVrThread(vr_thread);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals(SCHED_OTHER, policy);
+        setPersistentVrModeEnabled(false);
+
+        // When not in persistent mode the API works again.
+        mActivity.setVrModeEnabled(true, mRequestedComponent);
+        mActivityManager.setVrThread(vr_thread);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+        // The thread loses priority when persistent mode is disabled.
+        setPersistentVrModeEnabled(true);
+        policy = (int) Process.getThreadScheduler(vr_thread);
+        assertEquals(SCHED_OTHER, policy);
+        setPersistentVrModeEnabled(false);
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index d90fc2b..6820e92 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -51,6 +51,11 @@
 public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
     private static final byte[] BYTE_ARRAY =
             Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+    // Use a different instance to test byte array comparison
+    private static final byte[] BYTE_ARRAY_COPY =
+            Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+    private static final byte[] BYTE_ARRAY_2 =
+            Base64.decode("e04fd020ea3a6910a2d808002b32", Base64.DEFAULT);
     private static final String PACKAGE_NAME = "com.my.font.provider.package";
 
     private final FontRequest request = new FontRequest(
@@ -268,6 +273,34 @@
         assertNull(result);
     }
 
+    public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
+            throws PackageManager.NameNotFoundException {
+        ProviderInfo info = new ProviderInfo();
+        info.packageName = PACKAGE_NAME;
+        info.applicationInfo = new ApplicationInfo();
+        when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
+        PackageInfo packageInfo = new PackageInfo();
+        Signature signature = mock(Signature.class);
+        when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
+        Signature signature2 = mock(Signature.class);
+        when(signature2.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
+        packageInfo.packageName = PACKAGE_NAME;
+        packageInfo.signatures = new Signature[] { signature, signature2 };
+        when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
+
+        // The provider has {BYTE_ARRAY_COPY, BYTE_ARRAY_COPY}, the request has
+        // {BYTE_ARRAY_2, BYTE_ARRAY_COPY}.
+        List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
+        FontRequest requestRightCerts = new FontRequest(
+                TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
+        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
+
+        // The given list includes an extra cert and doesn't have a second copy of the cert like
+        // the provider does, so it should have failed.
+        verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
+        assertNull(result);
+    }
+
     public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
             throws PackageManager.NameNotFoundException {
         ProviderInfo info = setupPackageManager();
@@ -306,7 +339,7 @@
         when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
         PackageInfo packageInfo = new PackageInfo();
         Signature signature = mock(Signature.class);
-        when(signature.toByteArray()).thenReturn(BYTE_ARRAY);
+        when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
         packageInfo.packageName = PACKAGE_NAME;
         packageInfo.signatures = new Signature[] { signature };
         when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
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..1b25a62 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/Paint.java b/graphics/java/android/graphics/Paint.java
index 5d6aa8a..f4bf079 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -19,7 +19,9 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Size;
+import android.graphics.FontListParser;
 import android.os.LocaleList;
+import android.text.FontConfig;
 import android.text.GraphicsOperations;
 import android.text.SpannableString;
 import android.text.SpannedString;
@@ -30,6 +32,9 @@
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -1531,21 +1536,46 @@
     /**
      * Set font variation settings.
      *
+     * This function does nothing if none of the settings is applicable to underlying font files.
+     *
      * @param settings font variation settings, e.g. "'wdth' 300, 'wght' 1.8"
      *
      * @see #getFontVariationSettings()
      *
      * @param settings the font variation settings. You can pass null or empty string as no
      *                 variation settings.
+     * @return true if the given settings is effective to at least one font file underlying this
+     *         typeface. This function also returns true for empty settings string. Otherwise
+     *         returns false
      */
-    public void setFontVariationSettings(String settings) {
+    public boolean setFontVariationSettings(String settings) {
         settings = TextUtils.nullIfEmpty(settings);
         if (settings == mFontVariationSettings
                 || (settings != null && settings.equals(mFontVariationSettings))) {
-            return;
+            return true;
+        }
+
+        if (settings == null || settings.length() == 0) {
+            mFontVariationSettings = null;
+            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+                      Collections.emptyList()));
+            return true;
+        }
+
+        final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings(settings);
+        final ArrayList<FontConfig.Axis> filteredAxes = new ArrayList<FontConfig.Axis>();
+        for (int i = 0; i < axes.size(); ++i) {
+            final FontConfig.Axis axis = axes.get(i);
+            if (mTypeface.isSupportedAxes(axis.getTag())) {
+                filteredAxes.add(axis);
+            }
+        }
+        if (filteredAxes.isEmpty()) {
+            return false;
         }
         mFontVariationSettings = settings;
-        setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, settings));
+        setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, filteredAxes));
+        return true;
     }
 
     /**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3416401..c74f65b 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;
@@ -34,18 +38,21 @@
 import android.os.ResultReceiver;
 import android.provider.FontsContract;
 import android.text.FontConfig;
+import android.util.Base64;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.LruCache;
 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;
@@ -53,10 +60,14 @@
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 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.
@@ -116,6 +127,9 @@
 
     private int mStyle = 0;
 
+    private int[] mSupportedAxes;
+    private static final int[] EMPTY_AXES = {};
+
     private static void setDefault(Typeface t) {
         sDefaultTypeface = t;
         nativeSetDefault(t.native_instance);
@@ -144,13 +158,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);
@@ -180,10 +197,22 @@
                 if (typeface != null) {
                     return typeface;
                 }
+                List<List<String>> givenCerts = providerEntry.getCerts();
+                List<List<byte[]>> certs = new ArrayList<>();
+                if (givenCerts != null) {
+                    for (int i = 0; i < givenCerts.size(); i++) {
+                        List<String> certSet = givenCerts.get(i);
+                        List<byte[]> byteArraySet = new ArrayList<>();
+                        for (int j = 0; j < certSet.size(); j++) {
+                            byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
+                        }
+                        certs.add(byteArraySet);
+                    }
+                }
                 // Downloaded font and it wasn't cached, request it again and return a
                 // default font instead (nothing we can do now).
                 create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(),
-                        providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK);
+                        providerEntry.getQuery(), certs), NO_OP_REQUEST_CALLBACK);
                 return DEFAULT;
             }
 
@@ -194,8 +223,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;
                 }
             }
@@ -203,7 +234,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;
@@ -217,7 +249,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;
@@ -328,8 +360,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);
@@ -428,6 +461,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
@@ -492,10 +866,8 @@
 
     /** @hide */
     public static Typeface createFromTypefaceWithVariation(Typeface family,
-            String fontVariationSettings) {
+            List<FontConfig.Axis> axes) {
         final long ni = family == null ? 0 : family.native_instance;
-        ArrayList<FontConfig.Axis> axes =
-                FontListParser.parseFontVariationSettings(fontVariationSettings);
         return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
     }
 
@@ -516,49 +888,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) {
@@ -576,7 +925,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());
     }
 
@@ -586,15 +937,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);
@@ -604,9 +964,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;
@@ -619,9 +978,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;
@@ -658,8 +1016,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());
             }
         }
@@ -788,7 +1146,23 @@
         return result;
     }
 
+    /** @hide */
+    public boolean isSupportedAxes(int axis) {
+        if (mSupportedAxes == null) {
+            synchronized (this) {
+                if (mSupportedAxes == null) {
+                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
+                    if (mSupportedAxes == null) {
+                        mSupportedAxes = EMPTY_AXES;
+                    }
+                }
+            }
+        }
+        return Arrays.binarySearch(mSupportedAxes, axis) > 0;
+    }
+
     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);
@@ -796,4 +1170,5 @@
     private static native int  nativeGetStyle(long native_instance);
     private static native long nativeCreateFromArray(long[] familyArray);
     private static native void nativeSetDefault(long native_instance);
+    private static native int[] nativeGetSupportedAxes(long native_instance);
 }
diff --git a/libs/androidfw/.clang-format b/libs/androidfw/.clang-format
index ee1bee2..c91502a 100644
--- a/libs/androidfw/.clang-format
+++ b/libs/androidfw/.clang-format
@@ -1,2 +1,7 @@
 BasedOnStyle: Google
 ColumnLimit: 100
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 359cfac..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;
             }
         }
@@ -6431,32 +6433,42 @@
             }
 
             if (newEntryCount > 0) {
+                bool addToType = true;
                 uint8_t typeIndex = typeSpec->id - 1;
                 ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
                 if (idmapIndex >= 0) {
                     typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+                } else if (header->resourceIDMap != NULL) {
+                    // This is an overlay, but the types in this overlay are not
+                    // overlaying anything according to the idmap. We can skip these
+                    // as they will otherwise conflict with the other resources in the package
+                    // without a mapping.
+                    addToType = false;
                 }
 
-                TypeList& typeList = group->types.editItemAt(typeIndex);
-                if (!typeList.isEmpty()) {
-                    const Type* existingType = typeList[0];
-                    if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
-                        ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
-                                (int) newEntryCount, (int) existingType->entryCount);
-                        // We should normally abort here, but some legacy apps declare
-                        // resources in the 'android' package (old bug in AAPT).
+                if (addToType) {
+                    TypeList& typeList = group->types.editItemAt(typeIndex);
+                    if (!typeList.isEmpty()) {
+                        const Type* existingType = typeList[0];
+                        if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+                            ALOGW("ResTable_typeSpec entry count inconsistent: "
+                                  "given %d, previously %d",
+                                  (int) newEntryCount, (int) existingType->entryCount);
+                            // We should normally abort here, but some legacy apps declare
+                            // resources in the 'android' package (old bug in AAPT).
+                        }
                     }
-                }
 
-                Type* t = new Type(header, package, newEntryCount);
-                t->typeSpec = typeSpec;
-                t->typeSpecFlags = (const uint32_t*)(
-                        ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
-                if (idmapIndex >= 0) {
-                    t->idmapEntries = idmapEntries[idmapIndex];
+                    Type* t = new Type(header, package, newEntryCount);
+                    t->typeSpec = typeSpec;
+                    t->typeSpecFlags = (const uint32_t*)(
+                            ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+                    if (idmapIndex >= 0) {
+                        t->idmapEntries = idmapEntries[idmapIndex];
+                    }
+                    typeList.add(t);
+                    group->largestTypeId = max(group->largestTypeId, typeSpec->id);
                 }
-                typeList.add(t);
-                group->largestTypeId = max(group->largestTypeId, typeSpec->id);
             } else {
                 ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
             }
@@ -6499,31 +6511,40 @@
             }
 
             if (newEntryCount > 0) {
+                bool addToType = true;
                 uint8_t typeIndex = type->id - 1;
                 ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
                 if (idmapIndex >= 0) {
                     typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+                } else if (header->resourceIDMap != NULL) {
+                    // This is an overlay, but the types in this overlay are not
+                    // overlaying anything according to the idmap. We can skip these
+                    // as they will otherwise conflict with the other resources in the package
+                    // without a mapping.
+                    addToType = false;
                 }
 
-                TypeList& typeList = group->types.editItemAt(typeIndex);
-                if (typeList.isEmpty()) {
-                    ALOGE("No TypeSpec for type %d", type->id);
-                    return (mError=BAD_TYPE);
-                }
+                if (addToType) {
+                    TypeList& typeList = group->types.editItemAt(typeIndex);
+                    if (typeList.isEmpty()) {
+                        ALOGE("No TypeSpec for type %d", type->id);
+                        return (mError=BAD_TYPE);
+                    }
 
-                Type* t = typeList.editItemAt(typeList.size() - 1);
-                if (t->package != package) {
-                    ALOGE("No TypeSpec for type %d", type->id);
-                    return (mError=BAD_TYPE);
-                }
+                    Type* t = typeList.editItemAt(typeList.size() - 1);
+                    if (t->package != package) {
+                        ALOGE("No TypeSpec for type %d", type->id);
+                        return (mError=BAD_TYPE);
+                    }
 
-                t->configs.add(type);
+                    t->configs.add(type);
 
-                if (kDebugTableGetEntry) {
-                    ResTable_config thisConfig;
-                    thisConfig.copyFromDtoH(type->config);
-                    ALOGI("Adding config to type %d: %s\n", type->id,
-                            thisConfig.toString().string());
+                    if (kDebugTableGetEntry) {
+                        ResTable_config thisConfig;
+                        thisConfig.copyFromDtoH(type->config);
+                        ALOGI("Adding config to type %d: %s\n", type->id,
+                                thisConfig.toString().string());
+                    }
                 }
             } else {
                 ALOGV("Skipping empty ResTable_type for type %d", type->id);
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 0928b1b..d12be18 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -30,25 +30,23 @@
  protected:
   void SetUp() override {
     std::string contents;
-    ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
-                                        "resources.arsc", &contents));
-    ASSERT_EQ(NO_ERROR,
-              target_table_.add(contents.data(), contents.size(), 0, true));
+    ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc",
+                                        &contents));
+    ASSERT_EQ(NO_ERROR, target_table_.add(contents.data(), contents.size(), 0, true));
 
-    ASSERT_TRUE(
-        ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
-                                "resources.arsc", &overlay_data_));
+    ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
+                                        "resources.arsc", &overlay_data_));
     ResTable overlay_table;
-    ASSERT_EQ(NO_ERROR,
-              overlay_table.add(overlay_data_.data(), overlay_data_.size()));
+    ASSERT_EQ(NO_ERROR, overlay_table.add(overlay_data_.data(), overlay_data_.size()));
 
     char target_name[256] = "com.android.basic";
-    ASSERT_EQ(NO_ERROR,
-              target_table_.createIdmap(overlay_table, 0, 0, target_name,
-                                        target_name, &data_, &data_size_));
+    ASSERT_EQ(NO_ERROR, target_table_.createIdmap(overlay_table, 0, 0, target_name, target_name,
+                                                  &data_, &data_size_));
   }
 
-  void TearDown() override { ::free(data_); }
+  void TearDown() override {
+    ::free(data_);
+  }
 
   ResTable target_table_;
   std::string overlay_data_;
@@ -56,13 +54,12 @@
   size_t data_size_ = 0;
 };
 
-TEST_F(IdmapTest, canLoadIdmap) {
+TEST_F(IdmapTest, CanLoadIdmap) {
   ASSERT_EQ(NO_ERROR,
-            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
-                              data_size_));
+            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
 }
 
-TEST_F(IdmapTest, overlayOverridesResourceValue) {
+TEST_F(IdmapTest, OverlayOverridesResourceValue) {
   Res_value val;
   ssize_t block = target_table_.getResource(R::string::test2, &val, false);
   ASSERT_GE(block, 0);
@@ -71,45 +68,60 @@
   ASSERT_TRUE(pool != NULL);
   ASSERT_LT(val.data, pool->size());
 
-  size_t strLen;
-  const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
-  ASSERT_TRUE(targetStr16 != NULL);
-  ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+  size_t str_len;
+  const char16_t* target_str16 = pool->stringAt(val.data, &str_len);
+  ASSERT_TRUE(target_str16 != NULL);
+  ASSERT_EQ(String16("test2"), String16(target_str16, str_len));
 
   ASSERT_EQ(NO_ERROR,
-            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
-                              data_size_));
+            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
 
-  ssize_t newBlock = target_table_.getResource(R::string::test2, &val, false);
-  ASSERT_GE(newBlock, 0);
-  ASSERT_NE(block, newBlock);
+  ssize_t new_block = target_table_.getResource(R::string::test2, &val, false);
+  ASSERT_GE(new_block, 0);
+  ASSERT_NE(block, new_block);
   ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
-  pool = target_table_.getTableStringBlock(newBlock);
+  pool = target_table_.getTableStringBlock(new_block);
   ASSERT_TRUE(pool != NULL);
   ASSERT_LT(val.data, pool->size());
 
-  targetStr16 = pool->stringAt(val.data, &strLen);
-  ASSERT_TRUE(targetStr16 != NULL);
-  ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen));
+  target_str16 = pool->stringAt(val.data, &str_len);
+  ASSERT_TRUE(target_str16 != NULL);
+  ASSERT_EQ(String16("test2-overlay"), String16(target_str16, str_len));
 }
 
-TEST_F(IdmapTest, overlaidResourceHasSameName) {
+TEST_F(IdmapTest, OverlaidResourceHasSameName) {
   ASSERT_EQ(NO_ERROR,
-            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
-                              data_size_));
+            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
 
-  ResTable::resource_name resName;
-  ASSERT_TRUE(
-      target_table_.getResourceName(R::array::integerArray1, false, &resName));
+  ResTable::resource_name res_name;
+  ASSERT_TRUE(target_table_.getResourceName(R::array::integerArray1, false, &res_name));
 
-  ASSERT_TRUE(resName.package != NULL);
-  ASSERT_TRUE(resName.type != NULL);
-  ASSERT_TRUE(resName.name != NULL);
+  ASSERT_TRUE(res_name.package != NULL);
+  ASSERT_TRUE(res_name.type != NULL);
+  ASSERT_TRUE(res_name.name != NULL);
 
-  EXPECT_EQ(String16("com.android.basic"),
-            String16(resName.package, resName.packageLen));
-  EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen));
-  EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen));
+  EXPECT_EQ(String16("com.android.basic"), String16(res_name.package, res_name.packageLen));
+  EXPECT_EQ(String16("array"), String16(res_name.type, res_name.typeLen));
+  EXPECT_EQ(String16("integerArray1"), String16(res_name.name, res_name.nameLen));
+}
+
+constexpr const uint32_t kNonOverlaidResourceId = 0x7fff0000u;
+
+TEST_F(IdmapTest, OverlayDoesNotIncludeNonOverlaidResources) {
+  // First check that the resource we're trying to not include when overlaid is present when
+  // the overlay is loaded as a standalone APK.
+  ResTable table;
+  ASSERT_EQ(NO_ERROR, table.add(overlay_data_.data(), overlay_data_.size(), 0, true));
+
+  Res_value val;
+  ssize_t block = table.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+  ASSERT_GE(block, 0);
+
+  // Now add the overlay and verify that the unoverlaid resource is gone.
+  ASSERT_EQ(NO_ERROR,
+            target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
+  block = target_table_.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+  ASSERT_LT(block, 0);
 }
 
 }  // namespace
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index e0e0543..40bf17c 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
index 3e1af98..8e4417e 100644
--- a/libs/androidfw/tests/data/overlay/res/values/values.xml
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -20,4 +20,6 @@
         <item>10</item>
         <item>11</item>
     </integer-array>
+    <public type="animator" name="unoverlaid" id="0x7fff0000" />
+    <item type="animator" name="unoverlaid">@null</item>
 </resources>
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 812e4d8..daf14af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -522,8 +522,10 @@
     SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
 #endif
     const int ptCount = vertexCount >> 1;
-    mCanvas->drawVertices(vertexMode, ptCount, (SkPoint*)verts, (SkPoint*)texs,
-                          (SkColor*)colors, indices, indexCount, paint);
+    mCanvas->drawVertices(SkVertices::MakeCopy(vertexMode, ptCount, (SkPoint*)verts,
+                                               (SkPoint*)texs, (SkColor*)colors,
+                                               indexCount, indices),
+                          SkBlendMode::kModulate, paint);
 }
 
 // ----------------------------------------------------------------------------
@@ -560,23 +562,17 @@
     hwuiBitmap.getSkBitmap(&bitmap);
     const int ptCount = (meshWidth + 1) * (meshHeight + 1);
     const int indexCount = meshWidth * meshHeight * 6;
-
-    /*  Our temp storage holds 2 or 3 arrays.
-        texture points [ptCount * sizeof(SkPoint)]
-        optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
-            copy to convert from float to fixed
-        indices [ptCount * sizeof(uint16_t)]
-    */
-    ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
-    storageSize += indexCount * sizeof(uint16_t);  // indices[]
-
-
-#ifndef SK_SCALAR_IS_FLOAT
-    SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
-#endif
-    std::unique_ptr<char[]> storage(new char[storageSize]);
-    SkPoint* texs = (SkPoint*)storage.get();
-    uint16_t* indices = (uint16_t*)(texs + ptCount);
+    uint32_t flags = SkVertices::kHasTexCoords_BuilderFlag;
+    if (colors) {
+        flags |= SkVertices::kHasColors_BuilderFlag;
+    }
+    SkVertices::Builder builder(SkCanvas::kTriangles_VertexMode, ptCount, indexCount, flags);
+    memcpy(builder.positions(), vertices, ptCount * sizeof(SkPoint));
+    if (colors) {
+        memcpy(builder.colors(), colors, ptCount * sizeof(SkColor));
+    }
+    SkPoint* texs = builder.texCoords();
+    uint16_t* indices = builder.indices();
 
     // cons up texture coordinates and indices
     {
@@ -625,7 +621,6 @@
             index += 1;
         }
         SkASSERT(indexPtr - indices == indexCount);
-        SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
     }
 
     // double-check that we have legal indices
@@ -646,9 +641,7 @@
     sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
     tmpPaint.setShader(image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode));
 
-    mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
-                         texs, (const SkColor*)colors, indices,
-                         indexCount, tmpPaint);
+    mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
 }
 
 void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 20ca80b..f6e92dc 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -31,6 +31,7 @@
 #include <SkRSXform.h>
 #include <SkSurface.h>
 #include <SkTextBlobRunIterator.h>
+#include <SkVertices.h>
 
 namespace android {
 namespace uirenderer {
@@ -180,20 +181,20 @@
     }
 }
 
-void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[],
-        const SkPoint texs[], const SkColor colors[], SkBlendMode, const uint16_t indices[],
-        int indexCount, const SkPaint& paint) {
+void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
+        const SkPaint& paint) {
     // TODO: should we pass through blendmode
     if (mFilterHwuiCalls) {
         return;
     }
     // convert the SkPoints into floats
     static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
-    const int floatCount = vertexCount << 1;
-    const float* vArray = &vertices[0].fX;
-    const float* tArray = (texs) ? &texs[0].fX : NULL;
-    const int* cArray = (colors) ? (int*)colors : NULL;
-    mCanvas->drawVertices(mode, floatCount, vArray, tArray, cArray, indices, indexCount, paint);
+    const int floatCount = vertices->vertexCount() << 1;
+    const float* vArray = (const float*)vertices->positions();
+    const float* tArray = (const float*)vertices->texCoords();
+    const int* cArray = (const int*)vertices->colors();
+    mCanvas->drawVertices(vertices->mode(), floatCount, vArray, tArray, cArray,
+            vertices->indices(), vertices->indexCount(), paint);
 }
 
 sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 3b1dd73..d11a779 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -75,10 +75,7 @@
             const SkPaint*);
     virtual void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst,
             const SkPaint*);
-    virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[],
-                                const SkPoint texs[], const SkColor colors[], SkBlendMode,
-                                const uint16_t indices[], int indexCount,
-                                const SkPaint&) override;
+    virtual void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
 
     virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
 
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/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 4831722..03d9496 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -80,9 +80,7 @@
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) {
         ADD_FAILURE() << "onDrawPoints not expected in this test";
     }
-    void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], const SkPoint texs[],
-            const SkColor colors[], SkBlendMode, const uint16_t indices[], int indexCount,
-            const SkPaint&) {
+    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) {
         ADD_FAILURE() << "onDrawVertices not expected in this test";
     }
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count,
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/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 760a2d1..56a5737 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Files;
@@ -133,6 +134,8 @@
     private int mBatteryLevel;
     private int mBatteryScale;
 
+    private int mDeviceType;
+
     static {
         System.loadLibrary("media_jni");
     }
@@ -195,6 +198,7 @@
         }
 
         initDeviceProperties(context);
+        mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
 
         mCloseGuard.open("close");
     }
@@ -710,6 +714,7 @@
             MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
             MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
             MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
+            MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
         };
     }
 
@@ -869,6 +874,10 @@
                 outStringValue[imageSize.length()] = 0;
                 return MtpConstants.RESPONSE_OK;
 
+            case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+                outIntValue[0] = mDeviceType;
+                return MtpConstants.RESPONSE_OK;
+
             // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
 
             default:
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 34a7f7c..f7f79169 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -76,6 +76,7 @@
 static jfieldID field_context;
 static jfieldID field_batteryLevel;
 static jfieldID field_batteryScale;
+static jfieldID field_deviceType;
 
 // MtpPropertyList fields
 static jfieldID field_mCount;
@@ -1030,6 +1031,7 @@
     {   MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,       MTP_TYPE_STR },
     {   MTP_DEVICE_PROPERTY_IMAGE_SIZE,                 MTP_TYPE_STR },
     {   MTP_DEVICE_PROPERTY_BATTERY_LEVEL,              MTP_TYPE_UINT8 },
+    {   MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,      MTP_TYPE_UINT32 },
 };
 
 bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
@@ -1209,6 +1211,10 @@
             result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
             result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
             break;
+        case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+            result = new MtpProperty(property, MTP_TYPE_UINT32);
+            result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType);
+            break;
     }
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -1388,6 +1394,11 @@
         ALOGE("Can't find MtpDatabase.mBatteryScale");
         return -1;
     }
+    field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I");
+    if (field_deviceType == NULL) {
+        ALOGE("Can't find MtpDatabase.mDeviceType");
+        return -1;
+    }
 
     // now set up fields for MtpPropertyList class
     clazz = env->FindClass("android/mtp/MtpPropertyList");
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e1e60bb..e49463f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -35,9 +35,7 @@
 import android.bluetooth.le.ScanSettings;
 import android.companion.AssociationRequest;
 import android.companion.BluetoothDeviceFilter;
-import android.companion.BluetoothDeviceFilterUtils;
 import android.companion.BluetoothLEDeviceFilter;
-import android.companion.CompanionDeviceManager;
 import android.companion.DeviceFilter;
 import android.companion.ICompanionDeviceDiscoveryService;
 import android.companion.ICompanionDeviceDiscoveryServiceCallback;
@@ -60,7 +58,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.TextView;
 
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
@@ -185,10 +183,10 @@
         mRequest = request;
 
         mFilters = request.getDeviceFilters();
-        mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class);
-        mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class);
-        mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
-        mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+        mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+        mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+        mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+        mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
 
         reset();
 
@@ -357,7 +355,7 @@
         public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
                 T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
             if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
-            final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev));
+            final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
             return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
         }
 
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 0b290ce..1072f95 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -9,6 +9,7 @@
     <application android:label="@string/app_label">
         <provider
             android:name=".ExternalStorageProvider"
+            android:label="@string/storage_description"
             android:authorities="com.android.externalstorage.documents"
             android:grantUriPermissions="true"
             android:exported="true"
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index 8b16d3c..324fb59 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -18,6 +18,9 @@
     <!-- Title of the external storage application [CHAR LIMIT=32] -->
     <string name="app_label">External Storage</string>
 
+    <!-- Meaningful storage location description shown to client applications [CHAR LIMIT=32] -->
+    <string name="storage_description">Local storage</string>
+
     <!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
     <string name="root_internal_storage">Internal storage</string>
     <!-- Title for directory in which a user may store their own documents and files. [CHAR LIMIT=24] -->
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/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 6e1385a..187e35a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -94,10 +94,10 @@
                     // but the content has changed.
                     if (mNextCommand == null) {
                         if (mUpdateSpec.pages != null && (mDocumentInfo.changed
-                                || mDocumentInfo.writtenPages == null
+                                || mDocumentInfo.pagesWrittenToFile == null
                                 || (mDocumentInfo.info.getPageCount()
                                         != PrintDocumentInfo.PAGE_COUNT_UNKNOWN
-                                && !PageRangeUtils.contains(mDocumentInfo.writtenPages,
+                                && !PageRangeUtils.contains(mDocumentInfo.pagesWrittenToFile,
                                         mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) {
                             mNextCommand = new WriteCommand(mContext, mLooper,
                                     mPrintDocumentAdapter, mDocumentInfo,
@@ -106,9 +106,10 @@
                         } else {
                             if (mUpdateSpec.pages != null) {
                                 // If we have the requested pages, update which ones to be printed.
-                                mDocumentInfo.printedPages = PageRangeUtils.computePrintedPages(
-                                        mUpdateSpec.pages, mDocumentInfo.writtenPages,
-                                        mDocumentInfo.info.getPageCount());
+                                mDocumentInfo.pagesInFileToPrint =
+                                        PageRangeUtils.computeWhichPagesInFileToPrint(
+                                                mUpdateSpec.pages, mDocumentInfo.pagesWrittenToFile,
+                                                mDocumentInfo.info.getPageCount());
                             }
                             // Notify we are done.
                             mState = STATE_UPDATED;
@@ -514,8 +515,20 @@
         public PrintAttributes attributes;
         public Bundle metadata;
         public PrintDocumentInfo info;
-        public PageRange[] printedPages;
-        public PageRange[] writtenPages;
+
+        /**
+         * Which pages out of the ones written to the file to print. This is not indexed by the
+         * document pages, but by the page number in the file.
+         * <p>E.g. if a document has 10 pages, we want pages 4-5 and 7, but only page 3-9 are in the
+         * file. This would contain 1-2 and 4.</p>
+         *
+         * @see PageRangeUtils#computeWhichPagesInFileToPrint
+         */
+        public PageRange[] pagesInFileToPrint;
+
+        /** Pages of the whole document that are currently written to file */
+        public PageRange[] pagesWrittenToFile;
+
         public MutexFileProvider fileProvider;
         public boolean changed;
         public boolean updated;
@@ -783,8 +796,8 @@
             if (changed || !equalsIgnoreSize(mDocument.info, info)) {
                 // If the content changed we throw away all pages as
                 // we will request them again with the new content.
-                mDocument.writtenPages = null;
-                mDocument.printedPages = null;
+                mDocument.pagesWrittenToFile = null;
+                mDocument.pagesInFileToPrint = null;
                 mDocument.changed = true;
             }
 
@@ -1098,17 +1111,17 @@
             }
 
             PageRange[] writtenPages = PageRangeUtils.normalize(pages);
-            PageRange[] printedPages = PageRangeUtils.computePrintedPages(
+            PageRange[] printedPages = PageRangeUtils.computeWhichPagesInFileToPrint(
                     mPages, writtenPages, mPageCount);
 
             // Handle if we got invalid pages
             if (printedPages != null) {
-                mDocument.writtenPages = writtenPages;
-                mDocument.printedPages = printedPages;
+                mDocument.pagesWrittenToFile = writtenPages;
+                mDocument.pagesInFileToPrint = printedPages;
                 completed();
             } else {
-                mDocument.writtenPages = null;
-                mDocument.printedPages = null;
+                mDocument.pagesWrittenToFile = null;
+                mDocument.pagesInFileToPrint = null;
                 failed(mContext.getString(R.string.print_error_default_message));
             }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4b51917..f6df995 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -85,8 +85,8 @@
 import android.widget.ImageView;
 import android.widget.Spinner;
 import android.widget.TextView;
-
 import android.widget.Toast;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.printspooler.R;
@@ -120,6 +120,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
         PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
@@ -543,8 +544,8 @@
         // pages in the printed document.
         PrintDocumentInfo info = document.info;
         if (info != null) {
-            final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
-                    getAdjustedPageCount(info));
+            final int pageCount = PageRangeUtils.getNormalizedPageCount(
+                    document.pagesWrittenToFile, getAdjustedPageCount(info));
             PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
                     .setContentType(info.getContentType())
                     .setPageCount(pageCount)
@@ -558,7 +559,7 @@
             }
 
             mPrintJob.setDocumentInfo(adjustedInfo);
-            mPrintJob.setPages(document.printedPages);
+            mPrintJob.setPages(document.pagesInFileToPrint);
         }
 
         switch (mState) {
@@ -627,7 +628,7 @@
         // Update the preview controller.
         mPrintPreviewController.onContentUpdated(contentUpdated,
                 getAdjustedPageCount(documentInfo.info),
-                mPrintedDocument.getDocumentInfo().writtenPages,
+                mPrintedDocument.getDocumentInfo().pagesWrittenToFile,
                 mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
                 mPrintJob.getAttributes().getMinMargins());
     }
@@ -2105,14 +2106,15 @@
         // If saving to PDF, apply the attibutes as we are acting as a print service.
         PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
                 ?  mPrintJob.getAttributes() : null;
-        new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
-            @Override
-            public void run() {
+        new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, error -> {
+            if (error == null) {
                 if (writeToUri != null) {
                     mPrintedDocument.writeContent(getContentResolver(), writeToUri);
                 }
                 setState(STATE_PRINT_COMPLETED);
                 doFinish();
+            } else {
+                onPrintDocumentError(error);
             }
         }).transform();
     }
@@ -3096,11 +3098,11 @@
 
         private final PrintAttributes mAttributesToApply;
 
-        private final Runnable mCallback;
+        private final Consumer<String> mCallback;
 
         public DocumentTransformer(Context context, PrintJobInfo printJob,
                 MutexFileProvider fileProvider, PrintAttributes attributes,
-                Runnable callback) {
+                Consumer<String> callback) {
             mContext = context;
             mPrintJob = printJob;
             mFileProvider = fileProvider;
@@ -3112,7 +3114,7 @@
         public void transform() {
             // If we have only the pages we want, done.
             if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
-                mCallback.run();
+                mCallback.accept(null);
                 return;
             }
 
@@ -3126,22 +3128,26 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
-            new AsyncTask<Void, Void, Void>() {
+            new AsyncTask<Void, Void, String>() {
                 @Override
-                protected Void doInBackground(Void... params) {
+                protected String doInBackground(Void... params) {
                     // It's OK to access the data members as they are
                     // final and this code is the last one to touch
                     // them as shredding is the very last step, so the
                     // UI is not interactive at this point.
-                    doTransform(editor);
-                    updatePrintJob();
-                    return null;
+                    try {
+                        doTransform(editor);
+                        updatePrintJob();
+                        return null;
+                    } catch (IOException | RemoteException | IllegalStateException e) {
+                        return e.toString();
+                    }
                 }
 
                 @Override
-                protected void onPostExecute(Void aVoid) {
+                protected void onPostExecute(String error) {
                     mContext.unbindService(DocumentTransformer.this);
-                    mCallback.run();
+                    mCallback.accept(error);
                 }
             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         }
@@ -3151,7 +3157,7 @@
             /* do nothing */
         }
 
-        private void doTransform(IPdfEditor editor) {
+        private void doTransform(IPdfEditor editor) throws IOException, RemoteException {
             File tempFile = null;
             ParcelFileDescriptor src = null;
             ParcelFileDescriptor dst = null;
@@ -3190,8 +3196,6 @@
                 in = new FileInputStream(tempFile);
                 out = new FileOutputStream(jobFile);
                 Streams.copy(in, out);
-            } catch (IOException|RemoteException e) {
-                Log.e(LOG_TAG, "Error dropping pages", e);
             } finally {
                 IoUtils.closeQuietly(src);
                 IoUtils.closeQuietly(dst);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index 7425c03..a36f583 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -394,33 +394,42 @@
         return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
     }
 
-    public static PageRange[] computePrintedPages(PageRange[] requestedPages,
-            PageRange[] writtenPages, int pageCount) {
+    /**
+     * Compute the pages of the file that correspond to the requested pages in the doc.
+     *
+     * @param pagesInDocRequested The requested pages, doc-indexed
+     * @param pagesWrittenToFile The pages in the file
+     * @param pageCount The number of pages in the doc
+     *
+     * @return The pages, file-indexed
+     */
+    public static PageRange[] computeWhichPagesInFileToPrint(PageRange[] pagesInDocRequested,
+            PageRange[] pagesWrittenToFile, int pageCount) {
         // Adjust the print job pages based on what was requested and written.
         // The cases are ordered in the most expected to the least expected
         // with a special case first where the app does not know the page count
         // so we ask for all to be written.
-        if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
+        if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
                 && pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
             return ALL_PAGES_RANGE;
-        } else if (Arrays.equals(writtenPages, requestedPages)) {
+        } else if (Arrays.equals(pagesWrittenToFile, pagesInDocRequested)) {
             // We got a document with exactly the pages we wanted. Hence,
             // the printer has to print all pages in the data.
             return ALL_PAGES_RANGE;
-        } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
+        } else if (Arrays.equals(pagesWrittenToFile, ALL_PAGES_RANGE)) {
             // We requested specific pages but got all of them. Hence,
             // the printer has to print only the requested pages.
-            return requestedPages;
-        } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
+            return pagesInDocRequested;
+        } else if (PageRangeUtils.contains(pagesWrittenToFile, pagesInDocRequested, pageCount)) {
             // We requested specific pages and got more but not all pages.
             // Hence, we have to offset appropriately the printed pages to
             // be based off the start of the written ones instead of zero.
             // The written pages are always non-null and not empty.
-            final int offset = -writtenPages[0].getStart();
-            PageRangeUtils.offset(requestedPages, offset);
-            return requestedPages;
-        } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
-                && isAllPages(writtenPages, pageCount)) {
+            final int offset = -pagesWrittenToFile[0].getStart();
+            PageRangeUtils.offset(pagesInDocRequested, offset);
+            return pagesInDocRequested;
+        } else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
+                && isAllPages(pagesWrittenToFile, pageCount)) {
             // We requested all pages via the special constant and got all
             // of them as an explicit enumeration. Hence, the printer has
             // to print only the requested pages.
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1010a8a..f6c5ade 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -73,8 +73,10 @@
     <!-- Summary for saved networks -->
     <string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
 
-    <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
-    <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+    <!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
+    <string name="connected_via_network_scorer">Automatically connected via %1$s</string>
+    <!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
+    <string name="connected_via_network_scorer_default">Automatically connected via Network Quality Scorer</string>
     <!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
     <string name="connected_via_passpoint">Connected via %1$s</string>
     <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
@@ -775,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/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 336942a..c2ce7c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -686,7 +686,11 @@
             }
 
             if (comparator != null) {
-                Collections.sort(filteredApps, comparator);
+                synchronized (mEntriesMap) {
+                    // Locking to ensure that the background handler does not mutate
+                    // the size of AppEntries used for ordering while sorting.
+                    Collections.sort(filteredApps, comparator);
+                }
             }
 
             synchronized (mRebuildSync) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index fed48b4..45004c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -27,6 +27,8 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppData;
 import android.net.ScoredNetwork;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
@@ -949,7 +951,15 @@
                 return String.format(format, passpointProvider);
             } else if (isEphemeral) {
                 // Special case for connected + ephemeral networks.
-                return context.getString(R.string.connected_via_wfa);
+                final NetworkScoreManager networkScoreManager = context.getSystemService(
+                        NetworkScoreManager.class);
+                NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
+                if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
+                    String format = context.getString(R.string.connected_via_network_scorer);
+                    return String.format(format, scorer.getRecommendationServiceLabel());
+                } else {
+                    return context.getString(R.string.connected_via_network_scorer_default);
+                }
             }
         }
 
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/drawable/pip_notification_icon.xml b/packages/SystemUI/res/drawable/pip_notification_icon.xml
new file mode 100644
index 0000000..592bc60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_notification_icon.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 31bd8b9..195eb9b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -21,110 +21,113 @@
         android:layout_height="wrap_content"
         android:id="@+id/notification_guts"
         android:clickable="true"
-        android:gravity="top|start"
         android:orientation="vertical"
         android:paddingStart="@*android:dimen/notification_content_margin_start"
-        android:paddingEnd="8dp"
         android:background="@color/notification_guts_bg_color"
         android:theme="@*android:style/Theme.DeviceDefault.Light">
 
     <!-- Package Info -->
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp" >
+        android:layout_height="@*android:dimen/notification_header_height"
+        android:clipChildren="false"
+        android:paddingTop="@*android:dimen/notification_header_padding_top"
+        android:paddingBottom="@*android:dimen/notification_header_padding_bottom"
+        android:gravity="center_vertical"
+        android:orientation="horizontal" >
         <ImageView
             android:id="@+id/pkgicon"
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:layout_marginEnd="6dp"
-            android:contentDescription="@null"
-            android:scaleType="fitCenter" />
+            android:layout_width="@*android:dimen/notification_header_icon_size"
+            android:layout_height="@*android:dimen/notification_header_icon_size"
+            android:layout_marginEnd="3dp"/>
         <TextView
             android:id="@+id/pkgname"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            style="@style/TextAppearance.NotificationGuts.Secondary" />
+            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+            android:layout_marginStart="3dp"
+            android:layout_marginEnd="2dp"
+            android:singleLine="true"/>
         <TextView
             android:id="@+id/pkg_group_divider"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
             android:layout_marginStart="2dp"
             android:layout_marginEnd="2dp"
-            android:text="@string/notification_header_divider_symbol_with_spaces"/>
+            android:text="@*android:string/notification_header_divider_symbol"/>
         <TextView
             android:id="@+id/group_name"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            style="@style/TextAppearance.NotificationGuts.Secondary" />
+            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+            android:layout_marginStart="2dp"
+            android:layout_marginEnd="2dp"/>
     </LinearLayout>
 
-    <!-- Channel Info -->
+    <!-- Channel Info Block -->
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingBottom="8dp"
+        android:layout_marginBottom="20dp"
+        android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
         android:orientation="horizontal">
-        <TextView
-            android:id="@+id/channel_name"
+        <!-- Channel Text -->
+        <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_below="@id/pkgname"
-            style="@style/TextAppearance.NotificationGuts.Header" />
+            android:orientation="vertical">
+            <!-- Channel Name -->
+            <TextView
+                android:id="@+id/channel_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="6dp"
+                style="@style/TextAppearance.NotificationInfo.Primary" />
+            <!-- Secondary Text - only one shows at a time -->
+            <TextView
+                android:id="@+id/channel_disabled"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/notification_channel_disabled"
+                style="@style/TextAppearance.NotificationInfo.Secondary.Warning" />
+            <TextView
+                android:id="@+id/num_channels_desc"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/notification_channel_disabled"
+                style="@style/TextAppearance.NotificationInfo.Secondary" />
+        </LinearLayout>
+        <!-- Ban Channel Switch -->
         <Switch
             android:id="@+id/channel_enabled_switch"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="end"
+            android:layout_gravity="end|center_vertical"
             android:layout_weight="1"
             android:background="@null" />
     </LinearLayout>
 
-    <!-- Secondary Text - only one shows at a time -->
-    <TextView
-        android:id="@+id/channel_disabled"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/notification_channel_disabled"
-        style="@style/TextAppearance.NotificationGuts.SecondaryWarning" />
-    <TextView
-        android:id="@+id/num_channels_desc"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/TextAppearance.NotificationGuts.Secondary" />
-
     <!-- Settings and Done buttons -->
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="48dp"
+        android:orientation="horizontal"
         android:gravity="end"
-        android:paddingTop="16dp"
-        android:paddingBottom="8dp" >
-
+        android:layout_marginBottom="8dp" >
         <TextView
             android:id="@+id/more_settings"
             android:text="@string/notification_more_settings"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            style="@style/TextAppearance.NotificationGuts.Button"
-            android:background="@drawable/btn_borderless_rect"
-            android:gravity="center"
-            android:paddingEnd="8dp"
-            android:paddingStart="8dp"
-            android:focusable="true" />
-
+            android:layout_height="match_parent"
+            android:layout_marginEnd="8dp"
+            style="@style/TextAppearance.NotificationInfo.Button"/>
         <TextView
             android:id="@+id/done"
             android:text="@string/notification_done"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            style="@style/TextAppearance.NotificationGuts.Button"
-            android:background="@drawable/btn_borderless_rect"
-            android:gravity="center"
-            android:layout_marginStart="8dp"
+            android:layout_height="match_parent"
             android:layout_marginEnd="8dp"
-            android:focusable="true"/>
+            style="@style/TextAppearance.NotificationInfo.Button"/>
     </LinearLayout>
 </com.android.systemui.statusbar.NotificationInfo>
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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c9e7e57..3e5e586 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1340,18 +1340,33 @@
     <!-- Notification Inline Controls: Header for apps that are not yet using notification channels. -->
     <string name="notification_header_default_channel">Notifications</string>
 
-    <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
-    <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
-
     <!-- Notification Inline Controls: Shown when a channel's notifications are currently blocked -->
     <string name="notification_channel_disabled">You won\'t get these notifications anymore.</string>
 
+    <!-- Notification: Control panel: Label that shows how many channels are included in this bundle
+        of notifications.  Replaces the channel name and only appears when there is more than one channel. -->
+    <string name="notification_num_channels"> <xliff:g id="number">%d</xliff:g> notification categories</string>
+
     <!-- Notification: Control panel: Label that shows how many channels this application has
-        defined, describing the current notification channel as "1 out of n". -->
+        defined, describing the current notification channel as "1 out of n categories from this app". -->
     <plurals name="notification_num_channels_desc">
         <item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> category from this app</item>
         <item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> categories from this app</item>
     </plurals>
+
+    <!-- Notification: Control panel: For bundles of notifications, this label that lists the
+        channels used by the contained notifications.  The first two channels are listed by name,
+        followed by "and N others"
+        Example: "Friend requests, Friend confirmations"
+        Example: "Friend requests, Friend confirmations, and 1 other"
+        Example: "Friend requests, Friend confirmations, and 2 others"
+    -->
+    <string name="notification_channels_list_desc_2"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g></string>
+    <plurals name="notification_channels_list_desc_2_and_others">
+        <item quantity="one"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g>, and <xliff:g id="number">%3$d</xliff:g> other</item>
+        <item quantity="other"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g>, and <xliff:g id="number">%3$d</xliff:g> others</item>
+    </plurals>
+
     <!-- Notification: Control panel: Label for button that launches notification settings. Used
         when this app has defined more than a single channel for notifications. -->
     <string name="notification_all_categories">All Categories</string>
@@ -1750,6 +1765,18 @@
     <!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]-->
     <string name="pip_phone_close">Close</string>
 
+    <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
+    <string name="pip_menu_title">Picture in picture menu</string>
+
+    <!-- User visible notification channel name for the PiP BTW notification. [CHAR LIMIT=NONE] -->
+    <string name="pip_notification_channel_name">Picture-in-picture</string>
+
+    <!-- PiP BTW notification title. [CHAR LIMIT=50] -->
+    <string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string>
+
+    <!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
+    <string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
+
     <!-- Tuner string -->
     <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
     <!-- Tuner string -->
@@ -1818,9 +1845,6 @@
     <!-- App label of the instant apps notification [CHAR LIMIT=60] -->
     <string name="instant_apps">Instant Apps</string>
 
-    <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
-    <string name="pip_menu_title">Picture in picture menu</string>
-
     <!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->
     <string name="instant_apps_message">Instant apps don\'t require installation.</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9168256..d6abda6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -349,38 +349,42 @@
         <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
     </style>
 
-    <style name="TextAppearance.NotificationGuts">
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">roboto-regular</item>
+    <style name="TextAppearance.NotificationInfo">
+        <item name="android:fontFamily">sans-serif</item>
         <item name="android:textColor">@android:color/black</item>
     </style>
 
-    <style name="TextAppearance.NotificationGuts.Header">
+    <style name="TextAppearance.NotificationInfo.Primary">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:alpha">0.87</item>
     </style>
 
-    <style name="TextAppearance.NotificationGuts.Secondary">
+    <style name="TextAppearance.NotificationInfo.Secondary">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:textSize">12sp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:alpha">0.54</item>
     </style>
 
-    <style name="TextAppearance.NotificationGuts.SecondaryWarning">
+    <style name="TextAppearance.NotificationInfo.Secondary.Warning">
         <item name="android:textColor">?android:attr/colorError</item>
-        <item name="android:textSize">12sp</item>
     </style>
 
-    <style name="TextAppearance.NotificationGuts.Button">
+    <style name="TextAppearance.NotificationInfo.Button">
+        <item name="android:fontFamily">sans-serif-medium</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textAllCaps">true</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
-        <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:background">@drawable/btn_borderless_rect</item>
+        <item name="android:gravity">center</item>
+        <item name="android:focusable">true</item>
+        <item name="android:paddingStart">8dp</item>
+        <item name="android:paddingEnd">8dp</item>
     </style>
 
     <style name="TextAppearance.SnoozeSnackBar">
         <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">roboto-regular</item>
+        <item name="android:fontFamily">sans-serif</item>
         <item name="android:textColor">@android:color/white</item>
     </style>
 
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/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a549c73..07bd242 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -278,7 +278,8 @@
      */
     private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT)
             .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                    | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                    | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
 
     /**
      * {@link #setKeyguardEnabled} waits on this condition when it reenables
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 5cd7e41..50720e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -91,6 +91,8 @@
                     player.setAudioAttributes(mCmd.attributes);
                     player.setDataSource(mCmd.context, mCmd.uri);
                     player.setLooping(mCmd.looping);
+                    player.setOnCompletionListener(NotificationPlayer.this);
+                    player.setOnErrorListener(NotificationPlayer.this);
                     player.prepare();
                     if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                             && (mCmd.uri.getEncodedPath().length() > 0)) {
@@ -118,12 +120,15 @@
                     //  can lead to AudioFocus being released too early, before the second sound is
                     //  done playing. This class should be modified to use a single thread, on which
                     //  command are issued, and on which it receives the completion callbacks.
-                    player.setOnCompletionListener(NotificationPlayer.this);
-                    player.setOnErrorListener(NotificationPlayer.this);
                     if (DEBUG)  { Log.d(mTag, "notification will be delayed by "
                             + mNotificationRampTimeMs + "ms"); }
-                    player.setStartDelayMs(mNotificationRampTimeMs);
-                    player.start();
+                    try {
+                        Thread.sleep(mNotificationRampTimeMs);
+                        player.start();
+                    } catch (InterruptedException e) {
+                        Log.e(mTag, "Exception while sleeping to sync notification playback"
+                                + " with ducking", e);
+                    }
                     if (mPlayer != null) {
                         mPlayer.release();
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ecc2fad..6cda076 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -56,6 +56,7 @@
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
+    private PipNotificationController mNotificationController;
     private PipTouchHandler mTouchHandler;
 
     /**
@@ -63,13 +64,24 @@
      */
     TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
-        public void onActivityPinned() {
+        public void onActivityPinned(String packageName) {
             if (!checkCurrentUserId(false /* debug */)) {
                 return;
             }
+
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
+            mNotificationController.onActivityPinned(packageName);
+        }
+
+        @Override
+        public void onActivityUnpinned() {
+            if (!checkCurrentUserId(false /* debug */)) {
+                return;
+            }
+
+            mNotificationController.onActivityUnpinned();
         }
 
         @Override
@@ -160,6 +172,7 @@
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
                 mInputConsumerController);
+        mNotificationController = new PipNotificationController(context, mActivityManager);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 9cb518c..2597ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -172,7 +172,6 @@
 
         updateFromIntent(getIntent());
         setTitle(R.string.pip_menu_title);
-        notifyActivityCallback(mMessenger);
     }
 
     @Override
@@ -249,7 +248,6 @@
 
     private void showMenu(Rect stackBounds, Rect movementBounds) {
         if (!mMenuVisible) {
-            setVisible(true);
             updateActionViews(stackBounds);
             if (mMenuContainerAnimator != null) {
                 mMenuContainerAnimator.cancel();
@@ -296,7 +294,9 @@
                     if (animationFinishedRunnable != null) {
                         animationFinishedRunnable.run();
                     }
-                    setVisible(false);
+                    if (getSystemService(AccessibilityManager.class).isEnabled()) {
+                        finish();
+                    }
                 }
             });
             mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
@@ -306,6 +306,7 @@
 
     private void updateFromIntent(Intent intent) {
         mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
+        notifyActivityCallback(mMessenger);
         ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
         if (actions != null) {
             mActions.clear();
@@ -405,7 +406,6 @@
     }
 
     private void updateDismissFraction(float fraction) {
-        setVisible(true);
         int alpha;
         if (mMenuVisible) {
             mMenuContainer.setAlpha(1-fraction);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
new file mode 100644
index 0000000..bdd6b65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
@@ -0,0 +1,142 @@
+/*
+ * 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.pip.phone;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
+ */
+public class PipNotificationController {
+    private static final String TAG = PipNotificationController.class.getSimpleName();
+
+    private static final String CHANNEL_ID = PipNotificationController.class.getName();
+    private static final int BTW_NOTIFICATION_ID = 0;
+
+    private Context mContext;
+    private IActivityManager mActivityManager;
+    private NotificationManager mNotificationManager;
+
+    public PipNotificationController(Context context, IActivityManager activityManager) {
+        mContext = context;
+        mActivityManager = activityManager;
+        mNotificationManager = NotificationManager.from(context);
+        createNotificationChannel();
+    }
+
+    public void onActivityPinned(String packageName) {
+        // Clear any existing notification
+        mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+
+        // Build a new notification
+        final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+                .setLocalOnly(true)
+                .setOngoing(true)
+                .setSmallIcon(R.drawable.pip_notification_icon)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        if (updateNotificationForApp(builder, packageName)) {
+            SystemUI.overrideNotificationAppName(mContext, builder);
+
+            // Show the new notification
+            mNotificationManager.notify(CHANNEL_ID, BTW_NOTIFICATION_ID, builder.build());
+        }
+    }
+
+    public void onActivityUnpinned() {
+        ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+        if (topPipActivity != null) {
+            onActivityPinned(topPipActivity.getPackageName());
+        } else {
+            mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+        }
+    }
+
+    /**
+     * Create the notification channel for the PiP BTW notifications if necessary.
+     */
+    private NotificationChannel createNotificationChannel() {
+        NotificationChannel channel = mNotificationManager.getNotificationChannel(CHANNEL_ID);
+        if (channel == null) {
+            channel = new NotificationChannel(CHANNEL_ID,
+                    mContext.getString(R.string.pip_notification_channel_name), IMPORTANCE_MIN);
+            channel.enableLights(false);
+            channel.enableVibration(false);
+            mNotificationManager.createNotificationChannel(channel);
+        }
+        return channel;
+    }
+
+    /**
+     * Updates the notification builder with app-specific information, returning whether it was
+     * successful.
+     */
+    private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = pm.getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not update notification for application", e);
+            return false;
+        }
+
+        if (appInfo != null) {
+            final String appName = pm.getApplicationLabel(appInfo).toString();
+            final String message = mContext.getString(R.string.pip_notification_message, appName);
+            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+                    Uri.fromParts("package", packageName, null));
+            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            final Icon appIcon = appInfo.icon != 0
+                    ? Icon.createWithResource(packageName, appInfo.icon)
+                    : Icon.createWithResource(Resources.getSystem(),
+                            com.android.internal.R.drawable.sym_def_app_icon);
+
+            builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
+                    .setContentText(message)
+                    .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
+                            settingsIntent, FLAG_CANCEL_CURRENT))
+                    .setStyle(new Notification.BigTextStyle().bigText(message))
+                    .setLargeIcon(appIcon);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index b34a07d..b2b5b02 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -17,6 +17,7 @@
 package com.android.systemui.pip.phone;
 
 import android.graphics.PointF;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
@@ -28,6 +29,7 @@
  */
 public class PipTouchState {
     private static final String TAG = "PipTouchHandler";
+    private static final boolean DEBUG = true;
 
     private ViewConfiguration mViewConfig;
 
@@ -72,6 +74,9 @@
                 initOrResetVelocityTracker();
 
                 mActivePointerId = ev.getPointerId(0);
+                if (DEBUG) {
+                    Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
+                }
                 mLastTouch.set(ev.getX(), ev.getY());
                 mDownTouch.set(mLastTouch);
                 mAllowDraggingOffscreen = true;
@@ -87,6 +92,11 @@
                 // Update the velocity tracker
                 mVelocityTracker.addMovement(ev);
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == -1) {
+                    Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
+                    break;
+                }
+
                 float x = ev.getX(pointerIndex);
                 float y = ev.getY(pointerIndex);
                 mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
@@ -119,6 +129,10 @@
                     // Select a new active pointer id and reset the movement state
                     final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
                     mActivePointerId = ev.getPointerId(newPointerIndex);
+                    if (DEBUG) {
+                        Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
+                                mActivePointerId);
+                    }
                     mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
                 }
                 break;
@@ -136,6 +150,11 @@
                 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
 
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == -1) {
+                    Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
+                    break;
+                }
+
                 mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
 
                 // Fall through to clean up
@@ -251,6 +270,7 @@
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
+        pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
         pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
         pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
         pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index b71c87d..b96b0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -627,7 +627,7 @@
         }
 
         @Override
-        public void onActivityPinned() {
+        public void onActivityPinned(String packageName) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
             if (!checkCurrentUserId(DEBUG)) {
                 return;
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 ac24e2e..25eea95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -155,7 +155,8 @@
     public abstract static class TaskStackListener {
         public void onTaskStackChanged() { }
         public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
-        public void onActivityPinned() { }
+        public void onActivityPinned(String packageName) { }
+        public void onActivityUnpinned() { }
         public void onPinnedActivityRestartAttempt() { }
         public void onPinnedStackAnimationStarted() { }
         public void onPinnedStackAnimationEnded() { }
@@ -196,17 +197,22 @@
         }
 
         @Override
-        public void onActivityPinned() throws RemoteException {
+        public void onActivityPinned(String packageName) throws RemoteException {
             mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED);
+            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget();
+        }
+
+        @Override
+        public void onActivityUnpinned() throws RemoteException {
+            mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+            mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
         }
 
         @Override
         public void onPinnedActivityRestartAttempt()
                 throws RemoteException{
             mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
-            mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT)
-                    .sendToTarget();
+            mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
         }
 
         @Override
@@ -631,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
@@ -643,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
@@ -663,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();
         }
@@ -672,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);
             }
@@ -1234,6 +1241,7 @@
         private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
         private static final int ON_TASK_PROFILE_LOCKED = 8;
         private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+        private static final int ON_ACTIVITY_UNPINNED = 10;
 
         @Override
         public void handleMessage(Message msg) {
@@ -1253,7 +1261,13 @@
                 }
                 case ON_ACTIVITY_PINNED: {
                     for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                        mTaskStackListeners.get(i).onActivityPinned();
+                        mTaskStackListeners.get(i).onActivityPinned((String) msg.obj);
+                    }
+                    break;
+                }
+                case ON_ACTIVITY_UNPINNED: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onActivityUnpinned();
                     }
                     break;
                 }
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 ededf96..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;
@@ -97,7 +99,6 @@
 
     TaskResourceLoadQueue mLoadQueue;
     TaskKeyLruCache<Drawable> mIconCache;
-    TaskKeyLruCache<ThumbnailData> mThumbnailCache;
     Bitmap mDefaultThumbnail;
     BitmapDrawable mDefaultIcon;
 
@@ -106,11 +107,10 @@
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
     public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
-            TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<ThumbnailData> thumbnailCache,
-            Bitmap defaultThumbnail, BitmapDrawable defaultIcon) {
+            TaskKeyLruCache<Drawable> iconCache, Bitmap defaultThumbnail,
+            BitmapDrawable defaultIcon) {
         mLoadQueue = loadQueue;
         mIconCache = iconCache;
-        mThumbnailCache = thumbnailCache;
         mDefaultThumbnail = defaultThumbnail;
         mDefaultIcon = defaultIcon;
         mMainThreadHandler = new Handler();
@@ -158,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
@@ -167,7 +166,6 @@
                     final Task t = mLoadQueue.nextTask();
                     if (t != null) {
                         Drawable cachedIcon = mIconCache.get(t.key);
-                        ThumbnailData cachedThumbnailData = mThumbnailCache.get(t.key);
 
                         // Load the icon if it is stale or we haven't cached one yet
                         if (cachedIcon == null) {
@@ -191,39 +189,21 @@
                             // default icon.
                             mIconCache.put(t.key, cachedIcon);
                         }
-                        // Load the thumbnail if it is stale or we haven't cached one yet
-                        if (cachedThumbnailData == null) {
-                            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
-                                if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-                                cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
-                            }
 
-                            if (cachedThumbnailData.thumbnail == null) {
-                                cachedThumbnailData.thumbnail = mDefaultThumbnail;
-                            } else {
-                                // Kick off an early upload of the bitmap to GL so
-                                // that this won't jank the first frame it's drawn in.
-                                cachedThumbnailData.thumbnail.prepareToDraw();
-                            }
+                        if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+                        ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+                                true /* reducedResolution */);
 
-                            // When svelte, we trim the memory to just the visible thumbnails when
-                            // leaving, so don't thrash the cache as the user scrolls (just load
-                            // them from scratch each time)
-                            if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
-                                    && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
-                                mThumbnailCache.put(t.key, cachedThumbnailData);
-                            }
+                        if (cachedThumbnailData.thumbnail == null) {
+                            cachedThumbnailData.thumbnail = mDefaultThumbnail;
                         }
+
                         if (!mCancelled) {
                             // Notify that the task data has changed
                             final Drawable newIcon = cachedIcon;
                             final ThumbnailData newThumbnailData = cachedThumbnailData;
-                            mMainThreadHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    t.notifyTaskDataLoaded(newThumbnailData, newIcon);
-                                }
-                            });
+                            mMainThreadHandler.post(
+                                    () -> t.notifyTaskDataLoaded(newThumbnailData, newIcon));
                         }
                     }
                 }
@@ -260,11 +240,11 @@
     // package in the cache has been updated, so that we may remove it.
     private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
     private final TaskKeyLruCache<Drawable> mIconCache;
-    private final TaskKeyLruCache<ThumbnailData> mThumbnailCache;
     private final TaskKeyLruCache<String> mActivityLabelCache;
     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;
@@ -311,13 +291,13 @@
         int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
         mLoadQueue = new TaskResourceLoadQueue();
         mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
-        mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
         mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
         mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
                 mClearActivityInfoOnEviction);
         mActivityInfoCache = new LruCache(numRecentTasks);
-        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
-                mDefaultThumbnail, mDefaultIcon);
+        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+        mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+                Looper.getMainLooper());
     }
 
     /** Returns the size of the app icon cache. */
@@ -330,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);
@@ -363,37 +347,25 @@
      */
     public void loadTaskData(Task t) {
         Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
-        Bitmap thumbnail = null;
-        ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
-        if (thumbnailData != null) {
-            thumbnail = thumbnailData.thumbnail;
-        }
-
-        // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
-        // use the default assets in their place until they load
-        boolean requiresLoad = (icon == null) || (thumbnail == null);
         icon = icon != null ? icon : mDefaultIcon;
-        if (requiresLoad) {
-            mLoadQueue.addTask(t);
-        }
-        t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnailData, icon);
+        mLoadQueue.addTask(t);
+        t.notifyTaskDataLoaded(null, icon);
     }
 
     /** 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. */
     public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
         mLoadQueue.removeTask(t);
-        mThumbnailCache.remove(t.key);
         mIconCache.remove(t.key);
         mActivityLabelCache.remove(t.key);
         mContentDescriptionCache.remove(t.key);
         if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(null, mDefaultIcon);
+            t.notifyTaskDataUnloaded(mDefaultIcon);
         }
     }
 
@@ -407,21 +379,12 @@
             case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                 // Stop the loader immediately when the UI is no longer visible
                 stopLoader();
-                if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
-                    mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
-                            mMaxThumbnailCacheSize / 2));
-                } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
-                    mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
-                } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
-                    mThumbnailCache.evictAll();
-                }
                 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
                         mMaxIconCacheSize / 2));
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
             case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
                 // We are leaving recents, so trim the data a bit
-                mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
                 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
                 mActivityInfoCache.trimToSize(Math.max(1,
                         ActivityManager.getMaxRecentTasksStatic() / 2));
@@ -429,7 +392,6 @@
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                 // We are going to be low on memory
-                mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
                 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
                 mActivityInfoCache.trimToSize(Math.max(1,
                         ActivityManager.getMaxRecentTasksStatic() / 4));
@@ -437,7 +399,6 @@
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                 // We are low on memory, so release everything
-                mThumbnailCache.evictAll();
                 mIconCache.evictAll();
                 mActivityInfoCache.evictAll();
                 // The cache is small, only clear the label cache when we are critical
@@ -539,28 +500,20 @@
     /**
      * 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();
 
-        // Return the cached thumbnail if it exists
-        ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (thumbnailData != null) {
-            return thumbnailData.thumbnail;
-        }
-
         if (loadIfNotCached) {
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                 // Load the thumbnail from the system
-                thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
                 if (thumbnailData.thumbnail != null) {
-                    if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
-                        mThumbnailCache.put(taskKey, thumbnailData);
-                    }
-                    return thumbnailData.thumbnail;
+                    return thumbnailData;
                 }
             }
         }
+
         // We couldn't load any thumbnail
         return null;
     }
@@ -637,7 +590,5 @@
         writer.print(prefix); writer.println(TAG);
         writer.print(prefix); writer.println("Icon Cache");
         mIconCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Thumbnail Cache");
-        mThumbnailCache.dump(innerPrefix, writer);
     }
 }
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/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 5db5498..a9043e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -35,7 +35,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -59,6 +58,8 @@
 import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
+import java.lang.IllegalArgumentException;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -68,9 +69,11 @@
     private static final String TAG = "InfoGuts";
 
     private INotificationManager mINotificationManager;
+    private String mPkg;
+    private int mAppUid;
+    private List<NotificationChannel> mNotificationChannels;
+    private NotificationChannel mSingleNotificationChannel;
     private int mStartingUserImportance;
-    private StatusBarNotification mStatusBarNotification;
-    private NotificationChannel mNotificationChannel;
 
     private TextView mNumChannelsView;
     private View mChannelDisabledView;
@@ -83,36 +86,42 @@
     }
 
     public interface OnSettingsClickListener {
-        void onClick(View v, int appUid);
+        void onClick(View v, NotificationChannel channel, int appUid);
     }
 
     public void bindNotification(final PackageManager pm,
             final INotificationManager iNotificationManager,
-            final StatusBarNotification sbn, final NotificationChannel channel,
+            final String pkg,
+            final List<NotificationChannel> notificationChannels,
             OnSettingsClickListener onSettingsClick,
-            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs)
+            throws RemoteException {
         mINotificationManager = iNotificationManager;
-        mNotificationChannel = channel;
-        mStatusBarNotification = sbn;
-        mStartingUserImportance = channel.getImportance();
+        mPkg = pkg;
+        mNotificationChannels = notificationChannels;
+        if (mNotificationChannels.isEmpty()) {
+            throw new IllegalArgumentException("bindNotification requires at least one channel");
+        } else if (mNotificationChannels.size() == 1) {
+            mSingleNotificationChannel = mNotificationChannels.get(0);
+            mStartingUserImportance = mSingleNotificationChannel.getImportance();
+        } else {
+            mSingleNotificationChannel = null;
+        }
 
-        final String pkg = sbn.getPackageName();
-        int appUid = -1;
-        String appName = pkg;
+        String appName = mPkg;
         Drawable pkgicon = null;
         CharSequence channelNameText = "";
         ApplicationInfo info = null;
         try {
-            info = pm.getApplicationInfo(pkg,
+            info = pm.getApplicationInfo(mPkg,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
-                appUid = info.uid;
+                mAppUid = info.uid;
                 appName = String.valueOf(pm.getApplicationLabel(info));
                 pkgicon = pm.getApplicationIcon(info);
-
             }
         } catch (PackageManager.NameNotFoundException e) {
             // app is gone, just show package name and generic icon
@@ -121,38 +130,54 @@
         ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
 
         int numChannels = 1;
-        try {
-            numChannels = iNotificationManager.getNumNotificationChannelsForPackage(
-                    pkg, appUid, false /* includeDeleted */);
-        } catch (RemoteException e) {
-            Log.e(TAG, e.toString());
-        }
+        numChannels = iNotificationManager.getNumNotificationChannelsForPackage(
+                pkg, mAppUid, false /* includeDeleted */);
 
+        String channelsDescText;
         mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc));
-        mNumChannelsView.setText(String.format(mContext.getResources().getQuantityString(
-                R.plurals.notification_num_channels_desc, numChannels), numChannels));
+        switch (mNotificationChannels.size()) {
+            case 1:
+                channelsDescText = String.format(mContext.getResources().getQuantityString(
+                        R.plurals.notification_num_channels_desc, numChannels), numChannels);
+                break;
+            case 2:
+                channelsDescText = mContext.getString(R.string.notification_channels_list_desc_2,
+                        mNotificationChannels.get(0).getName(),
+                        mNotificationChannels.get(1).getName());
+                break;
+            default:
+                final int numOthers = mNotificationChannels.size() - 2;
+                channelsDescText = String.format(
+                        mContext.getResources().getQuantityString(
+                                R.plurals.notification_channels_list_desc_2_and_others, numOthers),
+                        mNotificationChannels.get(0).getName(),
+                        mNotificationChannels.get(1).getName(),
+                        numOthers);
+        }
+        mNumChannelsView.setText(channelsDescText);
 
-        // If this is the placeholder channel, don't use our channel-specific text.
-        if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+        if (mSingleNotificationChannel == null) {
+            // Multiple channels don't use a channel name for the title.
+            channelNameText = mContext.getString(R.string.notification_num_channels,
+                    mNotificationChannels.size());
+        } else if (mSingleNotificationChannel.getId()
+                .equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            // If this is the placeholder channel, don't use our channel-specific text.
             channelNameText = mContext.getString(R.string.notification_header_default_channel);
         } else {
-            channelNameText = channel.getName();
+            channelNameText = mSingleNotificationChannel.getName();
         }
         ((TextView) findViewById(R.id.pkgname)).setText(appName);
         ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
 
         // Set group information if this channel has an associated group.
         CharSequence groupName = null;
-        if (channel.getGroup() != null) {
-            try {
-                final NotificationChannelGroup notificationChannelGroup =
-                        iNotificationManager.getNotificationChannelGroupForPackage(
-                                channel.getGroup(), pkg, appUid);
-                if (notificationChannelGroup != null) {
-                    groupName = notificationChannelGroup.getName();
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
+        if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
+            final NotificationChannelGroup notificationChannelGroup =
+                    iNotificationManager.getNotificationChannelGroupForPackage(
+                            mSingleNotificationChannel.getGroup(), pkg, mAppUid);
+            if (notificationChannelGroup != null) {
+                groupName = notificationChannelGroup.getName();
             }
         }
         TextView groupNameView = ((TextView) findViewById(R.id.group_name));
@@ -181,15 +206,15 @@
 
         // Top-level importance group
         mChannelDisabledView = findViewById(R.id.channel_disabled);
-        updateImportanceDisplay();
+        updateSecondaryText();
 
         // Settings button.
         final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
-        if (appUid >= 0 && onSettingsClick != null) {
-            final int appUidF = appUid;
+        if (mAppUid >= 0 && onSettingsClick != null) {
+            final int appUidF = mAppUid;
             settingsButton.setOnClickListener(
                     (View view) -> {
-                        onSettingsClick.onClick(view, appUidF);
+                        onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
                     });
             if (numChannels > 1) {
                 settingsButton.setText(R.string.notification_all_categories);
@@ -208,21 +233,24 @@
     }
 
     public boolean hasImportanceChanged() {
-        return mStartingUserImportance != getSelectedImportance();
+        return mSingleNotificationChannel != null &&
+                mStartingUserImportance != getSelectedImportance();
     }
 
     private void saveImportance() {
+        if (mSingleNotificationChannel == null) {
+            return;
+        }
         int selectedImportance = getSelectedImportance();
         if (selectedImportance == mStartingUserImportance) {
             return;
         }
         MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
                 selectedImportance - mStartingUserImportance);
-        mNotificationChannel.setImportance(selectedImportance);
+        mSingleNotificationChannel.setImportance(selectedImportance);
         try {
             mINotificationManager.updateNotificationChannelForPackage(
-                    mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
-                    mNotificationChannel);
+                    mPkg, mAppUid, mSingleNotificationChannel);
         } catch (RemoteException e) {
             // :(
         }
@@ -241,26 +269,32 @@
         mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
         mChannelEnabledSwitch.setChecked(
                 mStartingUserImportance != NotificationManager.IMPORTANCE_NONE);
-        mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+        final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
+        mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
 
         // Callback when checked.
         mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
             if (mGutsInteractionListener != null) {
                 mGutsInteractionListener.onInteraction(NotificationInfo.this);
             }
-            updateImportanceDisplay();
+            updateSecondaryText();
         });
     }
 
-    private void updateImportanceDisplay() {
-        final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
-        mChannelDisabledView.setVisibility(disabled ? View.VISIBLE : View.GONE);
-        if (disabled) {
-            // To be replaced by disabled text.
+    private void updateSecondaryText() {
+        final boolean defaultChannel = mSingleNotificationChannel != null &&
+                mSingleNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID);
+        final boolean disabled = mSingleNotificationChannel != null &&
+                getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+        if (defaultChannel) {
+            // Don't show any secondary text if this is from the default channel.
+            mChannelDisabledView.setVisibility(View.GONE);
             mNumChannelsView.setVisibility(View.GONE);
-        } else if (mNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            mNumChannelsView.setVisibility(View.INVISIBLE);
+        } else if (disabled) {
+            mChannelDisabledView.setVisibility(View.VISIBLE);
+            mNumChannelsView.setVisibility(View.GONE);
         } else {
+            mChannelDisabledView.setVisibility(View.GONE);
             mNumChannelsView.setVisibility(View.VISIBLE);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index b7b4a3b..8da17fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.widget.CachingIconView;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
@@ -126,7 +127,12 @@
         super.setDark(dark, fade, delay);
         if (mDark == dark) return;
         mDark = dark;
-        mShelfIcons.setDark(dark, fade, delay);
+        if (fade) {
+            mViewInvertHelper.fade(dark, delay);
+        } else {
+            mViewInvertHelper.update(dark);
+        }
+        mShelfIcons.setAmbient(dark);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index ffc4e8d..1101701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -47,7 +46,6 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 
 import java.text.NumberFormat;
@@ -101,6 +99,7 @@
     private int mDensity;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint();
+    private boolean mDotVisible;
     private float mDotRadius;
     private int mStaticDotRadius;
     private int mVisibleState = STATE_ICON;
@@ -111,8 +110,6 @@
     private OnVisibilityChangedListener mOnVisibilityChangedListener;
     private int mDrawableColor;
     private int mIconColor;
-    private int mDecorColor;
-    private float mDarkAmount;
     private ValueAnimator mColorAnimator;
     private int mCurrentSetColor = NO_COLOR;
     private int mAnimationStartColor = NO_COLOR;
@@ -122,7 +119,6 @@
                 animation.getAnimatedFraction());
         setColorInternal(newColor);
     };
-    private final NotificationIconDozeHelper mDozer;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -131,7 +127,6 @@
     public StatusBarIconView(Context context, String slot, Notification notification,
             boolean blocked) {
         super(context);
-        mDozer = new NotificationIconDozeHelper(context);
         mBlocked = blocked;
         mSlot = slot;
         mNumberPain = new Paint();
@@ -195,7 +190,6 @@
 
     public StatusBarIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDozer = new NotificationIconDozeHelper(context);
         mBlocked = false;
         mAlwaysScaleIcon = true;
         updateIconScale();
@@ -472,19 +466,7 @@
      * to the drawable.
      */
     public void setDecorColor(int iconTint) {
-        mDecorColor = iconTint;
-        updateDecorColor();
-    }
-
-    private void updateDecorColor() {
-        int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
-        if (mDotPaint.getColor() != color) {
-            mDotPaint.setColor(color);
-
-            if (mDotAppearAmount != 0) {
-                invalidate();
-            }
-        }
+        mDotPaint.setColor(iconTint);
     }
 
     /**
@@ -495,7 +477,6 @@
         mDrawableColor = color;
         setColorInternal(color);
         mIconColor = color;
-        mDozer.setColor(color);
     }
 
     private void setColorInternal(int color) {
@@ -668,14 +649,6 @@
         mOnVisibilityChangedListener = listener;
     }
 
-    public void setDark(boolean dark, boolean fade, long delay) {
-        mDozer.setImageDark(this, dark, fade, delay, mIconColor != NO_COLOR);
-        mDozer.setIntensityDark(f -> {
-            mDarkAmount = f;
-            updateDecorColor();
-        }, dark, fade, delay);
-    }
-
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..3efa29f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -18,7 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
+import android.animation.ValueAnimator;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.view.View;
@@ -38,8 +38,8 @@
     private boolean mIsLegacy;
     private int mLegacyColor;
 
-    protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
-        super(ctx, view, row);
+    protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
+        super(view, row);
         mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
         mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color);
     }
@@ -67,11 +67,13 @@
     }
 
     protected void fadeGrayscale(final boolean dark, long delay) {
-        getDozer().startIntensityAnimation(animation -> {
-            getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue());
-            mGreyPaint.setColorFilter(
-                    new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
-            mView.setLayerPaint(mGreyPaint);
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateGrayscaleMatrix((float) animation.getAnimatedValue());
+                mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+                mView.setLayerPaint(mGreyPaint);
+            }
         }, dark, delay, new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -84,9 +86,9 @@
 
     protected void updateGrayscale(boolean dark) {
         if (dark) {
-            getDozer().updateGrayscaleMatrix(1f);
+            updateGrayscaleMatrix(1f);
             mGreyPaint.setColorFilter(
-                    new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
+                    new ColorMatrixColorFilter(mGrayscaleColorMatrix));
             mView.setLayerPaint(mGreyPaint);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
deleted file mode 100644
index d592c5f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ /dev/null
@@ -1,92 +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.systemui.statusbar.notification;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.widget.ImageView;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-import java.util.function.Consumer;
-
-public class NotificationDozeHelper {
-    private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
-
-    public void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateGrayscaleMatrix((float) animation.getAnimatedValue());
-                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-            }
-        }, dark, delay, new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!dark) {
-                    target.setColorFilter(null);
-                }
-            }
-        });
-    }
-
-    public void updateGrayscale(ImageView target, boolean dark) {
-        if (dark) {
-            updateGrayscaleMatrix(1f);
-            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-        } else {
-            target.setColorFilter(null);
-        }
-    }
-
-    public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
-            boolean dark, long delay, Animator.AnimatorListener listener) {
-        float startIntensity = dark ? 0f : 1f;
-        float endIntensity = dark ? 1f : 0f;
-        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
-        animator.addUpdateListener(updateListener);
-        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
-        animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        animator.setStartDelay(delay);
-        if (listener != null) {
-            animator.addListener(listener);
-        }
-        animator.start();
-    }
-
-    public void setIntensityDark(Consumer<Float> listener, boolean dark,
-            boolean animate, long delay) {
-        if (animate) {
-            startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay,
-                    null /* listener */);
-        } else {
-            listener.accept(dark ? 1f : 0f);
-        }
-    }
-
-    public void updateGrayscaleMatrix(float intensity) {
-        mGrayscaleColorMatrix.setSaturation(1 - intensity);
-    }
-
-    public ColorMatrix getGrayscaleColorMatrix() {
-        return mGrayscaleColorMatrix;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 1ffc944..38e4ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,10 +16,17 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -30,6 +37,7 @@
 import android.widget.TextView;
 
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
@@ -47,6 +55,10 @@
 
     private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
             = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
+    private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
+            0, PorterDuff.Mode.SRC_ATOP);
+    private final int mIconDarkAlpha;
+    private final int mIconDarkColor = 0xffffffff;
 
     protected final ViewInvertHelper mInvertHelper;
     protected final ViewTransformationHelper mTransformationHelper;
@@ -62,7 +74,8 @@
     private boolean mTransformLowPriorityTitle;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
-        super(ctx, view, row);
+        super(view, row);
+        mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
         mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
         mTransformationHelper = new ViewTransformationHelper();
 
@@ -95,16 +108,6 @@
         updateInvertHelper();
     }
 
-    @Override
-    protected NotificationDozeHelper createDozer(Context ctx) {
-        return new NotificationIconDozeHelper(ctx);
-    }
-
-    @Override
-    protected NotificationIconDozeHelper getDozer() {
-        return (NotificationIconDozeHelper) super.getDozer();
-    }
-
     protected void resolveHeaderViews() {
         mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
@@ -113,7 +116,6 @@
         mColor = resolveColor(mExpandButton);
         mNotificationHeader = (NotificationHeaderView) mView.findViewById(
                 com.android.internal.R.id.notification_header);
-        getDozer().setColor(mColor);
     }
 
     private int resolveColor(ImageView icon) {
@@ -221,8 +223,90 @@
             // It also may lead to bugs where the icon isn't correctly greyed out.
             boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
                     != NotificationHeaderView.NO_COLOR;
+            if (fade) {
+                if (hadColorFilter) {
+                    fadeIconColorFilter(mIcon, dark, delay);
+                    fadeIconAlpha(mIcon, dark, delay);
+                } else {
+                    fadeGrayscale(mIcon, dark, delay);
+                }
+            } else {
+                if (hadColorFilter) {
+                    updateIconColorFilter(mIcon, dark);
+                    updateIconAlpha(mIcon, dark);
+                } else {
+                    updateGrayscale(mIcon, dark);
+                }
+            }
+        }
+    }
 
-            getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
+    private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateIconColorFilter(target, (Float) animation.getAnimatedValue());
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateGrayscaleMatrix((float) animation.getAnimatedValue());
+                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+            }
+        }, dark, delay, new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!dark) {
+                    target.setColorFilter(null);
+                }
+            }
+        });
+    }
+
+    private void updateIconColorFilter(ImageView target, boolean dark) {
+        updateIconColorFilter(target, dark ? 1f : 0f);
+    }
+
+    private void updateIconColorFilter(ImageView target, float intensity) {
+        int color = interpolateColor(mColor, mIconDarkColor, intensity);
+        mIconColorFilter.setColor(color);
+        Drawable iconDrawable = target.getDrawable();
+
+        // Also, the notification might have been modified during the animation, so background
+        // might be null here.
+        if (iconDrawable != null) {
+            Drawable d = iconDrawable.mutate();
+            // DrawableContainer ignores the color filter if it's already set, so clear it first to
+            // get it set and invalidated properly.
+            d.setColorFilter(null);
+            d.setColorFilter(mIconColorFilter);
+        }
+    }
+
+    private void updateIconAlpha(ImageView target, boolean dark) {
+        target.setImageAlpha(dark ? mIconDarkAlpha : 255);
+    }
+
+    protected void updateGrayscale(ImageView target, boolean dark) {
+        if (dark) {
+            updateGrayscaleMatrix(1f);
+            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+        } else {
+            target.setColorFilter(null);
         }
     }
 
@@ -232,6 +316,22 @@
         mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
     }
 
+    private static int interpolateColor(int source, int target, float t) {
+        int aSource = Color.alpha(source);
+        int rSource = Color.red(source);
+        int gSource = Color.green(source);
+        int bSource = Color.blue(source);
+        int aTarget = Color.alpha(target);
+        int rTarget = Color.red(target);
+        int gTarget = Color.green(target);
+        int bTarget = Color.blue(target);
+        return Color.argb(
+                (int) (aSource * (1f - t) + aTarget * t),
+                (int) (rSource * (1f - t) + rTarget * t),
+                (int) (gSource * (1f - t) + gTarget * t),
+                (int) (bSource * (1f - t) + bTarget * t));
+    }
+
     @Override
     public NotificationHeaderView getNotificationHeader() {
         return mNotificationHeader;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
deleted file mode 100644
index 9f79ef2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
+++ /dev/null
@@ -1,101 +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.systemui.statusbar.notification;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class NotificationIconDozeHelper extends NotificationDozeHelper {
-
-    private final int mImageDarkAlpha;
-    private final int mImageDarkColor = 0xffffffff;
-    private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
-            0, PorterDuff.Mode.SRC_ATOP);
-
-    private int mColor = Color.BLACK;
-
-    public NotificationIconDozeHelper(Context ctx) {
-        mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
-    }
-
-    public void setColor(int color) {
-        mColor = color;
-    }
-
-    public void setImageDark(ImageView target, boolean dark, boolean fade, long delay,
-            boolean useGrayscale) {
-        if (fade) {
-            if (!useGrayscale) {
-                fadeImageColorFilter(target, dark, delay);
-                fadeImageAlpha(target, dark, delay);
-            } else {
-                fadeGrayscale(target, dark, delay);
-            }
-        } else {
-            if (!useGrayscale) {
-                updateImageColorFilter(target, dark);
-                updateImageAlpha(target, dark);
-            } else {
-                updateGrayscale(target, dark);
-            }
-        }
-    }
-
-    private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(animation -> {
-            updateImageColorFilter(target, (Float) animation.getAnimatedValue());
-        }, dark, delay, null /* listener */);
-    }
-
-    private void fadeImageAlpha(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(animation -> {
-            float t = (float) animation.getAnimatedValue();
-            target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t));
-        }, dark, delay, null /* listener */);
-    }
-
-    private void updateImageColorFilter(ImageView target, boolean dark) {
-        updateImageColorFilter(target, dark ? 1f : 0f);
-    }
-
-    private void updateImageColorFilter(ImageView target, float intensity) {
-        int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
-        mImageColorFilter.setColor(color);
-        Drawable imageDrawable = target.getDrawable();
-
-        // Also, the notification might have been modified during the animation, so background
-        // might be null here.
-        if (imageDrawable != null) {
-            Drawable d = imageDrawable.mutate();
-            // DrawableContainer ignores the color filter if it's already set, so clear it first to
-            // get it set and invalidated properly.
-            d.setColorFilter(null);
-            d.setColorFilter(mImageColorFilter);
-        }
-    }
-
-    private void updateImageAlpha(ImageView target, boolean dark) {
-        target.setImageAlpha(dark ? mImageDarkAlpha : 255);
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index f0b6b2e..846d03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Color;
 import android.service.notification.StatusBarNotification;
@@ -45,8 +46,7 @@
     private int mContentHeight;
     private int mMinHeightHint;
 
-    protected NotificationTemplateViewWrapper(Context ctx, View view,
-            ExpandableNotificationRow row) {
+    protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
         mTransformationHelper.setCustomTransformation(
                 new ViewTransformationHelper.CustomTransformation() {
@@ -154,20 +154,16 @@
         // This also clears the existing types
         super.updateTransformedTypes();
         if (mTitle != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
-                    mTitle);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
         }
         if (mText != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
-                    mText);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
         }
         if (mPicture != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
-                    mPicture);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
         }
         if (mProgressBar != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
-                    mProgressBar);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
         }
     }
 
@@ -177,7 +173,7 @@
             return;
         }
         super.setDark(dark, fade, delay);
-        setPictureDark(dark, fade, delay);
+        setPictureGrayscale(dark, fade, delay);
         setProgressBarDark(dark, fade, delay);
     }
 
@@ -192,9 +188,12 @@
     }
 
     private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
-        getDozer().startIntensityAnimation(animation -> {
-            float t = (float) animation.getAnimatedValue();
-            updateProgressDark(target, t);
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                updateProgressDark(target, t);
+            }
         }, dark, delay, null /* listener */);
     }
 
@@ -208,9 +207,13 @@
         updateProgressDark(target, dark ? 1f : 0f);
     }
 
-    private void setPictureDark(boolean dark, boolean fade, long delay) {
+    protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
         if (mPicture != null) {
-            getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
+            if (fade) {
+                fadeGrayscale(mPicture, grayscale, delay);
+            } else {
+                updateGrayscale(mPicture, grayscale);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index c86616b..c85e8d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -16,17 +16,24 @@
 
 package com.android.systemui.statusbar.notification;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.ColorMatrix;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
 import android.support.v4.graphics.ColorUtils;
 import android.view.NotificationHeaderView;
 import android.view.View;
 
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 /**
  * Wraps the actual notification content view; used to implement behaviors which are different for
@@ -34,14 +41,14 @@
  */
 public abstract class NotificationViewWrapper implements TransformableView {
 
+    protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
     protected final View mView;
     protected final ExpandableNotificationRow mRow;
-    private final NotificationDozeHelper mDozer;
-
     protected boolean mDark;
     private int mBackgroundColor = 0;
     protected boolean mShouldInvertDark;
     protected boolean mDarkInitialized = false;
+    private boolean mForcedInvisible;
 
     public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -58,22 +65,13 @@
         } else if (v instanceof NotificationHeaderView) {
             return new NotificationHeaderViewWrapper(ctx, v, row);
         } else {
-            return new NotificationCustomViewWrapper(ctx, v, row);
+            return new NotificationCustomViewWrapper(v, row);
         }
     }
 
-    protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+    protected NotificationViewWrapper(View view, ExpandableNotificationRow row) {
         mView = view;
         mRow = row;
-        mDozer = createDozer(ctx);
-    }
-
-    protected NotificationDozeHelper createDozer(Context ctx) {
-        return new NotificationIconDozeHelper(mView.getContext());
-    }
-
-    protected NotificationDozeHelper getDozer() {
-        return mDozer;
     }
 
     /**
@@ -114,6 +112,26 @@
                 || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
     }
 
+
+    protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+            boolean dark, long delay, Animator.AnimatorListener listener) {
+        float startIntensity = dark ? 0f : 1f;
+        float endIntensity = dark ? 1f : 0f;
+        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+        animator.addUpdateListener(updateListener);
+        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+        animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        animator.setStartDelay(delay);
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        animator.start();
+    }
+
+    protected void updateGrayscaleMatrix(float intensity) {
+        mGrayscaleColorMatrix.setSaturation(1 - intensity);
+    }
+
     /**
      * Update the appearance of the expand button.
      *
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/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index dee15d8..3706dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -95,7 +95,7 @@
     private int mActualLayoutWidth = NO_VALUE;
     private float mActualPaddingEnd = NO_VALUE;
     private float mActualPaddingStart = NO_VALUE;
-    private boolean mDark;
+    private boolean mCentered;
     private boolean mChangingViewPositions;
     private int mAddAnimationStartIndex = -1;
     private int mCannedAnimationStartIndex = -1;
@@ -183,9 +183,6 @@
                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
-        if (mDark && child instanceof StatusBarIconView) {
-            ((StatusBarIconView) child).setDark(mDark, false, 0);
-        }
     }
 
     @Override
@@ -315,8 +312,7 @@
                 numDots++;
             }
         }
-        boolean center = mDark;
-        if (center && translationX < getLayoutEnd()) {
+        if (mCentered && translationX < getLayoutEnd()) {
             float delta = (getLayoutEnd() - translationX) / 2;
             for (int i = 0; i < childCount; i++) {
                 View view = getChildAt(i);
@@ -394,15 +390,9 @@
         mChangingViewPositions = changingViewPositions;
     }
 
-    public void setDark(boolean dark, boolean fade, long delay) {
-        mDark = dark;
+    public void setAmbient(boolean ambient) {
+        mCentered = ambient;
         mDisallowNextAnimation = true;
-        for (int i = 0; i < getChildCount(); i++) {
-            View view = getChildAt(i);
-            if (view instanceof StatusBarIconView) {
-                ((StatusBarIconView) view).setDark(dark, fade, delay);
-            }
-        }
     }
 
     public IconState getIconState(StatusBarIconView icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2c5bd3c..101aee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -18,7 +18,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
@@ -196,8 +195,6 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
-
-
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.volume.VolumeComponent;
 
@@ -5719,11 +5716,13 @@
 
     // The (i) button in the guts that links to the system notification settings for that app
     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
-            final String channelId) {
+            final NotificationChannel channel) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channelId);
+        if (channel != null) {
+            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+        }
         startNotificationGutsIntent(intent, appUid);
     }
 
@@ -5776,23 +5775,23 @@
         }
 
         if (item.gutsContent instanceof NotificationInfo) {
-            final NotificationChannel channel = row.getEntry().channel;
+            final UserHandle userHandle = sbn.getUser();
             PackageManager pmUser = getPackageManagerForUser(mContext,
-                    sbn.getUser().getIdentifier());
+                    userHandle.getIdentifier());
             final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
             final String pkg = sbn.getPackageName();
             NotificationInfo info = (NotificationInfo) item.gutsContent;
             final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
-                    int appUid) -> {
+                    NotificationChannel channel, int appUid) -> {
                 mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
                 guts.resetFalsingCheck();
-                startAppNotificationSettingsActivity(pkg, appUid, channel.getId());
+                startAppNotificationSettingsActivity(pkg, appUid, channel);
             };
             final View.OnClickListener onDoneClick = (View v) -> {
                 // If the user has security enabled, show challenge if the setting is changed.
                 if (info.hasImportanceChanged()
-                        && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+                        && isLockscreenPublicMode(userHandle.getIdentifier())
                         && (mState == StatusBarState.KEYGUARD
                                 || mState == StatusBarState.SHADE_LOCKED)) {
                     OnDismissAction dismissAction = new OnDismissAction() {
@@ -5807,9 +5806,30 @@
                     saveAndCloseNotificationMenu(info, row, guts, v);
                 }
             };
-            info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
-                    onDoneClick,
-                    mNonBlockablePkgs);
+
+            ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+            channels.add(row.getEntry().channel);
+            if (row.isSummaryWithChildren()) {
+                // If this is a summary, then add in the children notification channels for the
+                // same user and pkg.
+                final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+                final int numChildren = childrenRows.size();
+                for (int i = 0; i < numChildren; i++) {
+                    final ExpandableNotificationRow childRow = childrenRows.get(i);
+                    final NotificationChannel childChannel = childRow.getEntry().channel;
+                    final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+                    if (childSbn.getUser().equals(userHandle) &&
+                            childSbn.getPackageName().equals(pkg)) {
+                        channels.add(childChannel);
+                    }
+                }
+            }
+            try {
+                info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+                        onSettingsClick, onDoneClick, mNonBlockablePkgs);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         }
     }
 
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/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 726300f..8aca546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -56,8 +56,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
@@ -72,14 +73,12 @@
     private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
     private final PackageManager mMockPackageManager = mock(PackageManager.class);
     private NotificationChannel mNotificationChannel;
-    private final StatusBarNotification mMockStatusBarNotification =
-            mock(StatusBarNotification.class);
+    private NotificationChannel mDefaultNotificationChannel;
 
     @Before
     public void setUp() throws Exception {
         // Inflate the layout
-        final LayoutInflater layoutInflater =
-                LayoutInflater.from(mContext);
+        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
                 null);
 
@@ -92,30 +91,51 @@
         when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
                 applicationInfo);
 
-        // mMockStatusBarNotification with a test channel.
-        mNotificationChannel = new NotificationChannel(
-                TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
-        when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        // Package has one channel by default.
         when(mMockINotificationManager.getNumNotificationChannelsForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1);
+
+        // Some test channels.
+        mNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+        mDefaultNotificationChannel = new NotificationChannel(
+                NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
+                NotificationManager.IMPORTANCE_LOW);
     }
 
     private CharSequence getStringById(int resId) {
         return mContext.getString(resId);
     }
 
-    private CharSequence getNumChannelsString(int numChannels) {
+    private CharSequence getNumChannelsDescString(int numChannels) {
         return String.format(
                 mContext.getResources().getQuantityString(
                         R.plurals.notification_num_channels_desc, numChannels),
                 numChannels);
     }
 
+    private CharSequence getChannelsListDescString(NotificationChannel... channels) {
+        if (channels.length == 2) {
+            return mContext.getString(R.string.notification_channels_list_desc_2,
+                    channels[0].getName(), channels[1].getName());
+        } else {
+            final int numOthers = channels.length - 2;
+            return String.format(
+                    mContext.getResources().getQuantityString(
+                            R.plurals.notification_channels_list_desc_2_and_others, numOthers),
+                    channels[0].getName(), channels[1].getName(), numOthers);
+        }
+    }
+
+    private CharSequence getNumChannelsString(int numChannels) {
+        return mContext.getString(R.string.notification_num_channels, numChannels);
+    }
+
     @Test
     public void testBindNotification_SetsTextApplicationName() throws Exception {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
     }
@@ -126,7 +146,7 @@
         when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
                 .thenReturn(iconDrawable);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final ImageView iconView = (ImageView) mNotificationInfo.findViewById(R.id.pkgicon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
@@ -134,7 +154,7 @@
     @Test
     public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.GONE, groupNameView.getVisibility());
         final TextView groupDividerView =
@@ -151,7 +171,7 @@
                 eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
                 .thenReturn(notificationChannelGroup);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.VISIBLE, groupNameView.getVisibility());
         assertEquals("Test Group Name", groupNameView.getText());
@@ -163,7 +183,7 @@
     @Test
     public void testBindNotification_SetsTextChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
@@ -172,8 +192,29 @@
     public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel,
-                (View v, int appUid) -> { latch.countDown(); }, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+                (View v, NotificationChannel c, int appUid) -> {
+                  assertEquals(mNotificationChannel, c);
+                  latch.countDown();
+                }, null, null);
+
+        final TextView settingsButton =
+                (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+        settingsButton.performClick();
+        // Verify that listener was triggered.
+        assertEquals(0, latch.getCount());
+    }
+
+    @Test
+    public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME,
+                Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+                (View v, NotificationChannel c, int appUid) -> {
+                  assertEquals(null, c);
+                  latch.countDown();
+                }, null, null);
 
         final TextView settingsButton =
                 (TextView) mNotificationInfo.findViewById(R.id.more_settings);
@@ -185,8 +226,8 @@
     @Test
     public void testBindNotification_SettingsTextWithOneChannel() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null,
-                null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+                (View v, NotificationChannel c, int appUid) -> {}, null, null);
         final TextView settingsButton =
                 (TextView) mNotificationInfo.findViewById(R.id.more_settings);
         assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText());
@@ -197,8 +238,8 @@
         when(mMockINotificationManager.getNumNotificationChannelsForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null,
-                null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+                (View v, NotificationChannel c, int appUid) -> {}, null, null);
         final TextView settingsButton =
                 (TextView) mNotificationInfo.findViewById(R.id.more_settings);
         assertEquals(getStringById(R.string.notification_all_categories), settingsButton.getText());
@@ -208,7 +249,7 @@
     public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null,
                 (View v) -> { latch.countDown(); },
                 null);
 
@@ -220,11 +261,8 @@
 
     @Test
     public void testBindNotification_NumChannelsTextHiddenWhenDefaultChannel() throws Exception {
-        final NotificationChannel defaultChannel = new NotificationChannel(
-                NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
-                NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, defaultChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
         final TextView numChannelsView =
                 (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
         assertTrue(numChannelsView.getVisibility() != View.VISIBLE);
@@ -234,11 +272,11 @@
     public void testBindNotification_NumChannelsTextDisplaysWhenNotDefaultChannel()
             throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView numChannelsView =
                 (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
         assertEquals(numChannelsView.getVisibility(), View.VISIBLE);
-        assertEquals(getNumChannelsString(1), numChannelsView.getText());
+        assertEquals(getNumChannelsDescString(1), numChannelsView.getText());
     }
 
     @Test
@@ -247,16 +285,90 @@
         when(mMockINotificationManager.getNumNotificationChannelsForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView numChannelsView =
                 (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
-        assertEquals(getNumChannelsString(2), numChannelsView.getText());
+        assertEquals(getNumChannelsDescString(2), numChannelsView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBindNotification_NumChannelsTextListsChannelsWhenTwoInBundle()
+            throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+                null, null, null);
+        final TextView numChannelsView =
+                (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+        assertEquals(getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel),
+                numChannelsView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBindNotification_NumChannelsTextListsChannelsWhenThreeInBundle()
+            throws Exception {
+        NotificationChannel thirdChannel = new NotificationChannel(
+                "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME,
+                Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel),
+                null, null, null);
+        final TextView numChannelsView =
+                (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+        assertEquals(
+                getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel,
+                        thirdChannel),
+                numChannelsView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBindNotification_NumChannelsTextListsChannelsWhenFourInBundle()
+            throws Exception {
+        NotificationChannel thirdChannel = new NotificationChannel(
+                "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW);
+        NotificationChannel fourthChannel = new NotificationChannel(
+                "fourth_channel", "fourth_channel", NotificationManager.IMPORTANCE_LOW);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME,
+                Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel,
+                        fourthChannel),
+                null, null, null);
+        final TextView numChannelsView =
+                (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+        assertEquals(
+                getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel,
+                        thirdChannel, fourthChannel),
+                numChannelsView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testBindNotification_ChannelNameChangesWhenBundleFromDifferentChannels()
+            throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+                null, null, null);
+        final TextView channelNameView =
+                (TextView) mNotificationInfo.findViewById(R.id.channel_name);
+        assertEquals(getNumChannelsString(2), channelNameView.getText());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testEnabledSwitchInvisibleIfBundleFromDifferentChannels() throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+                null, null, null);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+        assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
     }
 
     @Test
     public void testbindNotification_ChannelDisabledTextGoneWhenNotDisabled() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView channelDisabledView =
                 (TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
         assertEquals(channelDisabledView.getVisibility(), View.GONE);
@@ -266,7 +378,7 @@
     public void testbindNotification_ChannelDisabledTextVisibleWhenDisabled() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         final TextView channelDisabledView =
                 (TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
         assertEquals(channelDisabledView.getVisibility(), View.VISIBLE);
@@ -277,9 +389,21 @@
     }
 
     @Test
+    @UiThreadTest
+    public void testBindNotification_ChannelDisabledTextHiddenWhenDefaultChannel()
+            throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
+        final TextView channelDisabledView =
+                (TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
+        assertTrue(channelDisabledView.getVisibility() != View.VISIBLE);
+    }
+
+    @Test
+    @UiThreadTest
     public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         assertFalse(mNotificationInfo.hasImportanceChanged());
     }
 
@@ -287,7 +411,7 @@
     public void testHasImportanceChanged_ReturnsTrueAfterChannelDisabled() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         // Find the high button and check it.
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         enabledSwitch.setChecked(false);
@@ -297,7 +421,7 @@
     @Test
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
     }
@@ -306,7 +430,7 @@
     public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         enabledSwitch.setChecked(false);
@@ -318,7 +442,7 @@
     public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged()
             throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         mNotificationInfo.handleCloseControls(true);
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -330,7 +454,7 @@
             throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         mNotificationInfo.handleCloseControls(true);
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -341,7 +465,7 @@
     public void testEnabledSwitchOnByDefault() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertTrue(enabledSwitch.isChecked());
@@ -351,7 +475,7 @@
     public void testEnabledButtonOffWhenAlreadyBanned() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertFalse(enabledSwitch.isChecked());
@@ -361,7 +485,7 @@
     public void testEnabledSwitchVisibleByDefault() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
@@ -371,7 +495,7 @@
     public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
                 Collections.singleton(TEST_PACKAGE_NAME));
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -379,10 +503,21 @@
     }
 
     @Test
+    public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception {
+        mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
+                Collections.singleton(TEST_PACKAGE_NAME));
+        mNotificationInfo.handleCloseControls(true);
+        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+                anyString(), anyInt(), any());
+    }
+
+    @Test
     public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
                 Collections.singleton(TEST_PACKAGE_NAME));
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -396,7 +531,7 @@
     public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, mNotificationChannel, null, null,
+                TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
                 Collections.singleton(TEST_PACKAGE_NAME));
 
         Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
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/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index f06c8dcf..fb714b9 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3345,11 +3345,11 @@
     // CATEGORY: SETTINGS
     SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
 
-    // ACTION: Allow "Enable picture-in-picture on hide" for an app
-    APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+    // ACTION: Allow "Enable picture-in-picture" for an app
+    APP_PICTURE_IN_PICTURE_ALLOW = 813;
 
-    // ACTION: Deny "Enable picture-in-picture on hide" for an app
-    APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+    // ACTION: Deny "Enable picture-in-picture" for an app
+    APP_PICTURE_IN_PICTURE_DENY = 814;
 
     // OPEN: Settings > Language & input > Text-to-speech output -> Speech rate & pitch
     // CATEGORY: SETTINGS
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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8f2b428..30d06db 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1295,7 +1295,7 @@
         if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
         mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener);
+                mTransportBoundListener, mHandlerThread.getLooper());
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 619ddb1..67f105e 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -27,9 +27,13 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -55,10 +59,15 @@
 
     private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
+    private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
+    private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
+    private static final int REBINDING_TIMEOUT_MSG = 1;
+
     private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final Set<ComponentName> mTransportWhitelist;
+    private final Handler mHandler;
 
     /**
      * This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -83,12 +92,13 @@
     private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
 
     TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
-            TransportBoundListener listener) {
+            TransportBoundListener listener, Looper looper) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>();
         mCurrentTransportName = defaultTransport;
         mTransportBoundListener = listener;
+        mHandler = new RebindOnTimeoutHandler(looper);
     }
 
     void onPackageAdded(String packageName) {
@@ -242,12 +252,12 @@
                 intent, 0, UserHandle.USER_SYSTEM);
         if (hosts != null) {
             for (ResolveInfo host : hosts) {
-                final ServiceInfo info = host.serviceInfo;
+                final ComponentName infoComponentName = host.serviceInfo.getComponentName();
                 boolean shouldBind = false;
                 if (components != null && packageName != null) {
                     for (String component : components) {
                         ComponentName cn = new ComponentName(pkgInfo.packageName, component);
-                        if (info.getComponentName().equals(cn)) {
+                        if (infoComponentName.equals(cn)) {
                             shouldBind = true;
                             break;
                         }
@@ -255,8 +265,8 @@
                 } else {
                     shouldBind = true;
                 }
-                if (shouldBind && isTransportTrusted(info.getComponentName())) {
-                    tryBindTransport(info);
+                if (shouldBind && isTransportTrusted(infoComponentName)) {
+                    tryBindTransport(infoComponentName);
                 }
             }
         }
@@ -283,8 +293,7 @@
         return true;
     }
 
-    private void tryBindTransport(ServiceInfo transport) {
-        final ComponentName transportComponentName = transport.getComponentName();
+    private void tryBindTransport(ComponentName transportComponentName) {
         Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
         // TODO: b/22388012 (Multi user backup and restore)
         TransportConnection connection = new TransportConnection(transportComponentName);
@@ -335,17 +344,22 @@
                     success = false;
                     Slog.e(TAG, "Couldn't get transport name.", e);
                 } finally {
+                    // we need to intern() the String of the component, so that we can use it with
+                    // Handler's removeMessages(), which uses == operator to compare the tokens
+                    String componentShortString = component.flattenToShortString().intern();
                     if (success) {
-                        Slog.d(TAG, "Bound to transport: " + component.flattenToShortString());
+                        Slog.d(TAG, "Bound to transport: " + componentShortString);
                         mBoundTransports.put(mTransportName, component);
                         for (SelectBackupTransportCallback listener : mListeners) {
                             listener.onSuccess(mTransportName);
                         }
+                        // cancel rebinding on timeout for this component as we've already connected
+                        mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
                     } else {
-                        Slog.w(TAG, "Bound to transport " + component.flattenToShortString() +
+                        Slog.w(TAG, "Bound to transport " + componentShortString +
                                 " but it is invalid");
                         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                                component.flattenToShortString(), 0);
+                                componentShortString, 0);
                         mContext.unbindService(this);
                         mValidTransports.remove(component);
                         mBinder = null;
@@ -364,9 +378,27 @@
                 mBinder = null;
                 mBoundTransports.remove(mTransportName);
             }
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                    component.flattenToShortString(), 0);
-            Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString());
+            String componentShortString = component.flattenToShortString();
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
+            Slog.w(TAG, "Disconnected from transport " + componentShortString);
+            scheduleRebindTimeout(component);
+        }
+
+        /**
+         * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
+         * for a few minutes after the binding went away.
+         */
+        private void scheduleRebindTimeout(ComponentName component) {
+            // we need to intern() the String of the component, so that we can use it with Handler's
+            // removeMessages(), which uses == operator to compare the tokens
+            final String componentShortString = component.flattenToShortString().intern();
+            final long rebindTimeout = getRebindTimeout();
+            mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
+            Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
+            msg.obj = componentShortString;
+            mHandler.sendMessageDelayed(msg, rebindTimeout);
+            Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
+                    + rebindTimeout + "ms");
         }
 
         private IBackupTransport getBinder() {
@@ -403,6 +435,14 @@
                 }
             }
         }
+
+        private long getRebindTimeout() {
+            final boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+            return isDeviceProvisioned
+                    ? REBINDING_TIMEOUT_PROVISIONED_MS
+                    : REBINDING_TIMEOUT_UNPROVISIONED_MS;
+        }
     }
 
     interface TransportBoundListener {
@@ -410,6 +450,43 @@
         boolean onTransportBound(IBackupTransport binder);
     }
 
+    private class RebindOnTimeoutHandler extends Handler {
+
+        RebindOnTimeoutHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == REBINDING_TIMEOUT_MSG) {
+                String componentShortString = (String) msg.obj;
+                ComponentName transportComponent =
+                        ComponentName.unflattenFromString(componentShortString);
+                synchronized (mTransportLock) {
+                    if (mBoundTransports.containsValue(transportComponent)) {
+                        Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
+                            + componentShortString + " so not attempting to rebind");
+                        return;
+                    }
+                    Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
+                            + componentShortString);
+                    // unbind the existing (broken) connection
+                    TransportConnection conn = mValidTransports.get(transportComponent);
+                    if (conn != null) {
+                        mContext.unbindService(conn);
+                        Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
+                                + componentShortString);
+                    }
+                }
+                // rebind to transport
+                tryBindTransport(transportComponent);
+            } else {
+                Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
+                        + msg.what);
+            }
+        }
+    }
+
     private static void log_verbose(String message) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Slog.v(TAG, message);
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/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 179b3d0..c6af290 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -954,10 +954,12 @@
         mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
                 new Intent(Intent.ACTION_TIME_TICK).addFlags(
                         Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND), 0,
+                        | Intent.FLAG_RECEIVER_FOREGROUND
+                        | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0,
                         UserHandle.ALL);
         Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
         
@@ -1034,7 +1036,8 @@
 
         if (timeZoneWasChanged) {
             Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             intent.putExtra("time-zone", zone.getID());
             getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
         }
@@ -2518,7 +2521,8 @@
                         Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
                         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                                 | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+                                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
                         getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
 
                         // The world has changed on us, so we need to re-evaluate alarms
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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index a4f9f21..d02b726 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3085,7 +3085,17 @@
         boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
-        return tetherEnabledInSettings && mUserManager.isAdminUser() &&
+
+        // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
+        boolean adminUser = false;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            adminUser = mUserManager.isAdminUser();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return tetherEnabledInSettings && adminUser &&
                mTethering.hasTetherableConfiguration();
     }
 
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 44ca6a9..3e2dae5 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1136,6 +1136,9 @@
 
     private final class BinderService extends IDeviceIdleController.Stub {
         @Override public void addPowerSaveWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
+            }
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
             long ident = Binder.clearCallingIdentity();
@@ -1147,6 +1150,9 @@
         }
 
         @Override public void removePowerSaveWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.i(TAG, "removePowerSaveWhitelistApp(name = " + name + ")");
+            }
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
             long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 14a9a31..19bedfb 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -32,6 +32,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.IGraphicsStats;
 import android.view.IGraphicsStatsCallback;
@@ -167,7 +168,10 @@
         long callingIdentity = Binder.clearCallingIdentity();
         try {
             mAppOps.checkPackage(uid, packageName);
-            PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+            PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(
+                    packageName,
+                    0,
+                    UserHandle.getUserId(uid));
             synchronized (mLock) {
                 pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode);
             }
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index c946d09..2067620 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -146,7 +146,6 @@
     private final LockPatternUtils mLockPatternUtils;
     private final NotificationManager mNotificationManager;
     private final UserManager mUserManager;
-    private final DevicePolicyManager mDevicePolicyManager;
     private final IActivityManager mActivityManager;
 
     private final KeyStore mKeyStore;
@@ -385,7 +384,6 @@
         mStorage = injector.getStorage();
         mNotificationManager = injector.getNotificationManager();
         mUserManager = injector.getUserManager();
-        mDevicePolicyManager = injector.getDevicePolicyManager();
         mStrongAuthTracker = injector.getStrongAuthTracker();
         mStrongAuthTracker.register(mStrongAuth);
 
@@ -2214,20 +2212,21 @@
                 Slog.i(TAG, "Managed profile can have escrow token");
                 return;
             }
+            DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
             // Devices with Device Owner should have escrow enabled on all users.
-            if (mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser() != null) {
+            if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
                 Slog.i(TAG, "Corp-owned device can have escrow token");
                 return;
             }
             // We could also have a profile owner on the given (non-managed) user for unicorn cases
-            if (mDevicePolicyManager.getProfileOwnerAsUser(userId) != null) {
+            if (dpm.getProfileOwnerAsUser(userId) != null) {
                 Slog.i(TAG, "User with profile owner can have escrow token");
                 return;
             }
             // If the device is yet to be provisioned (still in SUW), there is still
             // a chance that Device Owner will be set on the device later, so postpone
             // disabling escrow token for now.
-            if (!mDevicePolicyManager.isDeviceProvisioned()) {
+            if (!dpm.isDeviceProvisioned()) {
                 Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
                 return;
             }
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/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 88e0d03..8ed95ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -63,6 +63,7 @@
     static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
+    static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
     static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
     static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
     static final boolean DEBUG_POWER = DEBUG_ALL || false;
@@ -107,6 +108,7 @@
     static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
     static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
     static final String POSTFIX_MU = "_MU";
+    static final String POSTFIX_NETWORK = "_Network";
     static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
     static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
     static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b40e709..05a59d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -78,6 +78,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
@@ -107,6 +108,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
@@ -296,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;
@@ -421,6 +424,7 @@
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     private static final String TAG_LRU = TAG + POSTFIX_LRU;
     private static final String TAG_MU = TAG + POSTFIX_MU;
+    private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
     private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
     private static final String TAG_POWER = TAG + POSTFIX_POWER;
     private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
@@ -569,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;
@@ -593,7 +620,70 @@
     // default action automatically.  Important for devices without direct input
     // devices.
     private boolean mShowDialogs = true;
-    private boolean mInVrMode = false;
+    // VR state flags.
+    static final int NON_VR_MODE = 0;
+    static final int VR_MODE = 1;
+    static final int PERSISTENT_VR_MODE = 2;
+    private int mVrState = NON_VR_MODE;
+    private int mTopAppVrThreadTid = 0;
+    private int mPersistentVrThreadTid = 0;
+    final IPersistentVrStateCallbacks mPersistentVrModeListener =
+            new IPersistentVrStateCallbacks.Stub() {
+        @Override
+        public void onPersistentVrStateChanged(boolean enabled) {
+            synchronized(ActivityManagerService.this) {
+                // There are 4 possible cases here:
+                //
+                // Cases for enabled == true
+                // Invariant: mVrState != PERSISTENT_VR_MODE;
+                //    This is guaranteed as only this function sets mVrState to PERSISTENT_VR_MODE
+                // Invariant: mPersistentVrThreadTid == 0
+                //   This is guaranteed by VrManagerService, which only emits callbacks when the
+                //   mode changes, and in setPersistentVrThread, which only sets
+                //   mPersistentVrThreadTid when mVrState = PERSISTENT_VR_MODE
+                // Case 1: mTopAppVrThreadTid > 0 (someone called setVrThread successfully and is
+                // the top-app)
+                //     We reset the app which currently has SCHED_FIFO (mPersistentVrThreadTid) to
+                //     SCHED_OTHER
+                // Case 2: mTopAppVrThreadTid == 0
+                //     Do nothing
+                //
+                // Cases for enabled == false
+                // Invariant: mVrState == PERSISTENT_VR_MODE;
+                //     This is guaranteed by VrManagerService, which only emits callbacks when the
+                //     mode changes, and the only other assignment of mVrState outside of this
+                //     function checks if mVrState != PERSISTENT_VR_MODE
+                // Invariant: mTopAppVrThreadTid == 0
+                //     This is guaranteed in that mTopAppVrThreadTid is only set to a tid when
+                //     mVrState is VR_MODE, and is explicitly set to 0 when setPersistentVrThread is
+                //     called
+                //   mPersistentVrThreadTid > 0 (someone called setPersistentVrThread successfully)
+                //     3. Reset mPersistentVrThreadTidto SCHED_OTHER
+                //   mPersistentVrThreadTid == 0
+                //     4. Do nothing
+                if (enabled) {
+                    mVrState = PERSISTENT_VR_MODE;
+                } else {
+                    // Leaving persistent mode implies leaving VR mode.
+                    mVrState = NON_VR_MODE;
+                }
+
+                if (mVrState == PERSISTENT_VR_MODE) {
+                    if (mTopAppVrThreadTid > 0) {
+                        // Ensure that when entering persistent VR mode the last top-app loses
+                        // SCHED_FIFO.
+                        Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+                        mTopAppVrThreadTid = 0;
+                    }
+                } else if (mPersistentVrThreadTid > 0) {
+                    // Ensure that when leaving persistent VR mode we reschedule the high priority
+                    // persistent thread.
+                    Process.setThreadScheduler(mPersistentVrThreadTid, Process.SCHED_OTHER, 0);
+                    mPersistentVrThreadTid = 0;
+                }
+            }
+        }
+    };
 
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     private boolean mUseFifoUiScheduling = false;
@@ -1095,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;
         }
@@ -1465,6 +1555,8 @@
     @VisibleForTesting
     long mProcStateSeqCounter = 0;
 
+    private final Injector mInjector;
+
     static final class ProcessChangeItem {
         static final int CHANGE_ACTIVITIES = 1<<0;
         int changes;
@@ -1654,7 +1746,7 @@
 
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
-    final UiHandler mUiHandler;
+    final Handler mUiHandler;
 
     final ActivityManagerConstants mConstants;
 
@@ -2325,28 +2417,36 @@
                 }
                 final ActivityRecord r = (ActivityRecord) msg.obj;
                 boolean vrMode;
+                boolean inVrMode;
                 ComponentName requestedPackage;
                 ComponentName callingPackage;
                 int userId;
                 synchronized (ActivityManagerService.this) {
                     vrMode = r.requestedVrComponent != null;
+                    inVrMode = mVrState != NON_VR_MODE;
                     requestedPackage = r.requestedVrComponent;
                     userId = r.userId;
                     callingPackage = r.info.getComponentName();
-                    if (mInVrMode != vrMode) {
-                        mInVrMode = vrMode;
-                        mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
+                    if (vrMode != inVrMode) {
+                        // Don't change state if we're in persistent VR mode, but do update thread
+                        // priorities if necessary.
+                        if (mVrState != PERSISTENT_VR_MODE) {
+                            mVrState = vrMode ? VR_MODE : NON_VR_MODE;
+                        }
+                        mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), vrMode);
                         if (r.app != null) {
                             ProcessRecord proc = r.app;
                             if (proc.vrThreadTid > 0) {
                                 if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
                                     try {
-                                        if (mInVrMode == true) {
+                                        if (mVrState == VR_MODE) {
                                             Process.setThreadScheduler(proc.vrThreadTid,
                                                 Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+                                            mTopAppVrThreadTid = proc.vrThreadTid;
                                         } else {
                                             Process.setThreadScheduler(proc.vrThreadTid,
                                                 Process.SCHED_OTHER, 0);
+                                            mTopAppVrThreadTid = 0;
                                         }
                                     } catch (IllegalArgumentException e) {
                                         Slog.w(TAG, "Failed to set scheduling policy, thread does"
@@ -2631,11 +2731,12 @@
     }
 
     @VisibleForTesting
-    public ActivityManagerService(AppOpsService appOpsService) {
+    public ActivityManagerService(Injector injector) {
+        mInjector = injector;
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
-        mAppOpsService = appOpsService;
+        mAppOpsService = mInjector.getAppOpsService(null, null);
         mBatteryStatsService = null;
         mCompatModePackages = null;
         mConstants = null;
@@ -2653,7 +2754,7 @@
         mStackSupervisor = null;
         mSystemThread = null;
         mTaskChangeNotificationController = null;
-        mUiHandler = null;
+        mUiHandler = injector.getUiHandler(null);
         mUserController = null;
     }
 
@@ -2661,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();
@@ -2674,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);
 
@@ -2721,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) {
@@ -5380,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");
                 }
             }
 
@@ -5422,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");
                     }
@@ -5449,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);
@@ -8551,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;
@@ -9926,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 {
@@ -9940,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);
         }
@@ -10807,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;
@@ -10887,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;
     }
@@ -13010,54 +13137,101 @@
         }
     }
 
+    @Override
     public void setVrThread(int tid) {
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
             throw new UnsupportedOperationException("VR mode not supported on this device!");
         }
 
         synchronized (this) {
+            if (tid > 0 && mVrState == PERSISTENT_VR_MODE) {
+                Slog.e(TAG, "VR thread cannot be set in persistent VR mode!");
+                return;
+            }
             ProcessRecord proc;
             synchronized (mPidsSelfLocked) {
                 final int pid = Binder.getCallingPid();
                 proc = mPidsSelfLocked.get(pid);
-
-                if (proc != null && mInVrMode && tid >= 0) {
-                    // ensure the tid belongs to the process
-                    if (!Process.isThreadInProcess(pid, tid)) {
-                        throw new IllegalArgumentException("VR thread does not belong to process");
-                    }
-
-                    // reset existing VR thread to CFS if this thread still exists and belongs to
-                    // the calling process
-                    if (proc.vrThreadTid != 0
-                            && Process.isThreadInProcess(pid, proc.vrThreadTid)) {
-                        try {
-                            Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
-                        } catch (IllegalArgumentException e) {
-                            // Ignore this.  Only occurs in race condition where previous VR thread
-                            // was destroyed during this method call.
-                        }
-                    }
-
-                    proc.vrThreadTid = tid;
-
-                    // promote to FIFO now if the tid is non-zero
-                    try {
-                        if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
-                            proc.vrThreadTid > 0) {
-                            Process.setThreadScheduler(proc.vrThreadTid,
-                                Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
-                        }
-                    } catch (IllegalArgumentException e) {
-                        Slog.e(TAG, "Failed to set scheduling policy, thread does"
-                               + " not exist:\n" + e);
-                    }
+                if (proc != null && mVrState == VR_MODE && tid >= 0) {
+                    proc.vrThreadTid = updateVrThreadLocked(proc, proc.vrThreadTid, pid, tid);
+                    mTopAppVrThreadTid = proc.vrThreadTid;
                 }
             }
         }
     }
 
     @Override
+    public void setPersistentVrThread(int tid) {
+        if (checkCallingPermission(permission.RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
+            String msg = "Permission Denial: setPersistentVrThread() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + permission.RESTRICTED_VR_ACCESS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+            throw new UnsupportedOperationException("VR mode not supported on this device!");
+        }
+
+        synchronized (this) {
+            // Disable any existing VR thread.
+            if (mTopAppVrThreadTid > 0) {
+                Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+                mTopAppVrThreadTid = 0;
+            }
+
+            if (tid > 0 && mVrState != PERSISTENT_VR_MODE) {
+                Slog.e(TAG, "Persistent VR thread may only be set in persistent VR mode!");
+                return;
+            }
+            ProcessRecord proc;
+            synchronized (mPidsSelfLocked) {
+                final int pid = Binder.getCallingPid();
+                mPersistentVrThreadTid =
+                        updateVrThreadLocked(null, mPersistentVrThreadTid, pid, tid);
+            }
+        }
+    }
+
+    /**
+     * Used by setVrThread and setPersistentVrThread to update a thread's priority. When proc is
+     * non-null it must be in SCHED_GROUP_TOP_APP.  When it is null, the tid is unconditionally
+     * rescheduled.
+     */
+    private int updateVrThreadLocked(ProcessRecord proc, int lastTid, int pid, int tid) {
+        // ensure the tid belongs to the process
+        if (!Process.isThreadInProcess(pid, tid)) {
+            throw new IllegalArgumentException("VR thread does not belong to process");
+        }
+
+        // reset existing VR thread to CFS if this thread still exists and belongs to
+        // the calling process
+        if (lastTid != 0 && Process.isThreadInProcess(pid, lastTid)) {
+            try {
+                Process.setThreadScheduler(lastTid, Process.SCHED_OTHER, 0);
+            } catch (IllegalArgumentException e) {
+                // Ignore this.  Only occurs in race condition where previous VR thread
+                // was destroyed during this method call.
+            }
+        }
+
+        // promote to FIFO now if the tid is non-zero
+        try {
+            if ((proc == null || proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP)
+                    && tid > 0) {
+                Process.setThreadScheduler(tid,
+                    Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+            }
+            return tid;
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Failed to set scheduling policy, thread does"
+                   + " not exist:\n" + e);
+        }
+        return lastTid;
+    }
+
+    @Override
     public void setRenderThread(int tid) {
         synchronized (this) {
             ProcessRecord proc;
@@ -13643,7 +13817,10 @@
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
             mAssistUtils = new AssistUtils(mContext);
-
+            VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class);
+            if (vrManagerInternal != null) {
+                vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
+            }
             // Make sure we have the current profile info, since it is needed for security checks.
             mUserController.onSystemReady();
             mRecentTasks.onSystemReadyLocked();
@@ -19740,7 +19917,7 @@
                 mUserController.getCurrentUserIdLocked());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
-        mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
+        mShowDialogs = shouldShowDialogs(mTempConfig, mVrState != NON_VR_MODE);
 
         AttributeCache ac = AttributeCache.instance();
         if (ac != null) {
@@ -19777,14 +19954,16 @@
 
         Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_FOREGROUND);
+                | Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                 AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
                 UserHandle.USER_ALL);
         if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
             intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             if (initLocale || !mProcessesReady) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
@@ -21279,10 +21458,11 @@
                         // do nothing if we already switched to RT
                         if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                             // Switch VR thread for app to SCHED_FIFO
-                            if (mInVrMode && app.vrThreadTid != 0) {
+                            if (mVrState == VR_MODE && app.vrThreadTid != 0) {
                                 try {
                                     Process.setThreadScheduler(app.vrThreadTid,
                                         Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+                                    mTopAppVrThreadTid = app.vrThreadTid;
                                 } catch (IllegalArgumentException e) {
                                     // thread died, ignore
                                 }
@@ -21330,6 +21510,7 @@
                         // Safe to do even if we're not in VR mode
                         if (app.vrThreadTid != 0) {
                             Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
+                            mTopAppVrThreadTid = 0;
                         }
                         if (mUseFifoUiScheduling) {
                             // Reset UI pipeline to SCHED_OTHER
@@ -21504,7 +21685,8 @@
                 packages[0]);
     }
 
-    private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+    @VisibleForTesting
+    final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
         final UidRecord.ChangeItem pendingChange;
         if (uidRec == null || uidRec.pendingChange == null) {
             if (mPendingUidChanges.size() == 0) {
@@ -21546,6 +21728,9 @@
                 ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
         pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
         pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+        if (uidRec != null) {
+            uidRec.updateLastDispatchedProcStateSeq(change);
+        }
 
         // Directly update the power manager, since we sit on top of it and it is critical
         // it be kept in sync (so wake locks will be held as soon as appropriate).
@@ -21919,9 +22104,7 @@
             }
         }
 
-        for (int i = mActiveUids.size() - 1; i >= 0; --i) {
-            incrementProcStateSeqIfNeeded(mActiveUids.valueAt(i));
-        }
+        incrementProcStateSeqAndNotifyAppsLocked();
 
         mNumServiceProcs = mNewNumServiceProcs;
 
@@ -22273,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) {
@@ -22903,7 +23150,8 @@
         }
     }
 
-    private final class LocalService extends ActivityManagerInternal {
+    @VisibleForTesting
+    final class LocalService extends ActivityManagerInternal {
         @Override
         public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
                 int targetUserId) {
@@ -23153,6 +23401,122 @@
                 updateOomAdjLocked(pr);
             }
         }
+
+        /**
+         * Called after the network policy rules are updated by
+         * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid}
+         * and {@param procStateSeq}.
+         */
+        @Override
+        public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
+            if (DEBUG_NETWORK) {
+                Slog.d(TAG_NETWORK, "Got update from NPMS for uid: "
+                        + uid + " seq: " + procStateSeq);
+            }
+            UidRecord record;
+            synchronized (ActivityManagerService.this) {
+                record = mActiveUids.get(uid);
+                if (record == null) {
+                    if (DEBUG_NETWORK) {
+                        Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
+                                + " procStateSeq: " + procStateSeq);
+                    }
+                    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();
+            }
+        }
     }
 
     private final class SleepTokenImpl extends SleepToken {
@@ -23436,4 +23800,20 @@
             throw new IllegalStateException("Process disappeared");
         }
     }
+
+    @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 2b2471b..b9bb106 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -26,7 +26,7 @@
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
+import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -1005,6 +1005,11 @@
      *         the activity is not currently visible and {@param noThrow} is not set.
      */
     boolean checkEnterPictureInPictureState(String caller, boolean noThrow) {
+        // Check app-ops and see if PiP is supported for this package
+        if (!checkEnterPictureInPictureAppOpsState()) {
+            return false;
+        }
+
         boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
         boolean isKeyguardLocked = service.isKeyguardLocked();
         boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
@@ -1022,15 +1027,13 @@
                 // require that there is not an existing PiP activity and that the current system
                 // state supports entering PiP
                 return isNotLockedOrOnKeyguard && !hasPinnedStack
-                        && supportsPictureInPictureWhilePausing
-                        && checkEnterPictureInPictureOnHideAppOpsState();
+                        && supportsPictureInPictureWhilePausing;
             case STOPPING:
                 // When stopping in a valid state, then only allow enter PiP as in the pause state.
                 // Otherwise, fall through to throw an exception if the caller is trying to enter
                 // PiP in an invalid stopping state.
                 if (supportsPictureInPictureWhilePausing) {
-                    return isNotLockedOrOnKeyguard && !hasPinnedStack
-                            && checkEnterPictureInPictureOnHideAppOpsState();
+                    return isNotLockedOrOnKeyguard && !hasPinnedStack;
                 }
             default:
                 if (noThrow) {
@@ -1044,11 +1047,11 @@
     }
 
     /**
-     * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+     * @return Whether AppOps allows this package to enter picture-in-picture.
      */
-    private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+    private boolean checkEnterPictureInPictureAppOpsState() {
         try {
-            return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+            return service.getAppOpsService().checkOperation(OP_PICTURE_IN_PICTURE,
                     appInfo.uid, packageName) == MODE_ALLOWED;
         } catch (RemoteException e) {
             // Local call
@@ -2388,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 2885e66..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());
+            }
         }
     }
 
@@ -4983,6 +4987,11 @@
         }
 
         task.setStack(null);
+
+        // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
+        if (mStackId == PINNED_STACK_ID) {
+            mService.mTaskChangeNotificationController.notifyActivityUnpinned();
+        }
     }
 
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 217515b..5c49dfd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -399,6 +399,7 @@
     /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
     SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
 
+    // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
 
@@ -1226,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;
     }
@@ -1631,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);
@@ -2286,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;
@@ -2358,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) {
@@ -2470,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;
@@ -2514,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.
@@ -2853,7 +2890,7 @@
         resumeFocusedStackTopActivityLocked();
 
         stack.animateResizePinnedStack(bounds, -1 /* animationDuration */);
-        mService.mTaskChangeNotificationController.notifyActivityPinned();
+        mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
     }
 
     /** Move activity with its stack to front and make the stack focused. */
@@ -2885,9 +2922,10 @@
         return true;
     }
 
-    ActivityRecord findTaskLocked(ActivityRecord r) {
+    ActivityRecord findTaskLocked(ActivityRecord r, int displayId) {
         mTmpFindTaskResult.r = null;
         mTmpFindTaskResult.matchedByRootAffinity = false;
+        ActivityRecord affinityMatch = null;
         if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -2903,17 +2941,22 @@
                     continue;
                 }
                 stack.findTaskLocked(r, mTmpFindTaskResult);
-                // It is possible to have task in multiple stacks with the same root affinity.
-                // If the match we found was based on root affinity we keep on looking to see if
-                // there is a better match in another stack. We eventually return the match based
-                // on root affinity if we don't find a better match.
-                if (mTmpFindTaskResult.r != null && !mTmpFindTaskResult.matchedByRootAffinity) {
-                    return mTmpFindTaskResult.r;
+                // It is possible to have tasks in multiple stacks with the same root affinity, so
+                // we should keep looking after finding an affinity match to see if there is a
+                // better match in another stack. Also, task affinity isn't a good enough reason
+                // to target a display which isn't the source of the intent, so skip any affinity
+                // matches not on the specified display.
+                if (mTmpFindTaskResult.r != null) {
+                    if (!mTmpFindTaskResult.matchedByRootAffinity) {
+                        return mTmpFindTaskResult.r;
+                    } else if (mTmpFindTaskResult.r.getDisplayId() == displayId) {
+                        affinityMatch = mTmpFindTaskResult.r;
+                    }
                 }
             }
         }
-        if (DEBUG_TASKS && mTmpFindTaskResult.r == null) Slog.d(TAG_TASKS, "No task found");
-        return mTmpFindTaskResult.r;
+        if (DEBUG_TASKS && affinityMatch == null) Slog.d(TAG_TASKS, "No task found");
+        return affinityMatch;
     }
 
     ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
@@ -3719,11 +3762,9 @@
                 }
                 mActivityDisplays.put(displayId, activityDisplay);
                 calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
+                mWindowManager.onDisplayAdded(displayId);
             }
         }
-        if (newDisplay) {
-            mWindowManager.onDisplayAdded(displayId);
-        }
     }
 
     /** Check if display with specified id is added to the list. */
@@ -3762,9 +3803,9 @@
                     }
                 }
                 mActivityDisplays.remove(displayId);
+                mWindowManager.onDisplayRemoved(displayId);
             }
         }
-        mWindowManager.onDisplayRemoved(displayId);
     }
 
     private void handleDisplayChanged(int displayId) {
@@ -3773,8 +3814,8 @@
             if (activityDisplay != null) {
                 // TODO: Update the bounds.
             }
+            mWindowManager.onDisplayChanged(displayId);
         }
-        mWindowManager.onDisplayChanged(displayId);
     }
 
     private StackInfo getStackInfoLocked(ActivityStack stack) {
@@ -4734,7 +4775,7 @@
 
             init(mVirtualDisplay.getDisplay());
 
-            mWindowManager.handleDisplayAdded(mDisplayId);
+            mWindowManager.onDisplayAdded(mDisplayId);
         }
 
         void setSurface(Surface surface) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2bbfc21..1b7b225 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -54,6 +54,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -169,6 +170,7 @@
     private boolean mDoResume;
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
+    private int mSourceDisplayId;
 
     private TaskRecord mInTask;
     private boolean mAddingToTask;
@@ -208,6 +210,7 @@
         mDoResume = false;
         mStartFlags = 0;
         mSourceRecord = null;
+        mSourceDisplayId = INVALID_DISPLAY;
 
         mInTask = null;
         mAddingToTask = false;
@@ -451,8 +454,8 @@
                     Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
                             true, false) + "} from uid " + callingUid + " on display "
                             + (container == null ? (mSupervisor.mFocusedStack == null ?
-                            Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
-                            (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+                            DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
+                            (container.mActivityDisplay == null ? DEFAULT_DISPLAY :
                                     container.mActivityDisplay.mDisplayId)));
                 }
             }
@@ -1193,6 +1196,11 @@
         mVoiceSession = voiceSession;
         mVoiceInteractor = voiceInteractor;
 
+        mSourceDisplayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
+        if (mSourceDisplayId == INVALID_DISPLAY) {
+            mSourceDisplayId = DEFAULT_DISPLAY;
+        }
+
         mLaunchBounds = getOverrideBounds(r, options, inTask);
 
         mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
@@ -1439,7 +1447,7 @@
                         !mLaunchSingleTask);
             } else {
                 // Otherwise find the best task to put the activity in.
-                intentActivity = mSupervisor.findTaskLocked(mStartActivity);
+                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
             }
         }
         return intentActivity;
@@ -1925,33 +1933,31 @@
             return container.mStack;
         }
 
-        // The fullscreen stack can contain any task regardless of if the task is resizeable
-        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-        // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
-        // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
-        // we can also put it in the focused stack.
         if (canLaunchIntoFocusedStack(r, newTask)) {
             if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
                     "computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
             return mSupervisor.mFocusedStack;
         }
 
-        // We first try to put the task in the first dynamic stack on home display.
-        final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
-        for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            stack = homeDisplayStacks.get(stackNdx);
-            if (isDynamicStack(stack.mStackId)) {
-                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                        "computeStackFocus: Setting focused stack=" + stack);
-                return stack;
+        if (mSourceDisplayId == DEFAULT_DISPLAY) {
+            // We first try to put the task in the first dynamic stack on home display.
+            final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+            for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                stack = homeDisplayStacks.get(stackNdx);
+                if (isDynamicStack(stack.mStackId)) {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                            "computeStackFocus: Setting focused stack=" + stack);
+                    return stack;
+                }
             }
+            // If there is no suitable dynamic stack then we figure out which static stack to use.
+            final int stackId = task != null ? task.getLaunchStackId() :
+                    bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
+                            FULLSCREEN_WORKSPACE_STACK_ID;
+            stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+        } else {
+            stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
         }
-
-        // If there is no suitable dynamic stack then we figure out which static stack to use.
-        final int stackId = task != null ? task.getLaunchStackId() :
-                bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
-                        FULLSCREEN_WORKSPACE_STACK_ID;
-        stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
         if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
                 + r + " stackId=" + stack.mStackId);
         return stack;
@@ -1959,35 +1965,35 @@
 
     /** Check if provided activity record can launch in currently focused stack. */
     private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
-        // The fullscreen stack can contain any task regardless of if the task is resizeable
-        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-        // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
-        // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
-        // we can also put it in the focused stack.
         final ActivityStack focusedStack = mSupervisor.mFocusedStack;
         final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
         final boolean canUseFocusedStack;
         switch (focusedStackId) {
             case FULLSCREEN_WORKSPACE_STACK_ID:
+                // The fullscreen stack can contain any task regardless of if the task is resizeable
+                // or not. So, we let the task go in the fullscreen task if it is the focus stack.
                 canUseFocusedStack = true;
                 break;
             case ASSISTANT_STACK_ID:
                 canUseFocusedStack = r.isAssistantActivity();
                 break;
             case DOCKED_STACK_ID:
+                // Any activty which supports split screen can go in the docked stack.
                 canUseFocusedStack = r.supportsSplitScreen();
                 break;
             case FREEFORM_WORKSPACE_STACK_ID:
+                // Any activty which supports freeform can go in the freeform stack.
                 canUseFocusedStack = r.supportsFreeform();
                 break;
             default:
-                canUseFocusedStack = isDynamicStack(focusedStackId)
-                        && mSupervisor.isCallerAllowedToLaunchOnDisplay(r.launchedFromPid,
-                        r.launchedFromUid, focusedStack.mDisplayId);
+                // Dynamic stacks behave similarly to the fullscreen stack and can contain any task.
+                canUseFocusedStack = isDynamicStack(focusedStackId);
         }
 
         return canUseFocusedStack
-                && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks());
+                && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks())
+                // We strongly prefer to launch activities on the same display as their source.
+                && (mSourceDisplayId == focusedStack.mDisplayId);
     }
 
     private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2034,7 +2040,8 @@
             return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
         }
 
-        if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0) {
+        if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0
+                || mSourceDisplayId != DEFAULT_DISPLAY) {
             return null;
         }
         // Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 3cec7e4..94cf092 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -47,6 +47,7 @@
     static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
     static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
     static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
+    static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
 
     // Delay in notifying task stack change listeners (in millis)
     static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -94,7 +95,11 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        l.onActivityPinned();
+        l.onActivityPinned((String) m.obj);
+    };
+
+    private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
+        l.onActivityUnpinned();
     };
 
     private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
@@ -168,6 +173,9 @@
                 case NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyActivityPinned, msg);
                     break;
+                case NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG:
+                    forAllRemoteListeners(mNotifyActivityUnpinned, msg);
+                    break;
                 case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
                     break;
@@ -263,13 +271,22 @@
     }
 
     /** Notifies all listeners when an Activity is pinned. */
-    void notifyActivityPinned() {
+    void notifyActivityPinned(String packageName) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+                packageName);
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
 
+    /** Notifies all listeners when an Activity is unpinned. */
+    void notifyActivityUnpinned() {
+        mHandler.removeMessages(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+        forAllLocalListeners(mNotifyActivityUnpinned, msg);
+        msg.sendToTarget();
+    }
+
     /**
      * Notifies all listeners when an attempt was made to start an an activity that is already
      * running in the pinned stack and the activity was not actually started, but the task is
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 cf6c1e1..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.
  */
@@ -34,13 +37,37 @@
     boolean setWhitelist;
     boolean idle;
     int numProcs;
+
     /**
      * Sequence number associated with the {@link #curProcState}. This is incremented using
      * {@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;
@@ -67,6 +94,17 @@
         curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
     }
 
+    /**
+     * If the change being dispatched is neither CHANGE_GONE nor CHANGE_GONE_IDLE (not interested in
+     * these changes), then update the {@link #lastDispatchedProcStateSeq} with
+     * {@link #curProcStateSeq}.
+     */
+    public void updateLastDispatchedProcStateSeq(int changeToDispatch) {
+        if (changeToDispatch != CHANGE_GONE && changeToDispatch != CHANGE_GONE_IDLE) {
+            lastDispatchedProcStateSeq = curProcStateSeq;
+        }
+    }
+
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("UidRecord{");
@@ -92,6 +130,10 @@
         sb.append(numProcs);
         sb.append(" curProcStateSeq:");
         sb.append(curProcStateSeq);
+        sb.append(" lastNetworkUpdatedProcStateSeq:");
+        sb.append(lastNetworkUpdatedProcStateSeq);
+        sb.append(" lastDispatchedProcStateSeq:");
+        sb.append(lastDispatchedProcStateSeq);
         sb.append("}");
         return sb.toString();
     }
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 81e891a..ad66faa 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,6 +16,17 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
+
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
@@ -29,14 +40,12 @@
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
 import android.os.Parcelable;
+import android.util.SparseArray;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
 
 /** {@hide} */
 final public class IpConnectivityEventBuilder {
@@ -73,6 +82,12 @@
             return null;
         }
         out.timeMs = ev.timestamp;
+        out.networkId = ev.netId;
+        out.transports = ev.transports;
+        if (ev.ifname != null) {
+          out.ifName = ev.ifname;
+        }
+        inferLinkLayer(out);
         return out;
     }
 
@@ -137,14 +152,12 @@
 
     private static void setDhcpErrorEvent(IpConnectivityEvent out, DhcpErrorEvent in) {
         IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
-        dhcpEvent.ifName = in.ifName;
         dhcpEvent.setErrorCode(in.errorCode);
         out.setDhcpEvent(dhcpEvent);
     }
 
     private static void setDhcpClientEvent(IpConnectivityEvent out, DhcpClientEvent in) {
         IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
-        dhcpEvent.ifName = in.ifName;
         dhcpEvent.setStateTransition(in.msg);
         dhcpEvent.durationMs = in.durationMs;
         out.setDhcpEvent(dhcpEvent);
@@ -163,7 +176,6 @@
     private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) {
         IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent =
                 new IpConnectivityLogClass.IpProvisioningEvent();
-        ipProvisioningEvent.ifName = in.ifName;
         ipProvisioningEvent.eventType = in.eventType;
         ipProvisioningEvent.latencyMs = (int) in.durationMs;
         out.setIpProvisioningEvent(ipProvisioningEvent);
@@ -172,7 +184,6 @@
     private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) {
         IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent =
                 new IpConnectivityLogClass.IpReachabilityEvent();
-        ipReachabilityEvent.ifName = in.ifName;
         ipReachabilityEvent.eventType = in.eventType;
         out.setIpReachabilityEvent(ipReachabilityEvent);
     }
@@ -199,7 +210,6 @@
     private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) {
         IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent =
                 new IpConnectivityLogClass.ValidationProbeEvent();
-        validationProbeEvent.networkId = netIdOf(in.netId);
         validationProbeEvent.latencyMs = (int) in.durationMs;
         validationProbeEvent.probeType = in.probeType;
         validationProbeEvent.probeResult = in.returnCode;
@@ -280,4 +290,70 @@
     private static boolean isBitSet(int flags, int bit) {
         return (flags & (1 << bit)) != 0;
     }
+
+    private static void inferLinkLayer(IpConnectivityEvent ev) {
+        int linkLayer = IpConnectivityLogClass.UNKNOWN;
+        if (ev.transports != 0) {
+            linkLayer = transportsToLinkLayer(ev.transports);
+        } else if (ev.ifName != null) {
+            linkLayer = ifnameToLinkLayer(ev.ifName);
+        }
+        if (linkLayer == IpConnectivityLogClass.UNKNOWN) {
+            return;
+        }
+        ev.linkLayer = linkLayer;
+        ev.ifName = "";
+    }
+
+    private static int transportsToLinkLayer(long transports) {
+        switch (Long.bitCount(transports)) {
+            case 0:
+                return IpConnectivityLogClass.UNKNOWN;
+            case 1:
+                int t = Long.numberOfTrailingZeros(transports);
+                return transportToLinkLayer(t);
+            default:
+                return IpConnectivityLogClass.MULTIPLE;
+        }
+    }
+
+    private static int transportToLinkLayer(int transport) {
+        if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
+            return TRANSPORT_LINKLAYER_MAP[transport];
+        }
+        return IpConnectivityLogClass.UNKNOWN;
+    }
+
+    private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+    static {
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR]   = IpConnectivityLogClass.CELLULAR;
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI]       = IpConnectivityLogClass.WIFI;
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH]  = IpConnectivityLogClass.BLUETOOTH;
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET]   = IpConnectivityLogClass.ETHERNET;
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN]        = IpConnectivityLogClass.UNKNOWN;
+        // TODO: change mapping TRANSPORT_WIFI_AWARE -> WIFI_AWARE
+        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.UNKNOWN;
+    };
+
+    private static int ifnameToLinkLayer(String ifname) {
+        // Do not try to catch all interface names with regexes, instead only catch patterns that
+        // are cheap to check, and otherwise fallback on postprocessing in aggregation layer.
+        for (int i = 0; i < IFNAME_LINKLAYER_MAP.size(); i++) {
+            String pattern = IFNAME_LINKLAYER_MAP.valueAt(i);
+            if (ifname.startsWith(pattern)) {
+                return IFNAME_LINKLAYER_MAP.keyAt(i);
+            }
+        }
+        return IpConnectivityLogClass.UNKNOWN;
+    }
+
+    private static final SparseArray<String> IFNAME_LINKLAYER_MAP = new SparseArray<String>();
+    static {
+        IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.CELLULAR, "rmnet");
+        IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.WIFI, "wlan");
+        IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.BLUETOOTH, "bt-pan");
+        // TODO: rekey to USB
+        IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.ETHERNET, "usb");
+        // TODO: add mappings for nan -> WIFI_AWARE and p2p -> WIFI_P2P
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index cf33313..d591858 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -1048,8 +1048,12 @@
     }
 
     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
-        probeType =
-                ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation);
-        mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
+        long transports = mNetworkAgentInfo.networkCapabilities.getTransports();
+        boolean isFirstValidation = validationStage().isFirstValidation;
+        ValidationProbeEvent ev = new ValidationProbeEvent();
+        ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
+        ev.returnCode = probeResult;
+        ev.durationMs = durationMs;
+        mMetricsLog.log(mNetId, transports, ev);
     }
 }
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/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index cba694c..d1275bb 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -65,16 +65,6 @@
     private static final boolean DEBUG = false;
 
     /**
-     * Night display ~= 3400 K.
-     */
-    private static final float[] MATRIX_NIGHT = new float[] {
-        1,      0,      0, 0,
-        0, 0.754f,      0, 0,
-        0,      0, 0.516f, 0,
-        0,      0,      0, 1
-    };
-
-    /**
      * The transition time, in milliseconds, for Night Display to turn on/off.
      */
     private static final long TRANSITION_DURATION = 3000L;
@@ -112,13 +102,34 @@
                     if (enabled) {
                         dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
                     } else if (mController != null && mController.isActivated()) {
-                        dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_NIGHT);
+                        setMatrix(mController.getColorTemperature(), mMatrixNight);
+                        dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
                     }
                 }
             });
         }
     };
 
+    private float[] mMatrixNight = new float[16];
+
+    /**
+     *  These coefficients were generated by an LLS quadratic regression fitted to the
+     *  overdetermined system based on experimental readings (and subsequent conversion from xy
+     *  chromaticity coordinates to gamma-corrected RGB values): { (temperature, R, G, B) } ->
+     *  { (7304, 1.0, 1.0, 1.0), (4082, 1.0, 0.857, 0.719), (2850, 1.0, .754, .516),
+     *  (2596, 1.0, 0.722, 0.454) }. The 3x3 matrix is formatted like so:
+     *  <table>
+     *      <tr><td>R: a coefficient</td><td>G: a coefficient</td><td>B: a coefficient</td></tr>
+     *      <tr><td>R: b coefficient</td><td>G: b coefficient</td><td>B: b coefficient</td></tr>
+     *      <tr><td>R: y-intercept</td><td>G: y-intercept</td><td>B: y-intercept</td></tr>
+     *  </table>
+     */
+    private static final float[] mColorTempCoefficients = new float[] {
+            0.0f, -0.00000000962353339f, -0.0000000189359041f,
+            0.0f, 0.000153045476f, 0.000302412211f,
+            1.0f, 0.390782778f, -0.198650895f
+    };
+
     private int mCurrentUser = UserHandle.USER_NULL;
     private ContentObserver mUserSetupObserver;
     private boolean mBootCompleted;
@@ -232,6 +243,9 @@
         mController = new NightDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
+        // Prepare color transformation matrix.
+        setMatrix(mController.getColorTemperature(), mMatrixNight);
+
         // Initialize the current auto mode.
         onAutoModeChanged(mController.getAutoMode());
 
@@ -239,6 +253,9 @@
         if (mIsActivated == null) {
             onActivated(mController.isActivated());
         }
+
+        // Transition the screen to the current temperature.
+        applyTint(false);
     }
 
     private void tearDown() {
@@ -273,53 +290,7 @@
 
             mIsActivated = activated;
 
-            // Cancel the old animator if still running.
-            if (mColorMatrixAnimator != null) {
-                mColorMatrixAnimator.cancel();
-            }
-
-            // Don't do any color matrix change animations if we are ignoring them anyway.
-            if (mIgnoreAllColorMatrixChanges.get()) {
-                return;
-            }
-
-            final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
-            final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
-            final float[] to = mIsActivated ? MATRIX_NIGHT : null;
-
-            mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
-                    from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
-            mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
-            mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
-                    getContext(), android.R.interpolator.fast_out_slow_in));
-            mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animator) {
-                    final float[] value = (float[]) animator.getAnimatedValue();
-                    dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
-                }
-            });
-            mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
-
-                private boolean mIsCancelled;
-
-                @Override
-                public void onAnimationCancel(Animator animator) {
-                    mIsCancelled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animator) {
-                    if (!mIsCancelled) {
-                        // Ensure final color matrix is set at the end of the animation. If the
-                        // animation is cancelled then don't set the final color matrix so the new
-                        // animator can pick up from where this one left off.
-                        dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
-                    }
-                    mColorMatrixAnimator = null;
-                }
-            });
-            mColorMatrixAnimator.start();
+            applyTint(false);
         }
     }
 
@@ -361,6 +332,97 @@
         }
     }
 
+    @Override
+    public void onColorTemperatureChanged(int colorTemperature) {
+        setMatrix(colorTemperature, mMatrixNight);
+        applyTint(true);
+    }
+
+    /**
+     * Applies current color temperature matrix, or removes it if deactivated.
+     *
+     * @param immediate {@code true} skips transition animation
+     */
+    private void applyTint(boolean immediate) {
+        // Cancel the old animator if still running.
+        if (mColorMatrixAnimator != null) {
+            mColorMatrixAnimator.cancel();
+        }
+
+        // Don't do any color matrix change animations if we are ignoring them anyway.
+        if (mIgnoreAllColorMatrixChanges.get()) {
+            return;
+        }
+
+        final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+        final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
+        final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
+
+        if (immediate) {
+            dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+        } else {
+            mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
+                    from == null ? MATRIX_IDENTITY : from, to);
+            mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
+            mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+                    getContext(), android.R.interpolator.fast_out_slow_in));
+            mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    final float[] value = (float[]) animator.getAnimatedValue();
+                    dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
+                }
+            });
+            mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
+
+                private boolean mIsCancelled;
+
+                @Override
+                public void onAnimationCancel(Animator animator) {
+                    mIsCancelled = true;
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animator) {
+                    if (!mIsCancelled) {
+                        // Ensure final color matrix is set at the end of the animation. If the
+                        // animation is cancelled then don't set the final color matrix so the new
+                        // animator can pick up from where this one left off.
+                        dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+                    }
+                    mColorMatrixAnimator = null;
+                }
+            });
+            mColorMatrixAnimator.start();
+        }
+    }
+
+    /**
+     * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
+     *
+     * @param colorTemperature color temperature in Kelvin
+     * @param outTemp the 4x4 display transformation matrix for that color temperature
+     */
+    private void setMatrix(int colorTemperature, float[] outTemp) {
+        if (outTemp.length != 16) {
+            Slog.d(TAG, "The display transformation matrix must be 4x4");
+            return;
+        }
+
+        Matrix.setIdentityM(mMatrixNight, 0);
+
+        final float squareTemperature = colorTemperature * colorTemperature;
+        final float red = squareTemperature * mColorTempCoefficients[0]
+                + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
+        final float green = squareTemperature * mColorTempCoefficients[1]
+                + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
+        final float blue = squareTemperature * mColorTempCoefficients[2]
+                + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
+        outTemp[0] = red;
+        outTemp[5] = green;
+        outTemp[10] = blue;
+    }
+
     private abstract class AutoMode implements NightDisplayController.Callback {
         public abstract void onStart();
         public abstract void onStop();
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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9d93cc7..8a4f3f7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,6 +96,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -251,6 +252,13 @@
     private static final int VERSION_SWITCH_UID = 10;
     private static final int VERSION_LATEST = VERSION_SWITCH_UID;
 
+    /**
+     * Max items written to {@link #ProcStateSeqHistory}.
+     */
+    @VisibleForTesting
+    public static final int MAX_PROC_STATE_SEQ_HISTORY =
+            ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
+
     @VisibleForTesting
     public static final int TYPE_WARNING = 0x1;
     @VisibleForTesting
@@ -412,6 +420,15 @@
 
     private final IPackageManager mIPm;
 
+    private ActivityManagerInternal mActivityManagerInternal;
+
+    /**
+     * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
+     * the uid and procStateSeq will be written to this and will be printed as part of dump.
+     */
+    @VisibleForTesting
+    public ProcStateSeqHistory mObservedHistory
+            = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
 
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
@@ -628,6 +645,7 @@
                 }
             }
 
+            mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
             try {
                 mActivityManager.registerUidObserver(mUidObserver,
                         ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE,
@@ -724,7 +742,13 @@
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
             try {
                 synchronized (mUidRulesFirstLock) {
+                    // We received a uid state change callback, add it to the history so that it
+                    // will be useful for debugging.
+                    mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                    // Now update the network policy rules as per the updated uid state.
                     updateUidStateUL(uid, procState);
+                    // Updating the network rules is done, so notify AMS about this.
+                    mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -2429,6 +2453,11 @@
                     fout.println();
                 }
                 fout.decreaseIndent();
+
+                fout.println("Observed uid state changes:");
+                fout.increaseIndent();
+                mObservedHistory.dumpUL(fout);
+                fout.decreaseIndent();
             }
         }
     }
@@ -2524,10 +2553,16 @@
 
     // adjust stats accounting based on foreground status
     private void updateNetworkStats(int uid, boolean uidForeground) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+                    "updateNetworkStats: " + uid + "/" + (uidForeground ? "F" : "B"));
+        }
         try {
             mNetworkStats.setUidForeground(uid, uidForeground);
         } catch (RemoteException e) {
             // ignored; service lives in system_server
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
@@ -2655,12 +2690,19 @@
     void updateRuleForAppIdleUL(int uid) {
         if (!isUidValidForBlacklistRules(uid)) return;
 
-        int appId = UserHandle.getAppId(uid);
-        if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
-                && !isUidForegroundOnRestrictPowerUL(uid)) {
-            setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
-        } else {
-            setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
+        }
+        try {
+            int appId = UserHandle.getAppId(uid);
+            if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
+                    && !isUidForegroundOnRestrictPowerUL(uid)) {
+                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+            } else {
+                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
@@ -2696,7 +2738,10 @@
      * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
      */
     private void updateRulesForGlobalChangeAL(boolean restrictedNetworksChanged) {
-        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForGlobalChangeAL");
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+                    "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
+        }
         try {
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
@@ -2749,14 +2794,27 @@
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL-" + type);
         }
         try {
-            final PackageManager pm = mContext.getPackageManager();
-
             // update rules for all installed applications
-            final List<UserInfo> users = mUserManager.getUsers();
-            final List<ApplicationInfo> apps = pm.getInstalledApplications(
-                    PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+
+            final PackageManager pm = mContext.getPackageManager();
+            final List<UserInfo> users;
+            final List<ApplicationInfo> apps;
+
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-users");
+            try {
+                users = mUserManager.getUsers();
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+            }
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-uids");
+            try {
+                apps = pm.getInstalledApplications(
+                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
+                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+            }
 
             final int usersSize = users.size();
             final int appsSize = apps.size();
@@ -2778,9 +2836,7 @@
                 }
             }
         } finally {
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
-                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
-            }
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
@@ -2931,6 +2987,18 @@
      *
      */
     private void updateRulesForDataUsageRestrictionsUL(int uid) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+                    "updateRulesForDataUsageRestrictionsUL: " + uid);
+        }
+        try {
+            updateRulesForDataUsageRestrictionsULInner(uid);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    private void updateRulesForDataUsageRestrictionsULInner(int uid) {
         if (!isUidValidForWhitelistRules(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
             return;
@@ -3073,6 +3141,19 @@
      * @return the new computed rules for the uid
      */
     private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+                    "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+                    + (paroled ? "P" : "-"));
+        }
+        try {
+            return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
         if (!isUidValidForBlacklistRules(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
             return RULE_NONE;
@@ -3432,20 +3513,28 @@
      * Add or remove a uid to the firewall blacklist for all network ifaces.
      */
     private void setUidFirewallRule(int chain, int uid, int rule) {
-        if (chain == FIREWALL_CHAIN_DOZABLE) {
-            mUidFirewallDozableRules.put(uid, rule);
-        } else if (chain == FIREWALL_CHAIN_STANDBY) {
-            mUidFirewallStandbyRules.put(uid, rule);
-        } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
-            mUidFirewallPowerSaveRules.put(uid, rule);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+                    "setUidFirewallRule: " + chain + "/" + uid + "/" + rule);
         }
-
         try {
-            mNetworkManager.setFirewallUidRule(chain, uid, rule);
-        } catch (IllegalStateException e) {
-            Log.wtf(TAG, "problem setting firewall uid rules", e);
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
+            if (chain == FIREWALL_CHAIN_DOZABLE) {
+                mUidFirewallDozableRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_STANDBY) {
+                mUidFirewallStandbyRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
+                mUidFirewallPowerSaveRules.put(uid, rule);
+            }
+
+            try {
+                mNetworkManager.setFirewallUidRule(chain, uid, rule);
+            } catch (IllegalStateException e) {
+                Log.wtf(TAG, "problem setting firewall uid rules", e);
+            } catch (RemoteException e) {
+                // ignored; service lives in system_server
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
@@ -3609,4 +3698,74 @@
             }
         }
     }
+
+    /**
+     * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
+     * (uid, procStateSeq) pairs.
+     */
+    @VisibleForTesting
+    public static final class ProcStateSeqHistory {
+        private static final int INVALID_UID = -1;
+
+        /**
+         * Denotes maximum number of items this history can hold.
+         */
+        private final int mMaxCapacity;
+        /**
+         * Used for storing the uid information.
+         */
+        private final int[] mUids;
+        /**
+         * Used for storing the sequence numbers associated with {@link #mUids}.
+         */
+        private final long[] mProcStateSeqs;
+        /**
+         * Points to the next available slot for writing (uid, procStateSeq) pair.
+         */
+        private int mHistoryNext;
+
+        public ProcStateSeqHistory(int maxCapacity) {
+            mMaxCapacity = maxCapacity;
+            mUids = new int[mMaxCapacity];
+            Arrays.fill(mUids, INVALID_UID);
+            mProcStateSeqs = new long[mMaxCapacity];
+        }
+
+        @GuardedBy("mUidRulesFirstLock")
+        public void addProcStateSeqUL(int uid, long procStateSeq) {
+            mUids[mHistoryNext] = uid;
+            mProcStateSeqs[mHistoryNext] = procStateSeq;
+            mHistoryNext = increaseNext(mHistoryNext, 1);
+        }
+
+        @GuardedBy("mUidRulesFirstLock")
+        public void dumpUL(IndentingPrintWriter fout) {
+            if (mUids[0] == INVALID_UID) {
+                fout.println("NONE");
+                return;
+            }
+            int index = mHistoryNext;
+            do {
+                index = increaseNext(index, -1);
+                if (mUids[index] == INVALID_UID) {
+                    break;
+                }
+                fout.println(getString(mUids[index], mProcStateSeqs[index]));
+            } while (index != mHistoryNext);
+        }
+
+        public static String getString(int uid, long procStateSeq) {
+            return "UID=" + uid + " procStateSeq=" + procStateSeq;
+        }
+
+        private int increaseNext(int next, int increment) {
+            next += increment;
+            if (next >= mMaxCapacity) {
+                next = 0;
+            } else if (next < 0) {
+                next = mMaxCapacity - 1;
+            }
+            return next;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 104c296..6d666e8 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -384,6 +384,7 @@
         mContext.unregisterReceiver(mTetherReceiver);
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
+        mContext.unregisterReceiver(mUserReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
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 02f92fe..a42a70e 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -201,10 +201,9 @@
                             // Channels
                             if (TAG_CHANNEL.equals(tagName)) {
                                 String id = parser.getAttributeValue(null, ATT_ID);
-                                CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
+                                String channelName = parser.getAttributeValue(null, ATT_NAME);
                                 int channelImportance =
                                         safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-
                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
                                     NotificationChannel channel = new NotificationChannel(id,
                                             channelName, channelImportance);
@@ -214,7 +213,11 @@
                             }
                         }
 
-                        clampDefaultChannel(r);
+                        try {
+                            deleteDefaultChannelIfNeeded(r);
+                        } catch (NameNotFoundException e) {
+                            Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+                        }
                     }
                 }
             }
@@ -248,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 {
@@ -342,7 +379,9 @@
                 }
 
                 for (NotificationChannel channel : r.channels.values()) {
-                    channel.writeXml(out);
+                    if (!forBackup || (forBackup && !channel.isDeleted())) {
+                        channel.writeXml(out);
+                    }
                 }
 
                 out.endTag(null, TAG_PACKAGE);
@@ -507,7 +546,8 @@
                 existing.setDeleted(false);
             }
 
-            existing.setName(channel.getName());
+            existing.setName(channel.getName().toString());
+            existing.setDescription(channel.getDescription());
 
             MetricsLogger.action(getChannelLog(channel, pkg));
             updateConfig();
@@ -618,21 +658,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);
@@ -1056,10 +1081,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/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index 3432ecd..b0730ef 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -68,11 +68,12 @@
         mIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE).setComponent(componentName);
     }
 
-    public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[]) {
+    public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
+            String token) {
         throwIfCalledOnMainThread();
         try {
             return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
-                    getRemoteInstanceLazy(), hashPrefix);
+                    getRemoteInstanceLazy(), hashPrefix, token);
         } catch (RemoteException re) {
         } catch (TimeoutException te) {
         } finally {
@@ -83,8 +84,9 @@
         return null;
     }
 
-    public final void getInstantAppIntentFilterList(int hashPrefix[], String hostName,
-            PhaseTwoCallback callback, Handler callbackHandler, final long startTime) {
+    public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
+            String hostName, PhaseTwoCallback callback, Handler callbackHandler,
+            final long startTime) {
         final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
             @Override
             public void sendResult(Bundle data) throws RemoteException {
@@ -100,9 +102,8 @@
             }
         };
         try {
-            // TODO deprecate sequence; it's never used
-            getRemoteInstanceLazy().getInstantAppIntentFilterList(
-                    hashPrefix, 0 /*sequence*/, hostName, remoteCallback);
+            getRemoteInstanceLazy()
+                    .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
         } catch (RemoteException re) {
         } catch (TimeoutException te) {
         }
@@ -215,10 +216,10 @@
         }
 
         public List<InstantAppResolveInfo> getEphemeralResolveInfoList(
-                IInstantAppResolver target, int hashPrefix[])
+                IInstantAppResolver target, int hashPrefix[], String token)
                         throws RemoteException, TimeoutException {
             final int sequence = onBeforeRemoteCall();
-            target.getInstantAppResolveInfoList(hashPrefix, sequence, mCallback);
+            target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback);
             return getResultTimed(sequence);
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 3396954..59f8a2d 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -40,6 +40,7 @@
 import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
 import android.metrics.LogMaker;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
@@ -57,6 +58,9 @@
 
 /** @hide */
 public abstract class InstantAppResolver {
+    private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+    private static final String TAG = "PackageManager";
+
     private static int RESOLUTION_SUCCESS = 0;
     private static int RESOLUTION_FAILURE = 1;
 
@@ -70,6 +74,9 @@
 
     public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context,
             EphemeralResolverConnection connection, InstantAppRequest requestObj) {
+        if (DEBUG_EPHEMERAL) {
+            Log.d(TAG, "Resolving phase 1");
+        }
         final long startTime = System.currentTimeMillis();
         final String token = UUID.randomUUID().toString();
         final Intent intent = requestObj.origIntent;
@@ -77,11 +84,14 @@
                 new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/);
         final int[] shaPrefix = digest.getDigestPrefix();
         final List<InstantAppResolveInfo> instantAppResolveInfoList =
-                connection.getInstantAppResolveInfoList(shaPrefix); // pass token
+                connection.getInstantAppResolveInfoList(shaPrefix, token);
 
         final AuxiliaryResolveInfo resolveInfo;
         if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
             // No hash prefix match; there are no instant apps for this domain.
+            if (DEBUG_EPHEMERAL) {
+                Log.d(TAG, "No results returned");
+            }
             resolveInfo = null;
         } else {
             resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList,
@@ -98,11 +108,15 @@
     public static void doInstantAppResolutionPhaseTwo(Context context,
             EphemeralResolverConnection connection, InstantAppRequest requestObj,
             ActivityInfo instantAppInstaller, Handler callbackHandler) {
+        if (DEBUG_EPHEMERAL) {
+            Log.d(TAG, "Resolving phase 2");
+        }
         final long startTime = System.currentTimeMillis();
         final Intent intent = requestObj.origIntent;
         final String hostName = intent.getData().getHost();
         final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/);
         final int[] shaPrefix = digest.getDigestPrefix();
+        final String token = requestObj.responseObj.token;
 
         final PhaseTwoCallback callback = new PhaseTwoCallback() {
             @Override
@@ -111,7 +125,6 @@
                 final String packageName;
                 final String splitName;
                 final int versionCode;
-                final String token = requestObj.responseObj.token;
                 if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
                     final AuxiliaryResolveInfo instantAppIntentInfo =
                             InstantAppResolver.filterInstantAppIntent(
@@ -152,7 +165,7 @@
             }
         };
         connection.getInstantAppIntentFilterList(
-                shaPrefix, hostName, callback, callbackHandler, startTime);
+                shaPrefix, token, hostName, callback, callbackHandler, startTime);
     }
 
     /**
@@ -187,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(
@@ -203,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(
@@ -245,6 +260,9 @@
                         instantAppInfo.getIntentFilters();
                 // No filters; we need to start phase two
                 if (instantAppFilters == null || instantAppFilters.isEmpty()) {
+                    if (DEBUG_EPHEMERAL) {
+                        Log.d(TAG, "No app filters; go to phase 2");
+                    }
                     return new AuxiliaryResolveInfo(instantAppInfo,
                             new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/,
                             null /*splitName*/, token, true /*needsPhase2*/);
@@ -269,6 +287,13 @@
                 List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent(
                         intent, resolvedType, false /*defaultOnly*/, userId);
                 if (!matchedResolveInfoList.isEmpty()) {
+                    if (DEBUG_EPHEMERAL) {
+                        final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0);
+                        Log.d(TAG, "Found match;"
+                                + " package: " + info.packageName
+                                + ", split: " + info.splitName
+                                + ", versionCode: " + info.versionCode);
+                    }
                     return matchedResolveInfoList.get(0);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a02978c..96e2626 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18656,7 +18656,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 548fa1e..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;
@@ -297,7 +298,7 @@
     // These need to match the documentation/constant in
     // core/res/res/values/config.xml
     static final int LONG_PRESS_HOME_NOTHING = 0;
-    static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
+    static final int LONG_PRESS_HOME_ALL_APPS = 1;
     static final int LONG_PRESS_HOME_ASSIST = 2;
     static final int LAST_LONG_PRESS_HOME_BEHAVIOR = LONG_PRESS_HOME_ASSIST;
 
@@ -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;
@@ -1700,10 +1701,9 @@
         }
         mHomeConsumed = true;
         performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-
         switch (mLongPressOnHomeBehavior) {
-            case LONG_PRESS_HOME_RECENT_SYSTEM_UI:
-                toggleRecentApps();
+            case LONG_PRESS_HOME_ALL_APPS:
+                launchAllAppsAction();
                 break;
             case LONG_PRESS_HOME_ASSIST:
                 launchAssistAction(null, deviceId);
@@ -1714,6 +1714,11 @@
         }
     }
 
+    private void launchAllAppsAction() {
+        Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+        startActivityAsUser(intent, UserHandle.CURRENT);
+    }
+
     private void handleDoubleTapOnHome() {
         if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
             mHomeConsumed = true;
@@ -2397,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;
@@ -3332,8 +3339,7 @@
                     mHomeDoubleTapPending = false;
                     mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
                     handleDoubleTapOnHome();
-                } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
-                        || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+                } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
                     preloadRecentApps();
                 }
             } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
@@ -7524,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;
@@ -7551,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/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index c31369e..8f11436 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -145,10 +145,12 @@
         mHandler = new NotifierHandler(looper);
         mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
         mScreenOnIntent.addFlags(
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
         mScreenOffIntent.addFlags(
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mScreenBrightnessBoostIntent =
                 new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED);
         mScreenBrightnessBoostIntent.addFlags(
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 7720e24..e94dd0f 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -186,7 +186,8 @@
                 try {
                     // We need the app info to determine the uid and the uuid of the volume
                     // where the app is installed.
-                    ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+                    ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+                            packageName, 0, info.id);
                     requests.add(
                             new CacheQuotaHint.Builder()
                                     .setVolumeUuid(appInfo.volumeUuid)
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/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 0e6b1b6..012480e 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -299,6 +299,13 @@
     }
 
     /**
+     * @return the current aspect ratio.
+     */
+    float getAspectRatio() {
+        return mAspectRatio;
+    }
+
+    /**
      * Sets the current set of actions.
      */
     void setActions(List<RemoteAction> actions) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 6a0e353..34ccf87 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -64,6 +64,9 @@
             mContainer.getBounds(originalBounds);
             mContainer.setAnimatingBounds(toBounds);
             UiThread.getHandler().post(() -> {
+                if (mContainer == null) {
+                    return;
+                }
                 mService.mBoundsAnimationController.animateBounds(mContainer, originalBounds,
                         toBounds, animationDuration, moveToFullscreen);
             });
@@ -79,19 +82,17 @@
                 return;
             }
 
-            final int displayId = mContainer.getDisplayContent().getDisplayId();
-            final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
-            final Rect targetBounds = new Rect();
-            mContainer.getAnimatingBounds(targetBounds);
-            if (!toBounds.equals(targetBounds)) {
-                animateResizePinnedStack(toBounds, -1 /* duration */);
-            }
-
             final PinnedStackController pinnedStackController =
                     mContainer.getDisplayContent().getPinnedStackController();
-            pinnedStackController.setAspectRatio(
-                    pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
-                            ? aspectRatio : -1f);
+
+            if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
+                final int displayId = mContainer.getDisplayContent().getDisplayId();
+                final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+                animateResizePinnedStack(toBounds, -1 /* duration */);
+                pinnedStackController.setAspectRatio(
+                        pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
+                                ? aspectRatio : -1f);
+            }
         }
     }
 
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 7539cd4..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);
     }
 
     /**
@@ -4634,10 +4635,6 @@
         public static final int SHOW_STRICT_MODE_VIOLATION = 25;
         public static final int DO_ANIMATION_CALLBACK = 26;
 
-        public static final int DO_DISPLAY_ADDED = 27;
-        public static final int DO_DISPLAY_REMOVED = 28;
-        public static final int DO_DISPLAY_CHANGED = 29;
-
         public static final int CLIENT_FREEZE_TIMEOUT = 30;
         public static final int TAP_OUTSIDE_TASK = 31;
         public static final int NOTIFY_ACTIVITY_DRAWN = 32;
@@ -4981,22 +4978,6 @@
                     break;
                 }
 
-                case DO_DISPLAY_ADDED:
-                    handleDisplayAdded(msg.arg1);
-                    break;
-
-                case DO_DISPLAY_REMOVED:
-                    synchronized (mWindowMap) {
-                        handleDisplayRemovedLocked(msg.arg1);
-                    }
-                    break;
-
-                case DO_DISPLAY_CHANGED:
-                    synchronized (mWindowMap) {
-                        handleDisplayChangedLocked(msg.arg1);
-                    }
-                    break;
-
                 case TAP_OUTSIDE_TASK: {
                     handleTapOutsideTask((DisplayContent)msg.obj, msg.arg1, msg.arg2);
                 }
@@ -5324,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) {
                 }
@@ -5350,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);
     }
 
@@ -6710,10 +6690,6 @@
     }
 
     public void onDisplayAdded(int displayId) {
-        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
-    }
-
-    public void handleDisplayAdded(int displayId) {
         synchronized (mWindowMap) {
             final Display display = mDisplayManager.getDisplay(displayId);
             if (display != null) {
@@ -6725,28 +6701,24 @@
     }
 
     public void onDisplayRemoved(int displayId) {
-        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0));
-    }
-
-    private void handleDisplayRemovedLocked(int displayId) {
-        final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
-        if (displayContent != null) {
-            displayContent.removeIfPossible();
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null) {
+                displayContent.removeIfPossible();
+            }
+            mAnimator.removeDisplayLocked(displayId);
+            mWindowPlacerLocked.requestTraversal();
         }
-        mAnimator.removeDisplayLocked(displayId);
-        mWindowPlacerLocked.requestTraversal();
     }
 
     public void onDisplayChanged(int displayId) {
-        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0));
-    }
-
-    private void handleDisplayChangedLocked(int displayId) {
-        final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
-        if (displayContent != null) {
-            displayContent.updateDisplayInfo();
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null) {
+                displayContent.updateDisplayInfo();
+            }
+            mWindowPlacerLocked.requestTraversal();
         }
-        mWindowPlacerLocked.requestTraversal();
     }
 
     @Override
@@ -7333,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_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 703518d..ba3be67 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -31,7 +31,6 @@
 namespace android {
 
 using hardware::hidl_vec;
-using hardware::Status;
 using hardware::thermal::V1_0::CoolingDevice;
 using hardware::thermal::V1_0::CpuUsage;
 using hardware::thermal::V1_0::IThermal;
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 e6e0242..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 =
@@ -619,7 +617,6 @@
             startSensorService();
             traceLog.traceEnd();
         }, START_SENSOR_SERVICE);
-
     }
 
     /**
@@ -647,14 +644,6 @@
         traceBeginAndSlog("StartWebViewUpdateService");
         mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
         traceEnd();
-
-        // Start receiving calls from HIDL services. Start in in a separate thread
-        // because it need to connect to SensorManager.
-        SystemServerInitThreadPool.get().submit(() -> {
-            traceBeginAndSlog(START_HIDL_SERVICES);
-            startHidlServices();
-            traceEnd();
-        }, START_HIDL_SERVICES);
     }
 
     /**
@@ -813,6 +802,15 @@
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
             traceEnd();
 
+            // Start receiving calls from HIDL services. Start in in a separate thread
+            // because it need to connect to SensorManager. This have to start
+            // after START_SENSOR_SERVICE is done.
+            SystemServerInitThreadPool.get().submit(() -> {
+                traceBeginAndSlog(START_HIDL_SERVICES);
+                startHidlServices();
+                traceEnd();
+            }, START_HIDL_SERVICES);
+
             if (!disableVrManager) {
                 traceBeginAndSlog("StartVrManagerService");
                 mSystemServiceManager.startService(VrManagerService.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/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 2624f0b..ed78175 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1023,10 +1023,10 @@
     }
 
     private void logError(int errorCode) {
-        mMetricsLog.log(new DhcpErrorEvent(mIfaceName, errorCode));
+        mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
     }
 
     private void logState(String name, int durationMs) {
-        mMetricsLog.log(new DhcpClientEvent(mIfaceName, name, durationMs));
+        mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
     }
 }
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index c670782..59e698c 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -720,7 +720,7 @@
     private void recordMetric(final int type) {
         if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
         final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
-        mMetricsLog.log(new IpManagerEvent(mInterfaceName, type, duration));
+        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
     }
 
     // For now: use WifiStateMachine's historical notion of provisioned.
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 20eac62..d13449a 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -426,7 +426,7 @@
 
     private void logEvent(int probeType, int errorCode) {
         int eventType = probeType | (errorCode & 0xff);
-        mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+        mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
     }
 
     private void logNudFailed(ProvisioningChange delta) {
@@ -434,7 +434,7 @@
         boolean isFromProbe = (duration < getProbeWakeLockDuration());
         boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
-        mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+        mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
     }
 
     // TODO: simplify the number of objects by making this extend Thread.
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index e6e2cb3..9356dac 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -46,7 +46,10 @@
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.FgThread;
 import com.android.server.SystemService;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -86,10 +89,35 @@
 
     private final CompanionDeviceManagerImpl mImpl;
     private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
+    private IDeviceIdleController mIdleController;
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
         mImpl = new CompanionDeviceManagerImpl();
+        mIdleController = IDeviceIdleController.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+        registerPackageMonitor();
+    }
+
+    private void registerPackageMonitor() {
+        new PackageMonitor() {
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                updateAssociations(
+                        as -> CollectionUtils.filter(as,
+                                a -> !Objects.equals(a.companionAppPackage, packageName)),
+                        getChangingUserId());
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                int userId = getChangingUserId();
+                if (!ArrayUtils.isEmpty(readAllAssociations(userId, packageName))) {
+                    updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
+                }
+            }
+
+        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
     }
 
     @Override
@@ -124,9 +152,9 @@
 
         @Override
         public List<String> getAssociations(String callingPackage) {
-            return ArrayUtils.map(
+            return CollectionUtils.map(
                     readAllAssociations(getUserId(), callingPackage),
-                    (a) -> a.deviceAddress);
+                    a -> a.deviceAddress);
         }
 
         @Override
@@ -178,43 +206,55 @@
             @Override
             public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
                 //TODO unbind
-                grantSpecialAccessPermissionsIfNeeded(packageName, userId);
+                updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
                 recordAssociation(packageName, deviceAddress);
             }
         };
     }
 
-    private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) {
-        final long identity = Binder.clearCallingIdentity();
-        final PackageInfo packageInfo;
-        try {
+    private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) {
+        PackageInfo packageInfo = getPackageInfo(packageName, userId);
+        if (packageInfo == null) {
+            return;
+        }
+
+        Binder.withCleanCallingIdentity(() -> {
             try {
-                packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
-                        packageName, PackageManager.GET_PERMISSIONS, userId);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(LOG_TAG, "Error granting special access permissions to package:"
-                        + packageName, e);
-                return;
-            }
-            if (ArrayUtils.contains(packageInfo.requestedPermissions,
-                    Manifest.permission.RUN_IN_BACKGROUND)) {
-                IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface(
-                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-                try {
-                    idleController.addPowerSaveWhitelistApp(packageName);
-                } catch (RemoteException e) {
-                    /* ignore - local call */
+                if (ArrayUtils.contains(packageInfo.requestedPermissions,
+                        Manifest.permission.RUN_IN_BACKGROUND)) {
+                    mIdleController.addPowerSaveWhitelistApp(packageInfo.packageName);
+                } else {
+                    mIdleController.removePowerSaveWhitelistApp(packageInfo.packageName);
                 }
+            } catch (RemoteException e) {
+                /* ignore - local call */
             }
+
+            NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
             if (ArrayUtils.contains(packageInfo.requestedPermissions,
                     Manifest.permission.USE_DATA_IN_BACKGROUND)) {
-                NetworkPolicyManager.from(getContext()).addUidPolicy(
+                networkPolicyManager.addUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            } else {
+                networkPolicyManager.removeUidPolicy(
                         packageInfo.applicationInfo.uid,
                         NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
             }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        });
+    }
+
+    @Nullable
+    private PackageInfo getPackageInfo(String packageName, int userId) {
+        return Binder.withCleanCallingIdentity(() -> {
+            try {
+                return getContext().getPackageManager().getPackageInfoAsUser(
+                        packageName, PackageManager.GET_PERMISSIONS, userId);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + packageName, e);
+                return null;
+            }
+        });
     }
 
     private void recordAssociation(String priviledgedPackage, String deviceAddress) {
@@ -222,13 +262,16 @@
                 new Association(getUserId(), deviceAddress, priviledgedPackage)));
     }
 
-    private void updateAssociations(
-            Function<ArrayList<Association>, ArrayList<Association>> update) {
-        final int userId = getUserId();
+    private void updateAssociations(Function<ArrayList<Association>, List<Association>> update) {
+        updateAssociations(update, getUserId());
+    }
+
+    private void updateAssociations(Function<ArrayList<Association>, List<Association>> update,
+            int userId) {
         final AtomicFile file = getStorageFileForUser(userId);
         synchronized (file) {
             final ArrayList<Association> old = readAllAssociations(userId);
-            final ArrayList<Association> associations = update.apply(old);
+            final List<Association> associations = update.apply(old);
             if (Objects.equals(old, associations)) return;
 
             file.write((out) -> {
@@ -239,7 +282,7 @@
                     xml.startDocument(null, true);
                     xml.startTag(null, XML_TAG_ASSOCIATIONS);
 
-                    for (int i = 0; i < ArrayUtils.size(associations); i++) {
+                    for (int i = 0; i < CollectionUtils.size(associations); i++) {
                         Association association = associations.get(i);
                         xml.startTag(null, XML_TAG_ASSOCIATION)
                             .attribute(null, XML_ATTR_PACKAGE, association.companionAppPackage)
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 27b9a88..af44264 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,11 +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);
-        } 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() {
@@ -182,14 +178,15 @@
                 IMPORTANCE_LOW);
     }
 
-    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, String... channelIds)
+    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
+            String... channelIds)
             throws Exception {
         XmlSerializer serializer = new FastXmlSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
         serializer.startDocument(null, true);
         serializer.startTag(null, "ranking");
-        mHelper.writeXml(serializer, false);
+        mHelper.writeXml(serializer, forBackup);
         serializer.endTag(null, "ranking");
         serializer.endDocument();
         serializer.flush();
@@ -200,9 +197,18 @@
         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());
+        assertEquals(expected.getDescription(), actual.getDescription());
         assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
         assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
         assertEquals(expected.getImportance(), actual.getImportance());
@@ -278,6 +284,7 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        channel2.setDescription("descriptions for all");
         channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
@@ -287,34 +294,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, channel1.getId(), channel2.getId(),
-                NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+        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});
 
-        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())) {
@@ -337,23 +338,58 @@
     }
 
     @Test
-    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+    public void testChannelXml_backup() throws Exception {
+        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
         NotificationChannel channel1 =
-                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+        NotificationChannel channel3 =
+                new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
+        channel3.setGroup(ncg.getId());
 
-        mHelper.createNotificationChannel(pkg, uid, channel1, 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);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
-                NotificationChannel.DEFAULT_CHANNEL_ID);
+        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(),
+                channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+        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);
+        mHelper.readXml(parser, true);
 
-        final NotificationChannel updated = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, 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
+    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
+
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
+                NotificationChannel.DEFAULT_CHANNEL_ID);
+
+        loadStreamXml(baos);
+
+        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());
@@ -364,34 +400,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, 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();
@@ -400,30 +432,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
@@ -433,16 +504,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
@@ -453,17 +524,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
@@ -474,7 +545,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 =
@@ -482,10 +553,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
@@ -496,17 +567,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
@@ -517,17 +588,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
@@ -538,17 +609,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
@@ -558,16 +629,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
@@ -580,7 +651,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 =
@@ -590,17 +661,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
@@ -618,10 +687,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());
@@ -645,10 +714,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());
@@ -668,16 +737,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());
     }
@@ -697,14 +766,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())) {
@@ -713,7 +782,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())) {
@@ -730,35 +799,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) {
             // :)
@@ -772,24 +841,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) {
@@ -804,26 +873,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
@@ -832,9 +901,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
@@ -844,13 +913,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
@@ -866,28 +935,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
@@ -895,52 +964,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
@@ -949,7 +1018,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) {
         }
@@ -958,45 +1027,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) {
@@ -1022,19 +1091,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) {
@@ -1047,14 +1116,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());
     }
@@ -1074,7 +1143,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);
@@ -1082,7 +1151,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/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index a9b2ae5..f8d105e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -32,6 +32,8 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
 
+import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
+import static com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -58,6 +60,7 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.IUidObserver;
@@ -95,6 +98,7 @@
 import android.util.Log;
 import android.util.TrustedTime;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -120,10 +124,12 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -160,6 +166,8 @@
     private static final String TEST_IFACE = "test0";
     private static final String TEST_SSID = "AndroidAP";
 
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
     private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
 
     /**
@@ -186,6 +194,8 @@
     private @Mock PackageManager mPackageManager;
     private @Mock IPackageManager mIpm;
 
+    private static ActivityManagerInternal mActivityManagerInternal;
+
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
 
@@ -222,6 +232,7 @@
         final UsageStatsManagerInternal usageStats =
                 addLocalServiceMock(UsageStatsManagerInternal.class);
         when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
+        mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
     }
 
     @Before
@@ -961,6 +972,75 @@
         }
     }
 
+    @Test
+    public void testOnUidStateChanged_notifyAMS() throws Exception {
+        final long procStateSeq = 222;
+        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE,
+                procStateSeq);
+        verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(
+                new PrintWriter(outputStream), " ");
+        mService.mObservedHistory.dumpUL(writer);
+        writer.flush();
+        assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
+                outputStream.toString().trim());
+    }
+
+    @Test
+    public void testProcStateHistory() {
+        // Verify dump works correctly with no elements added.
+        verifyProcStateHistoryDump(0);
+
+        // Add items upto half of the max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+        // Add items upto the max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
+
+        // Add more items than max capacity and verify that dump works correctly.
+        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+    }
+
+    private void verifyProcStateHistoryDump(int count) {
+        final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(
+                new PrintWriter(outputStream), " ");
+
+        if (count == 0) {
+            // Verify with no uid info written to history.
+            history.dumpUL(writer);
+            writer.flush();
+            assertEquals("When no uid info is there, dump should contain NONE",
+                    "NONE", outputStream.toString().trim());
+            return;
+        }
+
+        int uid = 111;
+        long procStateSeq = 222;
+        // Add count items and verify dump works correctly.
+        for (int i = 0; i < count; ++i) {
+            uid++;
+            procStateSeq++;
+            history.addProcStateSeqUL(uid, procStateSeq);
+        }
+        history.dumpUL(writer);
+        writer.flush();
+        final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
+        // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
+        final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
+                ? count : MAX_PROC_STATE_SEQ_HISTORY;
+        assertEquals(expectedCount, uidsDump.length);
+        for (int i = 0; i < expectedCount; ++i) {
+            assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
+            uid--;
+            procStateSeq--;
+        }
+    }
+
     private static long parseTime(String time) {
         final Time result = new Time();
         result.parse3339(time);
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
new file mode 100644
index 0000000..e7c91c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManagerInternal;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link ActivityManagerInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@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;
+    private ActivityManagerInternal mAmi;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mAms = new ActivityManagerService(mMockInjector);
+        mAmi = mAms.new LocalService();
+    }
+
+    @MediumTest
+    @Test
+    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);
+
+        // 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 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 556b218..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,6 +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;
@@ -35,21 +41,33 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+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;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
 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;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,9 +75,12 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 
 /**
@@ -80,64 +101,165 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActivityManagerServiceTest {
+    private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
+
     private static final int TEST_UID = 111;
 
+    private static final long TEST_PROC_STATE_SEQ1 = 555;
+    private static final long TEST_PROC_STATE_SEQ2 = 556;
+
+    private static final int[] UID_RECORD_CHANGES = {
+        UidRecord.CHANGE_PROCSTATE,
+        UidRecord.CHANGE_GONE,
+        UidRecord.CHANGE_GONE_IDLE,
+        UidRecord.CHANGE_IDLE,
+        UidRecord.CHANGE_ACTIVE
+    };
+
     @Mock private AppOpsService mAppOpsService;
 
+    private TestInjector mInjector;
+    private ActivityManagerService mAms;
+    private HandlerThread mHandlerThread;
+    private TestHandler mHandler;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new TestHandler(mHandlerThread.getLooper());
+        mInjector = new TestInjector();
+        mAms = new ActivityManagerService(mInjector);
     }
 
-    @Test
-    public void testIncrementProcStateSeqIfNeeded() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
-        final UidRecord uidRec = new UidRecord(TEST_UID);
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
 
-        assertEquals("Initially global seq counter should be 0", 0, ams.mProcStateSeqCounter);
-        assertEquals("Initially seq counter in uidRecord should be 0", 0, uidRec.curProcStateSeq);
+    @MediumTest
+    @Test
+    public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception {
+        final UidRecord uidRec = new UidRecord(TEST_UID);
+        uidRec.waitingForNetwork = true;
+        mAms.mActiveUids.put(TEST_UID, uidRec);
+
+        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;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(0, ams.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;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(1, ams.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.
-        ams.mProcStateSeqCounter = 42;
+        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;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(42, ams.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;
-        ams.incrementProcStateSeqIfNeeded(uidRec);
-        assertEquals(43, ams.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() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+    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));
         };
@@ -145,32 +267,44 @@
         // No change in uid state
         uidRec.setProcState = PROCESS_STATE_RECEIVER;
         uidRec.curProcState = PROCESS_STATE_RECEIVER;
-        assertFalse(errorMsg.apply(error2), ams.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), ams.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), ams.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), ams.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), ams.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), ams.shouldIncrementProcStateSeq(uidRec));
+        expectedBlockState = NETWORK_STATE_UNBLOCK;
+        assertEquals(errorMsg.apply(expectedBlockState),
+                expectedBlockState, mAms.getBlockStateForUid(uidRec));
     }
 
     /**
@@ -179,7 +313,6 @@
      */
     @Test
     public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         when(mAppOpsService.checkOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
 
@@ -195,7 +328,7 @@
         for (int i = 0; i < observers.length; ++i) {
             observers[i] = Mockito.mock(IUidObserver.Stub.class);
             when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
-            ams.registerUidObserver(observers[i], changesToObserve[i] /* which */,
+            mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
                     ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
 
             // When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
@@ -206,13 +339,8 @@
         }
 
         // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_*
-        final int[] changesForPendingUidRecords = {
-            UidRecord.CHANGE_PROCSTATE,
-            UidRecord.CHANGE_GONE,
-            UidRecord.CHANGE_GONE_IDLE,
-            UidRecord.CHANGE_IDLE,
-            UidRecord.CHANGE_ACTIVE
-        };
+        final int[] changesForPendingUidRecords = UID_RECORD_CHANGES;
+
         final int[] procStatesForPendingUidRecords = {
             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
             ActivityManager.PROCESS_STATE_NONEXISTENT,
@@ -228,10 +356,10 @@
             pendingChange.processState = procStatesForPendingUidRecords[i];
             pendingChange.procStateSeq = i;
             changeItems.put(changesForPendingUidRecords[i], pendingChange);
-            ams.mPendingUidChanges.add(pendingChange);
+            mAms.mPendingUidChanges.add(pendingChange);
         }
 
-        ams.dispatchUidsChanged();
+        mAms.dispatchUidsChanged();
         // Verify the required changes have been dispatched to observers.
         for (int i = 0; i < observers.length; ++i) {
             final int changeToObserve = changesToObserve[i];
@@ -310,11 +438,10 @@
      */
     @Test
     public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
         final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
 
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        ams.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
+        mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
                 ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
         // When we invoke AMS.registerUidObserver, there are some interactions with observer
         // mock in RemoteCallbackList class. We don't want to test those interactions and
@@ -327,8 +454,8 @@
         changeItem.change = UidRecord.CHANGE_PROCSTATE;
         changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
         changeItem.procStateSeq = 111;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // First process state message is always delivered regardless of whether the process state
         // change is above or below the cutpoint (PROCESS_STATE_SERVICE).
         verify(observer).onUidStateChanged(TEST_UID,
@@ -336,15 +463,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also below cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is above cutpoint, so callback will be invoked with the
         // current process state change.
@@ -353,15 +480,15 @@
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is also above cutpoint, so no callback will be invoked.
         verifyNoMoreInteractions(observer);
 
         changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-        ams.mPendingUidChanges.add(changeItem);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.add(changeItem);
+        mAms.dispatchUidsChanged();
         // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
         // the current process state change is below cutpoint, so callback will be invoked with the
         // current process state change.
@@ -376,15 +503,8 @@
      */
     @Test
     public void testDispatchUidChanges_validateUidsUpdated() {
-        final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+        final int[] changesForPendingItems = UID_RECORD_CHANGES;
 
-        final int[] changesForPendingItems = {
-            UidRecord.CHANGE_PROCSTATE,
-            UidRecord.CHANGE_GONE,
-            UidRecord.CHANGE_GONE_IDLE,
-            UidRecord.CHANGE_IDLE,
-            UidRecord.CHANGE_ACTIVE
-        };
         final int[] procStatesForPendingItems = {
             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
             ActivityManager.PROCESS_STATE_CACHED_EMPTY,
@@ -404,20 +524,20 @@
 
         // Verify that when there no observers listening to uid state changes, then there will
         // be no changes to validateUids.
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
         assertEquals("No observers registered, so validateUids should be empty",
-                0, ams.mValidateUids.size());
+                0, mAms.mValidateUids.size());
 
         final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        ams.registerUidObserver(observer, 0, 0, null);
+        mAms.registerUidObserver(observer, 0, 0, null);
         // Verify that when observers are registered, then validateUids is correctly updated.
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
-            final UidRecord validateUidRecord = ams.mValidateUids.get(item.uid);
+            final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
             if (item.change == UidRecord.CHANGE_GONE || item.change == UidRecord.CHANGE_GONE_IDLE) {
                 assertNull("validateUidRecord should be null since the change is either "
                         + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -442,16 +562,218 @@
 
         // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
         // will be removed from validateUids.
-        assertNotEquals("validateUids should not be empty", 0, ams.mValidateUids.size());
+        assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
             // distribution for this assignment.
             item.change = (i % 2) == 0 ? UidRecord.CHANGE_GONE_IDLE : UidRecord.CHANGE_GONE;
         }
-        ams.mPendingUidChanges.addAll(pendingItemsForUids);
-        ams.dispatchUidsChanged();
-        assertEquals("validateUids should be empty, validateUids: " + ams.mValidateUids,
-                0, ams.mValidateUids.size());
+        mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+        mAms.dispatchUidsChanged();
+        assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids,
+                0, mAms.mValidateUids.size());
+    }
+
+    @Test
+    public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Verify with no pending changes for TEST_UID.
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
+
+        // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
+        // expected.
+        final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+        uidRecord.pendingChange = changeItem;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
+        verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+
+        // Use "null" uidRecord to make sure there is no crash.
+        // TODO: currently it crashes, uncomment after fixing it.
+        // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+    }
+
+    private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
+        // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+            // Reset lastProcStateSeqDispatchToObservers after every test.
+            uidRecord.lastDispatchedProcStateSeq = 0;
+            mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+            // Verify there is no effect on curProcStateSeq.
+            assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
+            if (changeToDispatch == UidRecord.CHANGE_GONE
+                    || changeToDispatch == UidRecord.CHANGE_GONE_IDLE) {
+                // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers is not updated.
+                assertNotEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            } else {
+                // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
+                // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
+                assertEquals(uidRecord.curProcStateSeq,
+                        uidRecord.lastDispatchedProcStateSeq);
+            }
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
+        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        final int expectedProcState = PROCESS_STATE_SERVICE;
+        uidRecord.setProcState = expectedProcState;
+        uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+        // Test with no pending uid records.
+        for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+            final int changeToDispatch = UID_RECORD_CHANGES[i];
+
+            // Reset the current state
+            mHandler.reset();
+            uidRecord.pendingChange = null;
+            mAms.mPendingUidChanges.clear();
+
+            mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+
+            // Verify that UidRecord.pendingChange is updated correctly.
+            assertNotNull(uidRecord.pendingChange);
+            assertEquals(TEST_UID, uidRecord.pendingChange.uid);
+            assertEquals(expectedProcState, uidRecord.pendingChange.processState);
+            assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
+
+            // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
+            mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+        }
+    }
+
+    @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
+
+        private Set<Integer> mMsgsHandled = new HashSet<>();
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mMsgsHandled.add(msg.what);
+        }
+
+        public void waitForMessage(int msg) {
+            final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS;
+            while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) {
+                SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS);
+            }
+            if (!mMsgsHandled.contains(msg)) {
+                fail("Timed out waiting for the message to be handled, msg: " + msg);
+            }
+        }
+
+        public void reset() {
+            mMsgsHandled.clear();
+        }
+    }
+
+    private class TestInjector extends Injector {
+        private boolean mRestricted = true;
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        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..4121447 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/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 86f4a01..2729795 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -92,9 +92,6 @@
     // Cookie sent for usb hal death notification.
     private static final int USB_HAL_DEATH_COOKIE = 1000;
 
-    // Usb hal service name.
-    private static String sServiceName = "usb_hal";
-
     // Used as the key while sending the bundle to Main thread.
     private static final String PORT_INFO = "port_info";
 
@@ -499,16 +496,15 @@
             }
 
             try {
-                mProxy = IUsb.getService(sServiceName);
+                mProxy = IUsb.getService();
                 mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
                 mProxy.setCallback(mHALCallback);
                 mProxy.queryPortStatus();
             } catch (NoSuchElementException e) {
-                logAndPrintException(pw, sServiceName + " not found."
+                logAndPrintException(pw, "connectToProxy: usb hal service not found."
                         + " Did the service fail to start?", e);
             } catch (RemoteException e) {
-                logAndPrintException(pw, sServiceName
-                        + " connectToProxy: Service not responding", e);
+                logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
             }
         }
     }
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 aa55860..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());
@@ -5054,26 +5054,21 @@
        public void onReceiveUssdResponseFailed(String request, int failureCode) {};
     }
 
-    /* <p>Requires permission:
-     * @link android.Manifest.permission#CALL_PHONE}
+    /**
+     * Sends an Unstructured Supplementary Service Data (USSD) request to the cellular network and
+     * informs the caller of the response via {@code callback}.
+     * <p>Carriers define USSD codes which can be sent by the user to request information such as
+     * the user's current data balance or minutes balance.
+     * <p>Requires permission:
+     * {@link android.Manifest.permission#CALL_PHONE}
      * @param ussdRequest the USSD command to be executed.
-     * @param wrappedCallback receives a callback result.
+     * @param callback called by the framework to inform the caller of the result of executing the
+     *                 USSD request (see {@link OnReceiveUssdResponseCallback}).
+     * @param handler the {@link Handler} to run the request on.
      */
     @RequiresPermission(android.Manifest.permission.CALL_PHONE)
     public void sendUssdRequest(String ussdRequest,
                                 final OnReceiveUssdResponseCallback callback, Handler handler) {
-        sendUssdRequest(ussdRequest, getSubId(), callback, handler);
-    }
-
-   /* <p>Requires permission:
-    * @link android.Manifest.permission#CALL_PHONE}
-    * @param subId The subscription to use.
-    * @param ussdRequest the USSD command to be executed.
-    * @param wrappedCallback receives a callback result.
-    */
-    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
-    public void sendUssdRequest(String ussdRequest, int subId,
-                                final OnReceiveUssdResponseCallback callback, Handler handler) {
         checkNotNull(callback, "OnReceiveUssdResponseCallback cannot be null.");
 
         ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@@ -5095,7 +5090,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.handleUssdRequest(subId, ussdRequest, wrappedCallback);
+                telephony.handleUssdRequest(mSubId, ussdRequest, wrappedCallback);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
@@ -5646,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:
@@ -5654,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);
@@ -6242,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:
@@ -6256,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);
@@ -6271,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:
@@ -6285,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/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 48861bd..d819b96 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -26,6 +26,11 @@
 import static com.android.server.connectivity.MetricsTestUtil.b;
 import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
 
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
@@ -47,6 +52,135 @@
 public class IpConnectivityEventBuilderTest extends TestCase {
 
     @SmallTest
+    public void testLinkLayerInferrence() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(IpReachabilityEvent.class),
+                anInt(IpReachabilityEvent.NUD_FAILED));
+
+        String want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.netId = 123;
+        ev.transports = 3; // transports have priority for inferrence of link layer
+        ev.ifname = "wlan0";
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", MULTIPLE),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 3",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.transports = 1;
+        ev.ifname = null;
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", CELLULAR),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 1",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.transports = 0;
+        ev.ifname = "not_inferred";
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"not_inferred\"",
+                "  link_layer: 0",
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.ifname = "bt-pan";
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", BLUETOOTH),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.ifname = "rmnet_ipa0";
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", CELLULAR),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+
+        ev.ifname = "wlan0";
+        want = joinLines(
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", WIFI),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2");
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
     public void testDefaultNetworkEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DefaultNetworkEvent.class),
@@ -86,7 +220,6 @@
     public void testDhcpClientEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DhcpClientEvent.class),
-                aString("wlan0"),
                 aString("SomeState"),
                 anInt(192));
 
@@ -100,7 +233,7 @@
                 "  transports: 0",
                 "  dhcp_event <",
                 "    duration_ms: 192",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "    state_transition: \"SomeState\"",
                 "  >",
                 ">",
@@ -113,7 +246,6 @@
     public void testDhcpErrorEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DhcpErrorEvent.class),
-                aString("wlan0"),
                 anInt(DhcpErrorEvent.L4_NOT_UDP));
 
         String want = joinLines(
@@ -126,7 +258,7 @@
                 "  transports: 0",
                 "  dhcp_event <",
                 "    duration_ms: 0",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "    error_code: 50397184",
                 "  >",
                 ">",
@@ -191,7 +323,6 @@
     public void testIpManagerEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(IpManagerEvent.class),
-                aString("wlan0"),
                 anInt(IpManagerEvent.PROVISIONING_OK),
                 aLong(5678));
 
@@ -205,7 +336,7 @@
                 "  transports: 0",
                 "  ip_provisioning_event <",
                 "    event_type: 1",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "    latency_ms: 5678",
                 "  >",
                 ">",
@@ -218,7 +349,6 @@
     public void testIpReachabilityEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(IpReachabilityEvent.class),
-                aString("wlan0"),
                 anInt(IpReachabilityEvent.NUD_FAILED));
 
         String want = joinLines(
@@ -231,7 +361,7 @@
                 "  transports: 0",
                 "  ip_reachability_event <",
                 "    event_type: 512",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "  >",
                 ">",
                 "version: 2");
@@ -272,7 +402,6 @@
     public void testValidationProbeEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(ValidationProbeEvent.class),
-                anInt(120),
                 aLong(40730),
                 anInt(ValidationProbeEvent.PROBE_HTTP),
                 anInt(204));
@@ -287,9 +416,6 @@
                 "  transports: 0",
                 "  validation_probe_event <",
                 "    latency_ms: 40730",
-                "    network_id <",
-                "      network_id: 120",
-                "    >",
                 "    probe_result: 204",
                 "    probe_type: 1",
                 "  >",
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 785e1ce..68786d0 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -48,7 +48,7 @@
 
 public class IpConnectivityMetricsTest extends TestCase {
     static final IpReachabilityEvent FAKE_EV =
-            new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
 
     @Mock Context mCtx;
     @Mock IIpConnectivityMetrics mMockService;
@@ -153,48 +153,58 @@
         apfStats.programUpdatesAll = 7;
         apfStats.programUpdatesAllowingMulticast = 3;
         apfStats.maxProgramSize = 2048;
+
+        ValidationProbeEvent validationEv = new ValidationProbeEvent();
+        validationEv.durationMs = 40730;
+        validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
+        validationEv.returnCode = 204;
+
         Parcelable[] events = {
-            new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
-            new DhcpClientEvent("wlan0", "SomeState", 192),
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
+            new DhcpClientEvent("SomeState", 192),
             new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
-            new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
-            new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
+            new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
+            validationEv,
             apfStats,
             new RaEvent(2000, 400, 300, -1, 1000, -1)
         };
 
         for (int i = 0; i < events.length; i++) {
-            logger.log(100 * (i + 1), events[i]);
+            ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+            ev.timestamp = 100 * (i + 1);
+            ev.ifname = "wlan0";
+            ev.data = events[i];
+            logger.log(ev);
         }
 
         String want = joinLines(
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 100",
                 "  transports: 0",
                 "  ip_reachability_event <",
                 "    event_type: 512",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 200",
                 "  transports: 0",
                 "  dhcp_event <",
                 "    duration_ms: 192",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "    state_transition: \"SomeState\"",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 300",
                 "  transports: 0",
@@ -213,34 +223,31 @@
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 400",
                 "  transports: 0",
                 "  ip_provisioning_event <",
                 "    event_type: 1",
-                "    if_name: \"wlan0\"",
+                "    if_name: \"\"",
                 "    latency_ms: 5678",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 500",
                 "  transports: 0",
                 "  validation_probe_event <",
                 "    latency_ms: 40730",
-                "    network_id <",
-                "      network_id: 120",
-                "    >",
                 "    probe_result: 204",
                 "    probe_type: 1",
                 "  >",
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 600",
                 "  transports: 0",
@@ -259,7 +266,7 @@
                 ">",
                 "events <",
                 "  if_name: \"\"",
-                "  link_layer: 0",
+                "  link_layer: 4",
                 "  network_id: 0",
                 "  time_ms: 700",
                 "  transports: 0",
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/aapt2/.clang-format b/tools/aapt2/.clang-format
index 71c5ef2..c91502a 100644
--- a/tools/aapt2/.clang-format
+++ b/tools/aapt2/.clang-format
@@ -1,3 +1,7 @@
 BasedOnStyle: Google
 ColumnLimit: 100
-
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
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) {