Merge "Update BiometricPrompt API naming"
diff --git a/Android.bp b/Android.bp
index c97ee3d..9077344 100644
--- a/Android.bp
+++ b/Android.bp
@@ -352,12 +352,8 @@
         "core/java/android/service/chooser/IChooserTargetResult.aidl",
         "core/java/android/service/resolver/IResolverRankerService.aidl",
         "core/java/android/service/resolver/IResolverRankerResult.aidl",
-        "core/java/android/service/textclassifier/IConversationActionsCallback.aidl",
-        "core/java/android/service/textclassifier/ITextClassificationCallback.aidl",
+        "core/java/android/service/textclassifier/ITextClassifierCallback.aidl",
         "core/java/android/service/textclassifier/ITextClassifierService.aidl",
-        "core/java/android/service/textclassifier/ITextLanguageCallback.aidl",
-        "core/java/android/service/textclassifier/ITextLinksCallback.aidl",
-        "core/java/android/service/textclassifier/ITextSelectionCallback.aidl",
         "core/java/android/service/attention/IAttentionService.aidl",
         "core/java/android/service/attention/IAttentionCallback.aidl",
         "core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl",
diff --git a/api/current.txt b/api/current.txt
index 8852a9e..9fad6ca 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4447,7 +4447,7 @@
 
   public final class AutomaticZenRule implements android.os.Parcelable {
     ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
-    ctor public AutomaticZenRule(String, android.content.ComponentName, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, int, boolean);
+    ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
     method public int describeContents();
     method public android.net.Uri getConditionId();
@@ -14113,14 +14113,14 @@
   public class HardwareRenderer {
     ctor public HardwareRenderer();
     method public void clearContent();
-    method public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
+    method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
     method public void destroy();
     method public boolean isOpaque();
     method public void notifyFramePending();
     method public void setContentRoot(@Nullable android.graphics.RenderNode);
     method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setLightSourceGeometry(float, float, float, float);
-    method public void setName(String);
+    method public void setName(@NonNull String);
     method public void setOpaque(boolean);
     method public void setStopped(boolean);
     method public void setSurface(@Nullable android.view.Surface);
@@ -14132,9 +14132,9 @@
   }
 
   public final class HardwareRenderer.FrameRenderRequest {
-    method public android.graphics.HardwareRenderer.FrameRenderRequest setFrameCommitCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
-    method public android.graphics.HardwareRenderer.FrameRenderRequest setVsyncTime(long);
-    method public android.graphics.HardwareRenderer.FrameRenderRequest setWaitForPresent(boolean);
+    method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setFrameCommitCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
+    method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setVsyncTime(long);
+    method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setWaitForPresent(boolean);
     method public int syncAndDraw();
   }
 
@@ -14953,8 +14953,8 @@
 
   public final class RenderNode {
     ctor public RenderNode(@Nullable String);
-    method public android.graphics.RecordingCanvas beginRecording(int, int);
-    method public android.graphics.RecordingCanvas beginRecording();
+    method @NonNull public android.graphics.RecordingCanvas beginRecording(int, int);
+    method @NonNull public android.graphics.RecordingCanvas beginRecording();
     method public long computeApproximateMemoryUsage();
     method public void discardDisplayList();
     method public void endRecording();
@@ -15007,7 +15007,7 @@
     method public boolean setPivotX(float);
     method public boolean setPivotY(float);
     method public boolean setPosition(int, int, int, int);
-    method public boolean setPosition(android.graphics.Rect);
+    method public boolean setPosition(@NonNull android.graphics.Rect);
     method public boolean setProjectBackwards(boolean);
     method public boolean setProjectionReceiver(boolean);
     method public boolean setRotationX(float);
@@ -15446,38 +15446,38 @@
     method public float getGradientCenterY();
     method public float getGradientRadius();
     method public int getGradientType();
-    method public int getInnerRadius();
+    method @Px public int getInnerRadius();
     method public float getInnerRadiusRatio();
     method public int getOpacity();
     method public android.graphics.drawable.GradientDrawable.Orientation getOrientation();
     method public int getShape();
-    method public int getThickness();
+    method @Px public int getThickness();
     method public float getThicknessRatio();
     method public boolean getUseLevel();
     method public void setAlpha(int);
     method public void setColor(@ColorInt int);
     method public void setColor(@Nullable android.content.res.ColorStateList);
     method public void setColorFilter(@Nullable android.graphics.ColorFilter);
-    method public void setColors(@ColorInt int[]);
-    method public void setColors(@ColorInt int[], @Nullable float[]);
+    method public void setColors(@Nullable @ColorInt int[]);
+    method public void setColors(@Nullable @ColorInt int[], @Nullable float[]);
     method public void setCornerRadii(@Nullable float[]);
     method public void setCornerRadius(float);
     method public void setDither(boolean);
     method public void setGradientCenter(float, float);
     method public void setGradientRadius(float);
     method public void setGradientType(int);
-    method public void setInnerRadius(int);
-    method public void setInnerRadiusRatio(float);
+    method public void setInnerRadius(@Px int);
+    method public void setInnerRadiusRatio(@FloatRange(from=0.0f, fromInclusive=false) float);
     method public void setOrientation(android.graphics.drawable.GradientDrawable.Orientation);
-    method public void setPadding(int, int, int, int);
+    method public void setPadding(@Px int, @Px int, @Px int, @Px int);
     method public void setShape(int);
     method public void setSize(int, int);
     method public void setStroke(int, @ColorInt int);
     method public void setStroke(int, android.content.res.ColorStateList);
     method public void setStroke(int, @ColorInt int, float, float);
     method public void setStroke(int, android.content.res.ColorStateList, float, float);
-    method public void setThickness(int);
-    method public void setThicknessRatio(float);
+    method public void setThickness(@Px int);
+    method public void setThicknessRatio(@FloatRange(from=0.0f, fromInclusive=false) float);
     method public void setUseLevel(boolean);
     field public static final int LINE = 2; // 0x2
     field public static final int LINEAR_GRADIENT = 0; // 0x0
@@ -15690,10 +15690,10 @@
   public class StateListDrawable extends android.graphics.drawable.DrawableContainer {
     ctor public StateListDrawable();
     method public void addState(int[], android.graphics.drawable.Drawable);
-    method public int findStateDrawableIndex(int[]);
+    method public int findStateDrawableIndex(@NonNull int[]);
     method public int getStateCount();
-    method public android.graphics.drawable.Drawable getStateDrawable(int);
-    method public int[] getStateSet(int);
+    method @Nullable public android.graphics.drawable.Drawable getStateDrawable(int);
+    method @NonNull public int[] getStateSet(int);
   }
 
   public class TransitionDrawable extends android.graphics.drawable.LayerDrawable implements android.graphics.drawable.Drawable.Callback {
@@ -22859,6 +22859,7 @@
     method public float getBearing();
     method public float getBearingAccuracyDegrees();
     method public long getElapsedRealtimeNanos();
+    method public long getElapsedRealtimeUncertaintyNanos();
     method public android.os.Bundle getExtras();
     method public double getLatitude();
     method public double getLongitude();
@@ -22871,6 +22872,7 @@
     method public boolean hasAltitude();
     method public boolean hasBearing();
     method public boolean hasBearingAccuracy();
+    method public boolean hasElapsedRealtimeUncertaintyNanos();
     method public boolean hasSpeed();
     method public boolean hasSpeedAccuracy();
     method public boolean hasVerticalAccuracy();
@@ -22886,6 +22888,7 @@
     method public void setBearing(float);
     method public void setBearingAccuracyDegrees(float);
     method public void setElapsedRealtimeNanos(long);
+    method public void setElapsedRealtimeUncertaintyNanos(long);
     method public void setExtras(android.os.Bundle);
     method public void setLatitude(double);
     method public void setLongitude(double);
@@ -23053,7 +23056,7 @@
     ctor public AudioAttributes.Builder();
     ctor public AudioAttributes.Builder(android.media.AudioAttributes);
     method public android.media.AudioAttributes build();
-    method public android.media.AudioAttributes.Builder setAllowCapture(boolean);
+    method @NonNull public android.media.AudioAttributes.Builder setAllowCapture(boolean);
     method public android.media.AudioAttributes.Builder setContentType(int);
     method public android.media.AudioAttributes.Builder setFlags(int);
     method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
@@ -23392,11 +23395,11 @@
 
   public static final class AudioPlaybackCaptureConfiguration.Builder {
     ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection);
-    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
-    method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
-    method public android.media.AudioPlaybackCaptureConfiguration build();
-    method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
-    method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(@NonNull android.media.AudioAttributes);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration build();
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(@NonNull android.media.AudioAttributes);
   }
 
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
@@ -23497,7 +23500,7 @@
     ctor public AudioRecord.Builder();
     method public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
     method public android.media.AudioRecord.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
-    method public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
+    method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
     method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
     method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
   }
@@ -25137,7 +25140,7 @@
     field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
   }
 
-  public static class MediaItem2.Builder {
+  public static final class MediaItem2.Builder {
     ctor public MediaItem2.Builder();
     method @NonNull public android.media.MediaItem2 build();
     method @NonNull public android.media.MediaItem2.Builder setEndPosition(long);
@@ -41869,26 +41872,26 @@
 
   public static class ZenPolicy.Builder {
     ctor public ZenPolicy.Builder();
-    method public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
-    method public android.service.notification.ZenPolicy.Builder allowAllSounds();
-    method public android.service.notification.ZenPolicy.Builder allowCalls(int);
-    method public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
-    method public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
-    method public android.service.notification.ZenPolicy.Builder allowMessages(int);
-    method public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
-    method public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
-    method public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
-    method public android.service.notification.ZenPolicy build();
-    method public android.service.notification.ZenPolicy.Builder disallowAllSounds();
-    method public android.service.notification.ZenPolicy.Builder hideAllVisualEffects();
-    method public android.service.notification.ZenPolicy.Builder showAllVisualEffects();
-    method public android.service.notification.ZenPolicy.Builder showBadges(boolean);
-    method public android.service.notification.ZenPolicy.Builder showFullScreenIntent(boolean);
-    method public android.service.notification.ZenPolicy.Builder showInAmbientDisplay(boolean);
-    method public android.service.notification.ZenPolicy.Builder showInNotificationList(boolean);
-    method public android.service.notification.ZenPolicy.Builder showLights(boolean);
-    method public android.service.notification.ZenPolicy.Builder showPeeking(boolean);
-    method public android.service.notification.ZenPolicy.Builder showStatusBarIcons(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
+    method @NonNull public android.service.notification.ZenPolicy build();
+    method @NonNull public android.service.notification.ZenPolicy.Builder disallowAllSounds();
+    method @NonNull public android.service.notification.ZenPolicy.Builder hideAllVisualEffects();
+    method @NonNull public android.service.notification.ZenPolicy.Builder showAllVisualEffects();
+    method @NonNull public android.service.notification.ZenPolicy.Builder showBadges(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showFullScreenIntent(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showInAmbientDisplay(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showInNotificationList(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showLights(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showPeeking(boolean);
+    method @NonNull public android.service.notification.ZenPolicy.Builder showStatusBarIcons(boolean);
   }
 
 }
@@ -51972,6 +51975,7 @@
     method public int getStableInsetRight();
     method public int getStableInsetTop();
     method @NonNull public android.graphics.Insets getStableInsets();
+    method @NonNull public android.graphics.Insets getSystemGestureInsets();
     method public int getSystemWindowInsetBottom();
     method public int getSystemWindowInsetLeft();
     method public int getSystemWindowInsetRight();
@@ -51993,6 +51997,7 @@
     method @NonNull public android.view.WindowInsets build();
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
+    method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
   }
 
@@ -56786,7 +56791,7 @@
     method public boolean isFillViewport();
     method public boolean isSmoothScrollingEnabled();
     method public boolean pageScroll(int);
-    method public void scrollToDescendant(android.view.View);
+    method public void scrollToDescendant(@NonNull android.view.View);
     method public void setBottomEdgeEffectColor(@ColorInt int);
     method public void setEdgeEffectColor(@ColorInt int);
     method public void setFillViewport(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index 752640b..7518bb7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25,6 +25,7 @@
     field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
     field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
     field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
+    field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
     field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
     field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
@@ -163,6 +164,7 @@
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
     field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
+    field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
     field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
@@ -1413,7 +1415,7 @@
     field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
     field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
     field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
-    field public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
+    field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
     field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
     field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
@@ -6763,15 +6765,15 @@
     method public static android.view.textclassifier.TextClassifier getDefaultTextClassifierImplementation(@NonNull android.content.Context);
     method @Deprecated public final android.view.textclassifier.TextClassifier getLocalTextClassifier();
     method @Nullable public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onClassifyText(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassification.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
-    method public void onCreateTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext, @NonNull android.view.textclassifier.TextClassificationSessionId);
-    method public void onDestroyTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationSessionId);
-    method public void onDetectLanguage(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLanguage.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
-    method public abstract void onGenerateLinks(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLinks.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
-    method @Deprecated public void onSelectionEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.SelectionEvent);
-    method public void onSuggestConversationActions(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.ConversationActions.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
-    method public abstract void onSuggestSelection(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextSelection.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
-    method public void onTextClassifierEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassifierEvent);
+    method @MainThread public abstract void onClassifyText(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassification.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
+    method @MainThread public void onCreateTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext, @NonNull android.view.textclassifier.TextClassificationSessionId);
+    method @MainThread public void onDestroyTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationSessionId);
+    method @MainThread public void onDetectLanguage(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLanguage.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
+    method @MainThread public abstract void onGenerateLinks(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLinks.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
+    method @Deprecated @MainThread public void onSelectionEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.SelectionEvent);
+    method @MainThread public void onSuggestConversationActions(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.ConversationActions.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
+    method @MainThread public abstract void onSuggestSelection(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextSelection.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
+    method @MainThread public void onTextClassifierEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassifierEvent);
     field public static final String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService";
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 684524e..c070171 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -523,6 +523,7 @@
     method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+    field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
   }
 
   public interface RoleManagerCallback {
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index da72002..7298da6 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -219,6 +219,7 @@
         "tests/anomaly/AnomalyTracker_test.cpp",
         "tests/ConfigManager_test.cpp",
         "tests/external/puller_util_test.cpp",
+        "tests/external/IncidentReportArgs_test.cpp",
         "tests/external/StatsPuller_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a983b27..5968fa8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1568,7 +1568,7 @@
         ROLLBACK_INITIATE = 1;
         ROLLBACK_SUCCESS = 2;
         ROLLBACK_FAILURE = 3;
-        ROLLBACK_ROOT_TRIGGERED = 4;
+        ROLLBACK_BOOT_TRIGGERED = 4;
     }
     optional RollbackType rollback_type = 1;
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 5c6d548..0e91f52 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -324,6 +324,12 @@
     EXPLICIT = 1;
   }
   optional Destination dest = 2;
+
+  // Package name of the incident report receiver.
+  optional string receiver_pkg = 3;
+
+  // Class name of the incident report receiver.
+  optional string receiver_cls = 4;
 }
 
 message PerfettoDetails {
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index 0ed2d75..7c2d242 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -162,6 +162,10 @@
     }
     incidentReport.setDest(dest);
 
+    incidentReport.setReceiverPkg(config.receiver_pkg());
+
+    incidentReport.setReceiverCls(config.receiver_cls());
+
     sp<IIncidentManager> service = interface_cast<IIncidentManager>(
             defaultServiceManager()->getService(android::String16("incident")));
     if (service == nullptr) {
diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
new file mode 100644
index 0000000..c170b12
--- /dev/null
+++ b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
@@ -0,0 +1,72 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/os/IncidentReportArgs.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(IncidentReportArgsTest, testSerialization) {
+    IncidentReportArgs args;
+    args.setAll(0);
+    args.addSection(1000);
+    args.addSection(1001);
+
+    vector<uint8_t> header1;
+    header1.push_back(0x1);
+    header1.push_back(0x2);
+    vector<uint8_t> header2;
+    header1.push_back(0x22);
+    header1.push_back(0x33);
+
+    args.addHeader(header1);
+    args.addHeader(header2);
+
+    args.setDest(1);
+
+    args.setReceiverPkg("com.android.os");
+    args.setReceiverCls("com.android.os.Receiver");
+
+    Parcel out;
+    status_t err = args.writeToParcel(&out);
+    EXPECT_EQ(NO_ERROR, err);
+
+    out.setDataPosition(0);
+
+    IncidentReportArgs args2;
+    err = args2.readFromParcel(&out);
+    EXPECT_EQ(NO_ERROR, err);
+
+    EXPECT_EQ(0, args2.all());
+    set<int> sections;
+    sections.insert(1000);
+    sections.insert(1001);
+    EXPECT_EQ(sections, args2.sections());
+    EXPECT_EQ(1, args2.dest());
+
+    EXPECT_EQ(String16("com.android.os"), args2.receiverPkg());
+    EXPECT_EQ(String16("com.android.os.Receiver"), args2.receiverCls());
+
+    vector<vector<uint8_t>> headers;
+    headers.push_back(header1);
+    headers.push_back(header2);
+    EXPECT_EQ(headers, args2.headers());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7d828d8..92db23b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -276,6 +276,7 @@
     public abstract boolean isActivityStartsLoggingEnabled();
     /** Returns true if the background activity starts is enabled. */
     public abstract boolean isBackgroundActivityStartsEnabled();
+    public abstract boolean isPackageNameWhitelistedForBgActivityStarts(String packageName);
     public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
 
     /** Input dispatch timeout to a window, start the ANR process. */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b654258..08239a1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -332,7 +332,6 @@
     String[] mInstrumentedSplitAppDirs = null;
     String mInstrumentedLibDir = null;
     boolean mSystemThread = false;
-    boolean mJitEnabled = false;
     boolean mSomeActivitiesChanged = false;
     boolean mUpdatingSystemConfig = false;
     /* package */ boolean mHiddenApiWarningShown = false;
@@ -1696,7 +1695,6 @@
         public static final int SUICIDE                 = 130;
         @UnsupportedAppUsage
         public static final int REMOVE_PROVIDER         = 131;
-        public static final int ENABLE_JIT              = 132;
         public static final int DISPATCH_PACKAGE_BROADCAST = 133;
         @UnsupportedAppUsage
         public static final int SCHEDULE_CRASH          = 134;
@@ -1746,7 +1744,6 @@
                     case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
                     case SUICIDE: return "SUICIDE";
                     case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
-                    case ENABLE_JIT: return "ENABLE_JIT";
                     case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
                     case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
                     case DUMP_HEAP: return "DUMP_HEAP";
@@ -1858,9 +1855,6 @@
                     completeRemoveProvider((ProviderRefCount)msg.obj);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
-                case ENABLE_JIT:
-                    ensureJitEnabled();
-                    break;
                 case DISPATCH_PACKAGE_BROADCAST:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
                     handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
@@ -1996,7 +1990,6 @@
             if (stopProfiling) {
                 mProfiler.stopProfiling();
             }
-            ensureJitEnabled();
             return false;
         }
     }
@@ -2330,13 +2323,6 @@
         }
     }
 
-    void ensureJitEnabled() {
-        if (!mJitEnabled) {
-            mJitEnabled = true;
-            dalvik.system.VMRuntime.getRuntime().startJitCompilation();
-        }
-    }
-
     @UnsupportedAppUsage
     void scheduleGcIdler() {
         if (!mGcIdlerScheduled) {
@@ -3782,7 +3768,6 @@
                         ActivityManager.getService().serviceDoneExecuting(
                                 data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                     }
-                    ensureJitEnabled();
                 } catch (RemoteException ex) {
                     throw ex.rethrowFromSystemServer();
                 }
@@ -3896,7 +3881,6 @@
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
-                ensureJitEnabled();
             } catch (Exception e) {
                 if (!mInstrumentation.onException(s, e)) {
                     throw new RuntimeException(
@@ -6177,9 +6161,6 @@
             if (!data.restrictedBackupMode) {
                 if (!ArrayUtils.isEmpty(data.providers)) {
                     installContentProviders(app, data.providers);
-                    // For process that contains content providers, we want to
-                    // ensure that the JIT is enabled "at some point".
-                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                 }
             }
 
@@ -6812,12 +6793,6 @@
         sCurrentActivityThread = this;
         mSystemThread = system;
         if (!system) {
-            ViewRootImpl.addFirstDrawHandler(new Runnable() {
-                @Override
-                public void run() {
-                    ensureJitEnabled();
-                }
-            });
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                     UserHandle.myUserId());
             RuntimeInit.setApplicationObject(mAppThread.asBinder());
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 4a826d1..010a900 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -16,18 +16,15 @@
 
 package android.app;
 
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.NotificationManager.InterruptionFilter;
 import android.content.ComponentName;
-import android.content.Intent;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.notification.ZenPolicy;
 import android.service.notification.Condition;
-
-import com.android.internal.util.Preconditions;
+import android.service.notification.ZenPolicy;
 
 import java.util.Objects;
 
@@ -92,8 +89,9 @@
      *               action ({@link Condition#STATE_TRUE}).
      * @param enabled Whether the rule is enabled.
      */
-    public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
-            Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled) {
+    public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
+            @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
+            @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
         this.name = name;
         this.owner = owner;
         this.configurationActivity = configurationActivity;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index ad614b1..14c58e7 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -23,13 +23,7 @@
             "name": "FrameworksServicesTests",
             "options": [
                 {
-                    "include-filter": "com.android.server.appop.AppOpsUpgradeTest"
-                },
-                {
-                    "include-filter": "com.android.server.appop.AppOpsServiceTest"
-                },
-                {
-                    "include-filter": "com.android.server.appop.AppOpsActiveWatcherTest"
+                    "include-filter": "com.android.server.appop"
                 }
             ]
         }
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index c665cb23..f91d878 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -187,6 +187,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1;
 
     /**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d3b8e29..0715572 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2124,6 +2124,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES =
             "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index 1bdfd94..a1f2430 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -36,6 +36,8 @@
     private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>();
     private boolean mAll;
     private int mPrivacyPolicy;
+    private String mReceiverPkg;
+    private String mReceiverCls;
 
     /**
      * Construct an incident report args with no fields.
@@ -73,6 +75,10 @@
         }
 
         out.writeInt(mPrivacyPolicy);
+
+        out.writeString(mReceiverPkg);
+
+        out.writeString(mReceiverCls);
     }
 
     public void readFromParcel(Parcel in) {
@@ -91,6 +97,10 @@
         }
 
         mPrivacyPolicy = in.readInt();
+
+        mReceiverPkg = in.readString();
+
+        mReceiverCls = in.readString();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<IncidentReportArgs> CREATOR
@@ -126,6 +136,8 @@
         sb.append(mHeaders.size());
         sb.append(" headers), ");
         sb.append("privacy: ").append(mPrivacyPolicy);
+        sb.append("receiver pkg: ").append(mReceiverPkg);
+        sb.append("receiver cls: ").append(mReceiverCls);
         return sb.toString();
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 78c9ebd..ec9bdfd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11453,6 +11453,15 @@
                 "background_activity_starts_enabled";
 
         /**
+         * The packages temporarily whitelisted to be able so start activities from background.
+         * The list of packages is {@code ":"} colon delimited.
+         *
+         * @hide
+         */
+        public static final String BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST =
+                "background_activity_starts_package_names_whitelist";
+
+        /**
          * @hide
          * @see com.android.server.appbinding.AppBindingConstants
          */
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
index 40333bf..2814300 100644
--- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -52,6 +52,10 @@
 
     /**
      * The action for the intent used to define the content suggestions service.
+     *
+     * <p>To be supported, the service must also require the
+     *      * {@link android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE} permission so
+     *      * that other applications can not abuse it.
      */
     public static final String SERVICE_INTERFACE =
             "android.service.contentsuggestions.ContentSuggestionsService";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 74e6c6e..7ef40cd 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -17,6 +17,7 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.os.Parcel;
@@ -378,14 +379,14 @@
         /**
          * Builds the current ZenPolicy.
          */
-        public ZenPolicy build() {
+        public @NonNull ZenPolicy build() {
             return mZenPolicy.copy();
         }
 
         /**
          * Allows all notifications to bypass DND and unmutes all streams.
          */
-        public Builder allowAllSounds() {
+        public @NonNull Builder allowAllSounds() {
             for (int i = 0; i < mZenPolicy.mPriorityCategories.size(); i++) {
                 mZenPolicy.mPriorityCategories.set(i, STATE_ALLOW);
             }
@@ -401,7 +402,7 @@
          * {@link NotificationChannel#canBypassDnd can bypass DND}. If no channels can bypass DND,
          * the ringer stream is also muted.
          */
-        public Builder disallowAllSounds() {
+        public @NonNull Builder disallowAllSounds() {
             for (int i = 0; i < mZenPolicy.mPriorityCategories.size(); i++) {
                 mZenPolicy.mPriorityCategories.set(i, STATE_DISALLOW);
             }
@@ -413,7 +414,7 @@
         /**
          * Allows notifications intercepted by DND to show on all surfaces when DND is active.
          */
-        public Builder showAllVisualEffects() {
+        public @NonNull Builder showAllVisualEffects() {
             for (int i = 0; i < mZenPolicy.mVisualEffects.size(); i++) {
                 mZenPolicy.mVisualEffects.set(i, STATE_ALLOW);
             }
@@ -423,7 +424,7 @@
         /**
          * Disallows notifications intercepted by DND from showing when DND is active.
          */
-        public Builder hideAllVisualEffects() {
+        public @NonNull Builder hideAllVisualEffects() {
             for (int i = 0; i < mZenPolicy.mVisualEffects.size(); i++) {
                 mZenPolicy.mVisualEffects.set(i, STATE_DISALLOW);
             }
@@ -435,7 +436,7 @@
          * unset categories will default to the current applied policy.
          * @hide
          */
-        public Builder unsetPriorityCategory(@PriorityCategory int category) {
+        public @NonNull Builder unsetPriorityCategory(@PriorityCategory int category) {
             mZenPolicy.mPriorityCategories.set(category, STATE_UNSET);
 
             if (category == PRIORITY_CATEGORY_MESSAGES) {
@@ -452,7 +453,7 @@
          * unset effects will default to the current applied policy.
          * @hide
          */
-        public Builder unsetVisualEffect(@VisualEffect int effect) {
+        public @NonNull Builder unsetVisualEffect(@VisualEffect int effect) {
             mZenPolicy.mVisualEffects.set(effect, STATE_UNSET);
             return this;
         }
@@ -461,7 +462,7 @@
          * Whether to allow notifications with category {@link Notification#CATEGORY_REMINDER}
          * to play sounds and visually appear or to intercept them when DND is active.
          */
-        public Builder allowReminders(boolean allow) {
+        public @NonNull Builder allowReminders(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_REMINDERS,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -471,7 +472,7 @@
          * Whether to allow notifications with category {@link Notification#CATEGORY_EVENT}
          * to play sounds and visually appear or to intercept them when DND is active.
          */
-        public Builder allowEvents(boolean allow) {
+        public @NonNull Builder allowEvents(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_EVENTS,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -483,7 +484,7 @@
          * them when DND is active.
          * @param audienceType message senders that are allowed to bypass DND
          */
-        public Builder allowMessages(@PeopleType int audienceType) {
+        public @NonNull Builder allowMessages(@PeopleType int audienceType) {
             if (audienceType == STATE_UNSET) {
                 return unsetPriorityCategory(PRIORITY_CATEGORY_MESSAGES);
             }
@@ -507,7 +508,7 @@
          * them when DND is active.
          * @param audienceType callers that are allowed to bypass DND
          */
-        public Builder allowCalls(@PeopleType int audienceType) {
+        public @NonNull  Builder allowCalls(@PeopleType int audienceType) {
             if (audienceType == STATE_UNSET) {
                 return unsetPriorityCategory(PRIORITY_CATEGORY_CALLS);
             }
@@ -530,7 +531,7 @@
          * {@link Notification#CATEGORY_CALL} that have recently called
          * to play sounds and visually appear.
          */
-        public Builder allowRepeatCallers(boolean allow) {
+        public @NonNull Builder allowRepeatCallers(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_REPEAT_CALLERS,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -542,7 +543,7 @@
          * to play sounds and visually appear or to intercept them when DND is active.
          * Disallowing alarms will mute the alarm stream when DND is active.
          */
-        public Builder allowAlarms(boolean allow) {
+        public @NonNull Builder allowAlarms(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_ALARMS,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -553,7 +554,7 @@
          * appear or to intercept them when DND is active.
          * Disallowing media will mute the media stream when DND is active.
          */
-        public Builder allowMedia(boolean allow) {
+        public @NonNull Builder allowMedia(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_MEDIA,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -563,7 +564,7 @@
          * Whether to allow system sounds to play when DND is active.
          * Disallowing system sounds will mute the system stream when DND is active.
          */
-        public Builder allowSystem(boolean allow) {
+        public @NonNull Builder allowSystem(boolean allow) {
             mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_SYSTEM,
                     allow ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -573,7 +574,7 @@
          * Whether to allow {@link PriorityCategory} sounds to play when DND is active.
          * @hide
          */
-        public Builder allowCategory(@PriorityCategory int category, boolean allow) {
+        public @NonNull Builder allowCategory(@PriorityCategory int category, boolean allow) {
             switch (category) {
                 case PRIORITY_CATEGORY_ALARMS:
                     allowAlarms(allow);
@@ -601,7 +602,7 @@
          * Whether {@link Notification#fullScreenIntent full screen intents} that are intercepted
          * by DND are shown.
          */
-        public Builder showFullScreenIntent(boolean show) {
+        public @NonNull Builder showFullScreenIntent(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_FULL_SCREEN_INTENT,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -611,7 +612,7 @@
          * Whether {@link NotificationChannel#shouldShowLights() notification lights} from
          * notifications intercepted by DND are blocked.
          */
-        public Builder showLights(boolean show) {
+        public @NonNull Builder showLights(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_LIGHTS,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -620,7 +621,7 @@
         /**
          * Whether notifications intercepted by DND are prevented from peeking.
          */
-        public Builder showPeeking(boolean show) {
+        public @NonNull Builder showPeeking(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_PEEK,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -630,7 +631,7 @@
          * Whether notifications intercepted by DND are prevented from appearing in the status bar
          * on devices that support status bars.
          */
-        public Builder showStatusBarIcons(boolean show) {
+        public @NonNull Builder showStatusBarIcons(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_STATUS_BAR,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -640,7 +641,7 @@
          * Whether {@link NotificationChannel#canShowBadge() badges} from
          * notifications intercepted by DND are allowed on devices that support badging.
          */
-        public Builder showBadges(boolean show) {
+        public @NonNull Builder showBadges(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_BADGE,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -650,7 +651,7 @@
          * Whether notification intercepted by DND are prevented from appearing on ambient displays
          * on devices that support ambient display.
          */
-        public Builder showInAmbientDisplay(boolean show) {
+        public @NonNull Builder showInAmbientDisplay(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_AMBIENT,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -661,7 +662,7 @@
          * list views like the notification shade or lockscreen on devices that support those
          * views.
          */
-        public Builder showInNotificationList(boolean show) {
+        public @NonNull Builder showInNotificationList(boolean show) {
             mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_NOTIFICATION_LIST,
                     show ? STATE_ALLOW : STATE_DISALLOW);
             return this;
@@ -672,7 +673,7 @@
          * {@link VisualEffect}
          * @hide
          */
-        public Builder showVisualEffect(@VisualEffect int effect, boolean show) {
+        public @NonNull Builder showVisualEffect(@VisualEffect int effect, boolean show) {
             switch (effect) {
                 case VISUAL_EFFECT_FULL_SCREEN_INTENT:
                     showFullScreenIntent(show);
@@ -1001,7 +1002,7 @@
      * Makes deep copy of this ZenPolicy.
      * @hide
      */
-    public ZenPolicy copy() {
+    public @NonNull ZenPolicy copy() {
         final Parcel parcel = Parcel.obtain();
         try {
             writeToParcel(parcel, 0);
diff --git a/core/java/android/service/textclassifier/IConversationActionsCallback.aidl b/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
deleted file mode 100644
index c35d424..0000000
--- a/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.textclassifier;
-
-import android.view.textclassifier.ConversationActions;
-
-/**
- * Callback for a ConversationActions request.
- * @hide
- */
-oneway interface IConversationActionsCallback {
-    void onSuccess(in ConversationActions conversationActions);
-    void onFailure();
-}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
deleted file mode 100644
index 10bfe63..0000000
--- a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.textclassifier;
-
-import android.view.textclassifier.TextClassification;
-
-/**
- * Callback for a TextClassification request.
- * @hide
- */
-oneway interface ITextClassificationCallback {
-    void onSuccess(in TextClassification classification);
-    void onFailure();
-}
diff --git a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl b/core/java/android/service/textclassifier/ITextClassifierCallback.aidl
similarity index 82%
rename from core/java/android/service/textclassifier/ITextSelectionCallback.aidl
rename to core/java/android/service/textclassifier/ITextClassifierCallback.aidl
index 1b4c4d1..2926734 100644
--- a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierCallback.aidl
@@ -16,13 +16,14 @@
 
 package android.service.textclassifier;
 
+import android.os.Bundle;
 import android.view.textclassifier.TextSelection;
 
 /**
- * Callback for a TextSelection request.
+ * Callback for all requests from SystemTextClassifier.
  * @hide
  */
-oneway interface ITextSelectionCallback {
-    void onSuccess(in TextSelection selection);
+oneway interface ITextClassifierCallback {
+    void onSuccess(in Bundle result);
     void onFailure();
 }
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 7941794..2f8d67b 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -16,11 +16,7 @@
 
 package android.service.textclassifier;
 
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
+import android.service.textclassifier.ITextClassifierCallback;
 import android.view.textclassifier.ConversationActions;
 import android.view.textclassifier.SelectionEvent;
 import android.view.textclassifier.TextClassification;
@@ -41,17 +37,17 @@
     void onSuggestSelection(
             in TextClassificationSessionId sessionId,
             in TextSelection.Request request,
-            in ITextSelectionCallback callback);
+            in ITextClassifierCallback callback);
 
     void onClassifyText(
             in TextClassificationSessionId sessionId,
             in TextClassification.Request request,
-            in ITextClassificationCallback callback);
+            in ITextClassifierCallback callback);
 
     void onGenerateLinks(
             in TextClassificationSessionId sessionId,
             in TextLinks.Request request,
-            in ITextLinksCallback callback);
+            in ITextClassifierCallback callback);
 
     // TODO: Remove
     void onSelectionEvent(
@@ -72,10 +68,10 @@
     void onDetectLanguage(
             in TextClassificationSessionId sessionId,
             in TextLanguage.Request request,
-            in ITextLanguageCallback callback);
+            in ITextClassifierCallback callback);
 
     void onSuggestConversationActions(
             in TextClassificationSessionId sessionId,
             in ConversationActions.Request request,
-            in IConversationActionsCallback callback);
+            in ITextClassifierCallback callback);
 }
diff --git a/core/java/android/service/textclassifier/ITextLanguageCallback.aidl b/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
deleted file mode 100644
index 263d99af..0000000
--- a/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.textclassifier;
-
-import android.view.textclassifier.TextLanguage;
-
-/**
- * Callback for a TextLanguage request.
- * @hide
- */
-oneway interface ITextLanguageCallback {
-    void onSuccess(in TextLanguage textLanguage);
-    void onFailure();
-}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextLinksCallback.aidl b/core/java/android/service/textclassifier/ITextLinksCallback.aidl
deleted file mode 100644
index a9e0dde..0000000
--- a/core/java/android/service/textclassifier/ITextLinksCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.textclassifier;
-
-import android.view.textclassifier.TextLinks;
-
-/**
- * Callback for a TextLinks request.
- * @hide
- */
-oneway interface ITextLinksCallback {
-    void onSuccess(in TextLinks links);
-    void onFailure();
-}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 235b8e8..4088ce8 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -17,6 +17,7 @@
 package android.service.textclassifier;
 
 import android.Manifest;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -27,8 +28,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -46,6 +51,10 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /**
  * Abstract base class for the TextClassifier service.
  *
@@ -68,6 +77,11 @@
  *     </intent-filter>
  * </service>}</pre>
  *
+ * <p>From {@link android.os.Build.VERSION_CODES#Q} onward, all callbacks are called on the main
+ * thread. Prior to Q, there is no guarantee on what thread the callback will happen. You should
+ * make sure the callbacks are executed in your desired thread by using a executor, a handler or
+ * something else along the line.
+ *
  * @see TextClassifier
  * @hide
  */
@@ -85,201 +99,102 @@
     public static final String SERVICE_INTERFACE =
             "android.service.textclassifier.TextClassifierService";
 
+    /** @hide **/
+    private static final String KEY_RESULT = "key_result";
+
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), null, true);
+    private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();
+
     private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() {
 
         // TODO(b/72533911): Implement cancellation signal
         @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal();
 
-        /** {@inheritDoc} */
         @Override
         public void onSuggestSelection(
                 TextClassificationSessionId sessionId,
-                TextSelection.Request request, ITextSelectionCallback callback) {
+                TextSelection.Request request, ITextClassifierCallback callback) {
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
-            TextClassifierService.this.onSuggestSelection(
-                    sessionId, request, mCancellationSignal,
-                    new Callback<TextSelection>() {
-                        @Override
-                        public void onSuccess(TextSelection result) {
-                            try {
-                                callback.onSuccess(result);
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
+            mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestSelection(
+                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
 
-                        @Override
-                        public void onFailure(CharSequence error) {
-                            try {
-                                if (callback.asBinder().isBinderAlive()) {
-                                    callback.onFailure();
-                                }
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-                    });
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onClassifyText(
                 TextClassificationSessionId sessionId,
-                TextClassification.Request request, ITextClassificationCallback callback) {
+                TextClassification.Request request, ITextClassifierCallback callback) {
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
-            TextClassifierService.this.onClassifyText(
-                    sessionId, request, mCancellationSignal,
-                    new Callback<TextClassification>() {
-                        @Override
-                        public void onSuccess(TextClassification result) {
-                            try {
-                                callback.onSuccess(result);
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-
-                        @Override
-                        public void onFailure(CharSequence error) {
-                            try {
-                                callback.onFailure();
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-                    });
+            mMainThreadHandler.post(() -> TextClassifierService.this.onClassifyText(
+                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onGenerateLinks(
                 TextClassificationSessionId sessionId,
-                TextLinks.Request request, ITextLinksCallback callback) {
+                TextLinks.Request request, ITextClassifierCallback callback) {
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
-            TextClassifierService.this.onGenerateLinks(
-                    sessionId, request,
-                    mCancellationSignal,
-                    new Callback<TextLinks>() {
-                        @Override
-                        public void onSuccess(TextLinks result) {
-                            try {
-                                callback.onSuccess(result);
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-
-                        @Override
-                        public void onFailure(CharSequence error) {
-                            try {
-                                callback.onFailure();
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-                    });
+            mMainThreadHandler.post(() -> TextClassifierService.this.onGenerateLinks(
+                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onSelectionEvent(
                 TextClassificationSessionId sessionId,
                 SelectionEvent event) {
             Preconditions.checkNotNull(event);
-            TextClassifierService.this.onSelectionEvent(sessionId, event);
+            mMainThreadHandler.post(
+                    () -> TextClassifierService.this.onSelectionEvent(sessionId, event));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onTextClassifierEvent(
                 TextClassificationSessionId sessionId,
                 TextClassifierEvent event) {
             Preconditions.checkNotNull(event);
-            TextClassifierService.this.onTextClassifierEvent(sessionId, event);
+            mMainThreadHandler.post(
+                    () -> TextClassifierService.this.onTextClassifierEvent(sessionId, event));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onDetectLanguage(
                 TextClassificationSessionId sessionId,
                 TextLanguage.Request request,
-                ITextLanguageCallback callback) {
+                ITextClassifierCallback callback) {
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
-            TextClassifierService.this.onDetectLanguage(
-                    sessionId,
-                    request,
-                    mCancellationSignal,
-                    new Callback<TextLanguage>() {
-                        @Override
-                        public void onSuccess(TextLanguage result) {
-                            try {
-                                callback.onSuccess(result);
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-
-                        @Override
-                        public void onFailure(CharSequence error) {
-                            try {
-                                callback.onFailure();
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        };
-                    });
+            mMainThreadHandler.post(() -> TextClassifierService.this.onDetectLanguage(
+                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onSuggestConversationActions(
                 TextClassificationSessionId sessionId,
                 ConversationActions.Request request,
-                IConversationActionsCallback callback) {
+                ITextClassifierCallback callback) {
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
-            TextClassifierService.this.onSuggestConversationActions(
-                    sessionId,
-                    request,
-                    mCancellationSignal,
-                    new Callback<ConversationActions>() {
-                        @Override
-                        public void onSuccess(ConversationActions result) {
-                            try {
-                                callback.onSuccess(result);
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-
-                        @Override
-                        public void onFailure(CharSequence error) {
-                            try {
-                                callback.onFailure();
-                            } catch (RemoteException e) {
-                                Slog.d(LOG_TAG, "Error calling callback");
-                            }
-                        }
-                    });
+            mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestConversationActions(
+                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onCreateTextClassificationSession(
                 TextClassificationContext context, TextClassificationSessionId sessionId) {
             Preconditions.checkNotNull(context);
             Preconditions.checkNotNull(sessionId);
-            TextClassifierService.this.onCreateTextClassificationSession(context, sessionId);
+            mMainThreadHandler.post(
+                    () -> TextClassifierService.this.onCreateTextClassificationSession(
+                            context, sessionId));
         }
 
-        /** {@inheritDoc} */
         @Override
         public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
-            TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
+            mMainThreadHandler.post(
+                    () -> TextClassifierService.this.onDestroyTextClassificationSession(sessionId));
         }
     };
 
@@ -301,6 +216,7 @@
      * @param cancellationSignal object to watch for canceling the current operation
      * @param callback the callback to return the result to
      */
+    @MainThread
     public abstract void onSuggestSelection(
             @Nullable TextClassificationSessionId sessionId,
             @NonNull TextSelection.Request request,
@@ -316,6 +232,7 @@
      * @param cancellationSignal object to watch for canceling the current operation
      * @param callback the callback to return the result to
      */
+    @MainThread
     public abstract void onClassifyText(
             @Nullable TextClassificationSessionId sessionId,
             @NonNull TextClassification.Request request,
@@ -331,6 +248,7 @@
      * @param cancellationSignal object to watch for canceling the current operation
      * @param callback the callback to return the result to
      */
+    @MainThread
     public abstract void onGenerateLinks(
             @Nullable TextClassificationSessionId sessionId,
             @NonNull TextLinks.Request request,
@@ -345,12 +263,14 @@
      * @param cancellationSignal object to watch for canceling the current operation
      * @param callback the callback to return the result to
      */
+    @MainThread
     public void onDetectLanguage(
             @Nullable TextClassificationSessionId sessionId,
             @NonNull TextLanguage.Request request,
             @NonNull CancellationSignal cancellationSignal,
             @NonNull Callback<TextLanguage> callback) {
-        callback.onSuccess(getLocalTextClassifier().detectLanguage(request));
+        mSingleThreadExecutor.submit(() ->
+                callback.onSuccess(getLocalTextClassifier().detectLanguage(request)));
     }
 
     /**
@@ -361,12 +281,14 @@
      * @param cancellationSignal object to watch for canceling the current operation
      * @param callback the callback to return the result to
      */
+    @MainThread
     public void onSuggestConversationActions(
             @Nullable TextClassificationSessionId sessionId,
             @NonNull ConversationActions.Request request,
             @NonNull CancellationSignal cancellationSignal,
             @NonNull Callback<ConversationActions> callback) {
-        callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request));
+        mSingleThreadExecutor.submit(() ->
+                callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request)));
     }
 
     /**
@@ -383,6 +305,7 @@
      *      instead
      */
     @Deprecated
+    @MainThread
     public void onSelectionEvent(
             @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
 
@@ -396,6 +319,7 @@
      * @param sessionId the session id
      * @param event the TextClassifier event
      */
+    @MainThread
     public void onTextClassifierEvent(
             @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}
 
@@ -405,6 +329,7 @@
      * @param context the text classification context
      * @param sessionId the session's Id
      */
+    @MainThread
     public void onCreateTextClassificationSession(
             @NonNull TextClassificationContext context,
             @NonNull TextClassificationSessionId sessionId) {}
@@ -414,6 +339,7 @@
      *
      * @param sessionId the id of the session to destroy
      */
+    @MainThread
     public void onDestroyTextClassificationSession(
             @NonNull TextClassificationSessionId sessionId) {}
 
@@ -441,6 +367,11 @@
         return TextClassifier.NO_OP;
     }
 
+    /** @hide **/
+    public static <T extends Parcelable> T getResponse(Bundle bundle) {
+        return bundle.getParcelable(KEY_RESULT);
+    }
+
     /**
      * Callbacks for TextClassifierService results.
      *
@@ -494,4 +425,44 @@
                 si.permission));
         return null;
     }
+
+    /**
+     * Forwards the callback result to a wrapped binder callback.
+     */
+    private static final class ProxyCallback<T extends Parcelable> implements Callback<T> {
+        private WeakReference<ITextClassifierCallback> mTextClassifierCallback;
+
+        private ProxyCallback(ITextClassifierCallback textClassifierCallback) {
+            mTextClassifierCallback =
+                    new WeakReference<>(Preconditions.checkNotNull(textClassifierCallback));
+        }
+
+        @Override
+        public void onSuccess(T result) {
+            ITextClassifierCallback callback = mTextClassifierCallback.get();
+            if (callback == null) {
+                return;
+            }
+            try {
+                Bundle bundle = new Bundle(1);
+                bundle.putParcelable(KEY_RESULT, result);
+                callback.onSuccess(bundle);
+            } catch (RemoteException e) {
+                Slog.d(LOG_TAG, "Error calling callback");
+            }
+        }
+
+        @Override
+        public void onFailure(CharSequence error) {
+            ITextClassifierCallback callback = mTextClassifierCallback.get();
+            if (callback == null) {
+                return;
+            }
+            try {
+                callback.onFailure();
+            } catch (RemoteException e) {
+                Slog.d(LOG_TAG, "Error calling callback");
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 135a891..f3bbca3 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -22,6 +22,7 @@
 import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.SIDE_BARS;
 import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.TOP_BAR;
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.compatSystemInsets;
@@ -35,7 +36,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.SparseArray;
-import android.view.InsetsState.InternalInsetType;
 import android.view.WindowInsets.Type.InsetType;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
@@ -229,6 +229,7 @@
     static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
         typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0);
         typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
+        typeInsetMap[indexOf(SYSTEM_GESTURES)] = Insets.of(insets);
     }
 
     private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) {
@@ -630,6 +631,28 @@
     }
 
     /**
+     * Returns the system gesture insets.
+     *
+     * <p>The system gesture insets represent the area of a window where system gestures have
+     * priority and may consume some or all touch input, e.g. due to the a system bar
+     * occupying it, or it being reserved for touch-only gestures.
+     *
+     * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+     * as long as they are outside the {@link #getSystemWindowInsets() system window insets}.
+     *
+     * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+     * even when the system gestures are inactive due to
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+     * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+     *
+     * <p>This inset does not affect the result of {@link #isConsumed()} and cannot be consumed.
+     */
+    @NonNull
+    public Insets getSystemGestureInsets() {
+        return getInsets(mTypeInsetsMap, SYSTEM_GESTURES);
+    }
+
+    /**
      * Returns a copy of this WindowInsets with the stable insets fully consumed.
      *
      * @return A modified copy of this WindowInsets
@@ -853,6 +876,22 @@
         }
 
         /**
+         * Sets system gesture insets in pixels.
+         *
+         * <p>The system gesture insets represent the area of a window where system gestures have
+         * priority and may consume some or all touch input, e.g. due to the a system bar
+         * occupying it, or it being reserved for touch-only gestures.
+         *
+         * @see #getSystemGestureInsets()
+         * @return itself
+         */
+        @NonNull
+        public Builder setSystemGestureInsets(@NonNull Insets insets) {
+            WindowInsets.setInsets(mTypeInsetsMap, SYSTEM_GESTURES, insets);
+            return this;
+        }
+
+        /**
          * Sets the insets of a specific window type in pixels.
          *
          * <p>The insets represents the area of a a window that is partially or fully obscured by
@@ -1005,8 +1044,10 @@
         static final int IME = 0x2;
         static final int SIDE_BARS = 0x4;
 
-        static final int LAST = 0x8;
-        static final int SIZE = 4;
+        static final int SYSTEM_GESTURES = 0x8;
+
+        static final int LAST = 0x10;
+        static final int SIZE = 5;
         static final int WINDOW_DECOR = LAST;
 
         static int indexOf(@InsetType int type) {
@@ -1017,8 +1058,10 @@
                     return 1;
                 case SIDE_BARS:
                     return 2;
-                case WINDOW_DECOR:
+                case SYSTEM_GESTURES:
                     return 3;
+                case WINDOW_DECOR:
+                    return 4;
                 default:
                     throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
                             + " type=" + type);
@@ -1030,7 +1073,7 @@
 
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
-        @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR })
+        @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR, SYSTEM_GESTURES })
         public @interface InsetType {
         }
 
@@ -1064,6 +1107,27 @@
         }
 
         /**
+         * Returns an inset type representing the system gesture insets.
+         *
+         * <p>The system gesture insets represent the area of a window where system gestures have
+         * priority and may consume some or all touch input, e.g. due to the a system bar
+         * occupying it, or it being reserved for touch-only gestures.
+         *
+         * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+         * as long as they are outside the {@link #getSystemWindowInsets() system window insets}.
+         *
+         * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+         * even when the system gestures are inactive due to
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+         * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+         *
+         * @see #getSystemGestureInsets()
+         */
+        public static @InsetType int systemGestures() {
+            return SYSTEM_GESTURES;
+        }
+
+        /**
          * @return All system bars. Includes {@link #topBar()} as well as {@link #sideBars()}, but
          *         not {@link #ime()}.
          */
@@ -1082,6 +1146,9 @@
 
         /**
          * @return All inset types combined.
+         *
+         * TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and
+         *       {@link #ime()} does not seem very useful.
          */
         public static @InsetType int all() {
             return 0xFFFFFFFF;
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 8b370f5..8f8766e 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -20,15 +20,14 @@
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.Looper;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
+import android.service.textclassifier.ITextClassifierCallback;
 import android.service.textclassifier.ITextClassifierService;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
+import android.service.textclassifier.TextClassifierService;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -73,9 +72,10 @@
         Utils.checkMainThread();
         try {
             request.setCallingPackageName(mPackageName);
-            final TextSelectionCallback callback = new TextSelectionCallback();
+            final BlockingCallback<TextSelection> callback =
+                    new BlockingCallback<>("textselection");
             mManagerService.onSuggestSelection(mSessionId, request, callback);
-            final TextSelection selection = callback.mReceiver.get();
+            final TextSelection selection = callback.get();
             if (selection != null) {
                 return selection;
             }
@@ -95,9 +95,10 @@
         Utils.checkMainThread();
         try {
             request.setCallingPackageName(mPackageName);
-            final TextClassificationCallback callback = new TextClassificationCallback();
+            final BlockingCallback<TextClassification> callback =
+                    new BlockingCallback<>("textclassification");
             mManagerService.onClassifyText(mSessionId, request, callback);
-            final TextClassification classification = callback.mReceiver.get();
+            final TextClassification classification = callback.get();
             if (classification != null) {
                 return classification;
             }
@@ -122,9 +123,10 @@
 
         try {
             request.setCallingPackageName(mPackageName);
-            final TextLinksCallback callback = new TextLinksCallback();
+            final BlockingCallback<TextLinks> callback =
+                    new BlockingCallback<>("textlinks");
             mManagerService.onGenerateLinks(mSessionId, request, callback);
-            final TextLinks links = callback.mReceiver.get();
+            final TextLinks links = callback.get();
             if (links != null) {
                 return links;
             }
@@ -165,9 +167,10 @@
 
         try {
             request.setCallingPackageName(mPackageName);
-            final TextLanguageCallback callback = new TextLanguageCallback();
+            final BlockingCallback<TextLanguage> callback =
+                    new BlockingCallback<>("textlanguage");
             mManagerService.onDetectLanguage(mSessionId, request, callback);
-            final TextLanguage textLanguage = callback.mReceiver.get();
+            final TextLanguage textLanguage = callback.get();
             if (textLanguage != null) {
                 return textLanguage;
             }
@@ -184,9 +187,10 @@
 
         try {
             request.setCallingPackageName(mPackageName);
-            final ConversationActionsCallback callback = new ConversationActionsCallback();
+            final BlockingCallback<ConversationActions> callback =
+                    new BlockingCallback<>("conversation-actions");
             mManagerService.onSuggestConversationActions(mSessionId, request, callback);
-            final ConversationActions conversationActions = callback.mReceiver.get();
+            final ConversationActions conversationActions = callback.get();
             if (conversationActions != null) {
                 return conversationActions;
             }
@@ -245,82 +249,28 @@
         }
     }
 
-    private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
+    private static final class BlockingCallback<T extends Parcelable>
+            extends ITextClassifierCallback.Stub {
+        private final ResponseReceiver<T> mReceiver;
 
-        final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>("textselection");
+        BlockingCallback(String name) {
+            mReceiver = new ResponseReceiver<>(name);
+        }
 
         @Override
-        public void onSuccess(TextSelection selection) {
-            mReceiver.onSuccess(selection);
+        public void onSuccess(Bundle result) {
+            mReceiver.onSuccess(TextClassifierService.getResponse(result));
         }
 
         @Override
         public void onFailure() {
             mReceiver.onFailure();
         }
-    }
 
-    private static final class TextClassificationCallback extends ITextClassificationCallback.Stub {
-
-        final ResponseReceiver<TextClassification> mReceiver =
-                new ResponseReceiver<>("textclassification");
-
-        @Override
-        public void onSuccess(TextClassification classification) {
-            mReceiver.onSuccess(classification);
+        public T get() {
+            return mReceiver.get();
         }
 
-        @Override
-        public void onFailure() {
-            mReceiver.onFailure();
-        }
-    }
-
-    private static final class TextLinksCallback extends ITextLinksCallback.Stub {
-
-        final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>("textlinks");
-
-        @Override
-        public void onSuccess(TextLinks links) {
-            mReceiver.onSuccess(links);
-        }
-
-        @Override
-        public void onFailure() {
-            mReceiver.onFailure();
-        }
-    }
-
-    private static final class TextLanguageCallback extends ITextLanguageCallback.Stub {
-
-        final ResponseReceiver<TextLanguage> mReceiver = new ResponseReceiver<>("textlanguage");
-
-        @Override
-        public void onSuccess(TextLanguage textLanguage) {
-            mReceiver.onSuccess(textLanguage);
-        }
-
-        @Override
-        public void onFailure() {
-            mReceiver.onFailure();
-        }
-    }
-
-    private static final class ConversationActionsCallback
-            extends IConversationActionsCallback.Stub {
-
-        final ResponseReceiver<ConversationActions> mReceiver =
-                new ResponseReceiver<>("conversationaction");
-
-        @Override
-        public void onSuccess(ConversationActions conversationActions) {
-            mReceiver.onSuccess(conversationActions);
-        }
-
-        @Override
-        public void onFailure() {
-            mReceiver.onFailure();
-        }
     }
 
     private static final class ResponseReceiver<T> {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 457be34..a3e89c8 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1474,7 +1474,7 @@
      *
      * @param child the View to scroll to
      */
-    public void scrollToDescendant(View child) {
+    public void scrollToDescendant(@NonNull View child) {
         if (!mIsLayoutDirty) {
             child.getDrawingRect(mTempRect);
 
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 5064d23..445075d 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -616,7 +616,7 @@
     // OS: Q
     ACTION_PANEL_INTERACTION = 1658;
 
-    // ACTION: Show Contextual homepage, log latency in loading cards
+    // ACTION: Show Contextual homepage. Log total loading latency.
     ACTION_CONTEXTUAL_HOME_SHOW = 1662;
 
     // ACTION: Contextual card displays
@@ -649,6 +649,15 @@
     ACTION_ATCSCUC = 1680;
 
     ACTION_ATCHNUC = 1681;
+
+    // ACTION: Individual contextual card loading time
+    ACTION_CONTEXTUAL_CARD_LOAD = 1684;
+
+    //ACTION: Contextual card loading timeout
+    ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT = 1685;
+
+    //ACTION: Log result for each card's eligibility check
+    ACTION_CONTEXTUAL_CARD_ELIGIBILITY = 1686;
 }
 
 /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ae3e714..4c4393d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3139,6 +3139,14 @@
     <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4455,6 +4463,11 @@
          @hide -->
     <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
                 android:protectionLevel="signature" />
+    <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES}
+         intent.
+         @hide -->
+    <permission android:name="android.permission.REVIEW_ACCESSIBILITY_SERVICES"
+                android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an activity to replace the app name and icon displayed in share targets
          in the sharesheet for the Q-release and later.
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebc6be7..ad1403d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -132,6 +132,7 @@
                     Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
                     Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
                     Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+                    Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST,
                     Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
                     Settings.Global.BROADCAST_BG_CONSTANTS,
                     Settings.Global.BROADCAST_FG_CONSTANTS,
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 18eafa6..2e56e09 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1718,6 +1718,11 @@
      * <p>Modifies the bitmap to have the specified {@link ColorSpace}, without
      * affecting the underlying allocation backing the bitmap.</p>
      *
+     * <p>This affects how the framework will interpret the color at each pixel. A bitmap
+     * with {@link Config#ALPHA_8} never has a color space, since a color space does not
+     * affect the alpha channel. Other {@code Config}s must always have a non-null
+     * {@code ColorSpace}.</p>
+     *
      * @throws IllegalArgumentException If the specified color space is {@code null}, not
      *         {@link ColorSpace.Model#RGB RGB}, has a transfer function that is not an
      *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or whose
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e623354..7345ea4 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -193,7 +193,7 @@
      *
      * @param name The debug name to use for this HardwareRenderer instance
      */
-    public void setName(String name) {
+    public void setName(@NonNull String name) {
         nSetName(mNativeProxy, name);
     }
 
@@ -330,7 +330,7 @@
          *
          * @return this instance
          */
-        public FrameRenderRequest setVsyncTime(long vsyncTime) {
+        public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
             mFrameInfo.setVsync(vsyncTime, vsyncTime);
             mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
             return this;
@@ -351,7 +351,7 @@
          *
          * @return this instance
          */
-        public FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
+        public @NonNull FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
                 @NonNull Runnable frameCommitCallback) {
             setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback));
             return this;
@@ -372,7 +372,7 @@
          *                   completion.
          * @return this instance
          */
-        public FrameRenderRequest setWaitForPresent(boolean shouldWait) {
+        public @NonNull FrameRenderRequest setWaitForPresent(boolean shouldWait) {
             mWaitForPresent = shouldWait;
             return this;
         }
@@ -406,7 +406,7 @@
      * @return An instance of {@link FrameRenderRequest}. The instance may be reused for every
      * frame, so the caller should not hold onto it for longer than a single render request.
      */
-    public FrameRenderRequest createRenderRequest() {
+    public @NonNull FrameRenderRequest createRenderRequest() {
         mRenderRequest.reset();
         return mRenderRequest;
     }
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 42b6acd..c3bcbb4 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -62,7 +62,7 @@
  *
  * <h3>Creating a RenderNode</h3>
  * <pre class="prettyprint">
- *     RenderNode renderNode = RenderNode.create("myRenderNode");
+ *     RenderNode renderNode = new RenderNode("myRenderNode");
  *     renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
  *     RecordingCanvas canvas = renderNode.beginRecording();
  *     try {
@@ -106,7 +106,7 @@
  *
  * <pre class="prettyprint">
  *     private void createDisplayList() {
- *         mRenderNode = RenderNode.create("MyRenderNode");
+ *         mRenderNode = new RenderNode("MyRenderNode");
  *         mRenderNode.setLeftTopRightBottom(0, 0, width, height);
  *         RecordingCanvas canvas = mRenderNode.beginRecording();
  *         try {
@@ -338,7 +338,7 @@
      * @see #endRecording()
      * @see #hasDisplayList()
      */
-    public RecordingCanvas beginRecording(int width, int height) {
+    public @NonNull RecordingCanvas beginRecording(int width, int height) {
         if (mCurrentRecordingCanvas != null) {
             throw new IllegalStateException(
                     "Recording currently in progress - missing #endRecording() call?");
@@ -358,7 +358,7 @@
      * @see #endRecording()
      * @see #hasDisplayList()
      */
-    public RecordingCanvas beginRecording() {
+    public @NonNull RecordingCanvas beginRecording() {
         return beginRecording(nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode));
     }
 
@@ -1285,7 +1285,7 @@
      * @param position The position rectangle in pixels
      * @return True if the value changed, false if the new value was the same as the previous value.
      */
-    public boolean setPosition(Rect position) {
+    public boolean setPosition(@NonNull Rect position) {
         return nSetLeftTopRightBottom(mNativeRenderNode,
                 position.left, position.top, position.right, position.bottom);
     }
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 1894e32..e58e802 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -17,9 +17,11 @@
 package android.graphics.drawable;
 
 import android.annotation.ColorInt;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.Px;
 import android.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
@@ -670,7 +672,7 @@
      * @see #mutate()
      * @see #setColor(int)
      */
-    public void setColors(@ColorInt int[] colors) {
+    public void setColors(@Nullable @ColorInt int[] colors) {
         setColors(colors, null);
     }
 
@@ -690,7 +692,7 @@
      * @see #mutate()
      * @see #setColors(int[])
      */
-    public void setColors(@ColorInt int[] colors, @Nullable float[] offsets) {
+    public void setColors(@Nullable @ColorInt int[] colors, @Nullable float[] offsets) {
         mGradientState.setGradientColors(colors);
         mGradientState.mPositions = offsets;
         mGradientIsDirty = true;
@@ -877,7 +879,11 @@
      * @see #getInnerRadiusRatio()
      * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
      */
-    public void setInnerRadiusRatio(float innerRadiusRatio) {
+    public void setInnerRadiusRatio(
+            @FloatRange(from = 0.0f, fromInclusive = false) float innerRadiusRatio) {
+        if (innerRadiusRatio <= 0) {
+            throw new IllegalArgumentException("Ratio must be greater than zero");
+        }
         mGradientState.mInnerRadiusRatio = innerRadiusRatio;
         mPathIsDirty = true;
         invalidateSelf();
@@ -899,7 +905,7 @@
      * @see #getInnerRadius()
      * @attr ref android.R.styleable#GradientDrawable_innerRadius
      */
-    public void setInnerRadius(int innerRadius) {
+    public void setInnerRadius(@Px int innerRadius) {
         mGradientState.mInnerRadius = innerRadius;
         mPathIsDirty = true;
         invalidateSelf();
@@ -911,7 +917,7 @@
      * @see #setInnerRadius(int)
      * @attr ref android.R.styleable#GradientDrawable_innerRadius
      */
-    public int getInnerRadius() {
+    public @Px int getInnerRadius() {
         return mGradientState.mInnerRadius;
     }
 
@@ -921,7 +927,11 @@
      * @see #getThicknessRatio()
      * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
      */
-    public void setThicknessRatio(float thicknessRatio) {
+    public void setThicknessRatio(
+            @FloatRange(from = 0.0f, fromInclusive = false) float thicknessRatio) {
+        if (thicknessRatio <= 0) {
+            throw new IllegalArgumentException("Ratio must be greater than zero");
+        }
         mGradientState.mThicknessRatio = thicknessRatio;
         mPathIsDirty = true;
         invalidateSelf();
@@ -942,7 +952,7 @@
      *
      * @attr ref android.R.styleable#GradientDrawable_thickness
      */
-    public void setThickness(int thickness) {
+    public void setThickness(@Px int thickness) {
         mGradientState.mThickness = thickness;
         mPathIsDirty = true;
         invalidateSelf();
@@ -954,7 +964,7 @@
      * @see #setThickness(int)
      * @attr ref android.R.styleable#GradientDrawable_thickness
      */
-    public int getThickness() {
+    public @Px int getThickness() {
         return mGradientState.mThickness;
     }
 
@@ -970,7 +980,7 @@
      * @attr ref android.R.styleable#GradientDrawablePadding_right
      * @attr ref android.R.styleable#GradientDrawablePadding_bottom
      */
-    public void setPadding(int left, int top, int right, int bottom) {
+    public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) {
         if (mGradientState.mPadding == null) {
             mGradientState.mPadding = new Rect();
         }
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 2855227..f67188c 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -256,7 +256,7 @@
      * @see #getStateCount()
      * @see #getStateDrawable(int)
      */
-    public int[] getStateSet(int index) {
+    public @NonNull int[] getStateSet(int index) {
         return mStateListState.mStateSets[index];
     }
 
@@ -268,7 +268,7 @@
      * @see #getStateCount()
      * @see #getStateSet(int)
      */
-    public Drawable getStateDrawable(int index) {
+    public @Nullable Drawable getStateDrawable(int index) {
         return mStateListState.getChild(index);
     }
 
@@ -280,7 +280,7 @@
      * @see #getStateDrawable(int)
      * @see #getStateSet(int)
      */
-    public int findStateDrawableIndex(int[] stateSet) {
+    public int findStateDrawableIndex(@NonNull int[] stateSet) {
         return mStateListState.indexOfStateSet(stateSet);
     }
 
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h
index 5e8eac1..f056d3b 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include/android/os/IncidentReportArgs.h
@@ -47,12 +47,16 @@
     void setAll(bool all);
     void setDest(int dest);
     void addSection(int section);
+    void setReceiverPkg(const string& pkg);
+    void setReceiverCls(const string& cls);
     void addHeader(const vector<uint8_t>& headerProto);
 
     inline bool all() const { return mAll; }
     bool containsSection(int section) const;
     inline int dest() const { return mDest; }
     inline const set<int>& sections() const { return mSections; }
+    inline const String16& receiverPkg() const { return mReceiverPkg; }
+    inline const String16& receiverCls() const { return mReceiverCls; }
     inline const vector<vector<uint8_t>>& headers() const { return mHeaders; }
 
     void merge(const IncidentReportArgs& that);
@@ -62,6 +66,8 @@
     vector<vector<uint8_t>> mHeaders;
     bool mAll;
     int mDest;
+    String16 mReceiverPkg;
+    String16 mReceiverCls;
 };
 
 }
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index 06b7a5b..46c8dcf9 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -81,6 +81,16 @@
         return err;
     }
 
+    err = out->writeString16(mReceiverPkg);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    err = out->writeString16(mReceiverCls);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
     return NO_ERROR;
 }
 
@@ -134,6 +144,9 @@
     }
     mDest = dest;
 
+    mReceiverPkg = in->readString16();
+    mReceiverCls = in->readString16();
+
     return OK;
 }
 
@@ -161,6 +174,18 @@
 }
 
 void
+IncidentReportArgs::setReceiverPkg(const string& pkg)
+{
+    mReceiverPkg = String16(pkg.c_str());
+}
+
+void
+IncidentReportArgs::setReceiverCls(const string& cls)
+{
+    mReceiverCls = String16(cls.c_str());
+}
+
+void
 IncidentReportArgs::addHeader(const vector<uint8_t>& headerProto)
 {
     mHeaders.push_back(headerProto);
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 17e2509..ed74333 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -113,6 +113,10 @@
      * Bit mask for mFieldsMask indicating the presence of mBearingAccuracy.
      */
     private static final int HAS_BEARING_ACCURACY_MASK = 128;
+    /**
+     * Bit mask for mFieldsMask indicating the presence of mElapsedRealtimeUncertaintyNanos.
+     */
+    private static final int HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK = 256;
 
     // Cached data to make bearing/distance computations more efficient for the case
     // where distanceTo and bearingTo are called in sequence.  Assume this typically happens
@@ -130,6 +134,9 @@
     private long mTime = 0;
     @UnsupportedAppUsage
     private long mElapsedRealtimeNanos = 0;
+    // Estimate of the relative precision of the alignment of this SystemClock
+    // timestamp, with the reported measurements in nanoseconds (68% confidence).
+    private long mElapsedRealtimeUncertaintyNanos = 0;
     private double mLatitude = 0.0;
     private double mLongitude = 0.0;
     private double mAltitude = 0.0f;
@@ -171,6 +178,7 @@
         mProvider = l.mProvider;
         mTime = l.mTime;
         mElapsedRealtimeNanos = l.mElapsedRealtimeNanos;
+        mElapsedRealtimeUncertaintyNanos = l.mElapsedRealtimeUncertaintyNanos;
         mFieldsMask = l.mFieldsMask;
         mLatitude = l.mLatitude;
         mLongitude = l.mLongitude;
@@ -191,6 +199,7 @@
         mProvider = null;
         mTime = 0;
         mElapsedRealtimeNanos = 0;
+        mElapsedRealtimeUncertaintyNanos = 0;
         mFieldsMask = 0;
         mLatitude = 0;
         mLongitude = 0;
@@ -586,6 +595,37 @@
     }
 
     /**
+     * Get estimate of the relative precision of the alignment of the
+     * ElapsedRealtimeNanos timestamp, with the reported measurements in
+     * nanoseconds (68% confidence).
+     *
+     * @return uncertainty of elapsed real-time of fix, in nanoseconds.
+     */
+    public long getElapsedRealtimeUncertaintyNanos() {
+        return mElapsedRealtimeUncertaintyNanos;
+    }
+
+    /**
+     * Set estimate of the relative precision of the alignment of the
+     * ElapsedRealtimeNanos timestamp, with the reported measurements in
+     * nanoseconds (68% confidence).
+     *
+     * @param time uncertainty of the elapsed real-time of fix, in nanoseconds.
+     */
+    public void setElapsedRealtimeUncertaintyNanos(long time) {
+        mElapsedRealtimeUncertaintyNanos = time;
+        mFieldsMask |= HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK;
+    }
+
+    /**
+     * True if this location has a elapsed realtime accuracy.
+     */
+    public boolean hasElapsedRealtimeUncertaintyNanos() {
+        return (mFieldsMask & HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK) != 0;
+    }
+
+
+    /**
      * Get the latitude, in degrees.
      *
      * <p>All locations generated by the {@link LocationManager}
@@ -1062,6 +1102,10 @@
             s.append(" et=");
             TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s);
         }
+        if (hasElapsedRealtimeUncertaintyNanos()) {
+            s.append(" etAcc=");
+            TimeUtils.formatDuration(mElapsedRealtimeUncertaintyNanos / 1000000L, s);
+        }
         if (hasAltitude()) s.append(" alt=").append(mAltitude);
         if (hasSpeed()) s.append(" vel=").append(mSpeed);
         if (hasBearing()) s.append(" bear=").append(mBearing);
@@ -1092,6 +1136,7 @@
             Location l = new Location(provider);
             l.mTime = in.readLong();
             l.mElapsedRealtimeNanos = in.readLong();
+            l.mElapsedRealtimeUncertaintyNanos = in.readLong();
             l.mFieldsMask = in.readByte();
             l.mLatitude = in.readDouble();
             l.mLongitude = in.readDouble();
@@ -1122,6 +1167,7 @@
         parcel.writeString(mProvider);
         parcel.writeLong(mTime);
         parcel.writeLong(mElapsedRealtimeNanos);
+        parcel.writeLong(mElapsedRealtimeUncertaintyNanos);
         parcel.writeByte(mFieldsMask);
         parcel.writeDouble(mLatitude);
         parcel.writeDouble(mLongitude);
diff --git a/media/apex/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java
index fc0f08e..ff0d43e 100644
--- a/media/apex/java/android/media/MediaItem2.java
+++ b/media/apex/java/android/media/MediaItem2.java
@@ -245,7 +245,7 @@
     /**
      * Builder for {@link MediaItem2}.
      */
-    public static class Builder {
+    public static final class Builder {
         @SuppressWarnings("WeakerAccess") /* synthetic access */
         MediaMetadata mMetadata;
         @SuppressWarnings("WeakerAccess") /* synthetic access */
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 46d4204..aed8e4e 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -630,7 +630,7 @@
          *                     true to allow apps to capture the audio
          * @return the same Builder instance
          */
-        public Builder setAllowCapture(boolean allowCapture) {
+        public @NonNull Builder setAllowCapture(boolean allowCapture) {
             if (allowCapture) {
                 mFlags &= ~FLAG_NO_CAPTURE;
             } else {
diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
index 9a16aea..333cd2d 100644
--- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java
+++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
@@ -72,7 +72,7 @@
      *
      * @param audioFormat The format in which to capture the audio.
      */
-    AudioMix createAudioMix(AudioFormat audioFormat) {
+    @NonNull AudioMix createAudioMix(@NonNull AudioFormat audioFormat) {
         return new AudioMix.Builder(mAudioMixingRule)
                 .setFormat(audioFormat)
                 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER)
@@ -123,7 +123,7 @@
          * @throws IllegalStateException if called in conjunction with
          *     {@link #excludeUsage(AudioAttributes)}.
          */
-        public Builder addMatchingUsage(@NonNull AudioAttributes audioAttributes) {
+        public @NonNull Builder addMatchingUsage(@NonNull AudioAttributes audioAttributes) {
             Preconditions.checkNotNull(audioAttributes);
             Preconditions.checkState(
                     mUsageMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
@@ -141,7 +141,7 @@
          *
          * @throws IllegalStateException if called in conjunction with {@link #excludeUid(int)}.
          */
-        public Builder addMatchingUid(int uid) {
+        public @NonNull Builder addMatchingUid(int uid) {
             Preconditions.checkState(
                     mUidMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
             mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
@@ -158,7 +158,7 @@
          * @throws IllegalStateException if called in conjunction with
          *     {@link #addMatchingUsage(AudioAttributes)}.
          */
-        public Builder excludeUsage(@NonNull AudioAttributes audioAttributes) {
+        public @NonNull Builder excludeUsage(@NonNull AudioAttributes audioAttributes) {
             Preconditions.checkNotNull(audioAttributes);
             Preconditions.checkState(
                     mUsageMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
@@ -176,7 +176,7 @@
          *
          * @throws IllegalStateException if called in conjunction with {@link #addMatchingUid(int)}.
          */
-        public Builder excludeUid(int uid) {
+        public @NonNull Builder excludeUid(int uid) {
             Preconditions.checkState(
                     mUidMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
             mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
@@ -189,7 +189,7 @@
          *
          * @throws UnsupportedOperationException if the parameters set are incompatible.
          */
-        public AudioPlaybackCaptureConfiguration build() {
+        public @NonNull AudioPlaybackCaptureConfiguration build() {
             return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build(),
                                                          mProjection);
         }
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 3d5120f..28937a6 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -618,7 +618,7 @@
          * @throws IllegalStateException if called in conjunction with {@link #setAudioSource(int)}.
          * @throws NullPointerException if {@code config} is null.
          */
-        public Builder setAudioPlaybackCaptureConfig(
+        public @NonNull Builder setAudioPlaybackCaptureConfig(
                 @NonNull AudioPlaybackCaptureConfiguration config) {
             Preconditions.checkNotNull(
                     config, "Illegal null AudioPlaybackCaptureConfiguration argument");
@@ -647,7 +647,7 @@
             return this;
         }
 
-        private AudioRecord buildAudioPlaybackCaptureRecord() {
+        private @NonNull AudioRecord buildAudioPlaybackCaptureRecord() {
             AudioMix audioMix = mAudioPlaybackCaptureConfiguration.createAudioMix(mFormat);
             MediaProjection projection = mAudioPlaybackCaptureConfiguration.getMediaProjection();
             AudioPolicy audioPolicy = new AudioPolicy.Builder(/*context=*/ null)
diff --git a/packages/CaptivePortalLogin/Android.bp b/packages/CaptivePortalLogin/Android.bp
index a71977f..bc614e9 100644
--- a/packages/CaptivePortalLogin/Android.bp
+++ b/packages/CaptivePortalLogin/Android.bp
@@ -18,6 +18,7 @@
     name: "CaptivePortalLogin",
     srcs: ["src/**/*.java"],
     sdk_version: "system_current",
+    min_sdk_version: "28",
     certificate: "networkstack",
     static_libs: [
         "androidx.legacy_legacy-support-v4",
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index a5f3b88fe..44e0a65 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -18,7 +18,7 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.captiveportallogin"
-    android:versionCode="10"
+    android:versionCode="11"
     android:versionName="Q-initial">
 
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
index 7755cbc..719417e 100644
--- a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
+++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
@@ -114,6 +114,7 @@
     private NotificationManager mNM;
 
     private long mSystemSize;
+    private long mUserdataSize;
     private long mInstalledSize;
     private boolean mJustCancelledByUser;
 
@@ -220,9 +221,11 @@
 
         String url = intent.getStringExtra(DynamicAndroidClient.KEY_SYSTEM_URL);
         mSystemSize = intent.getLongExtra(DynamicAndroidClient.KEY_SYSTEM_SIZE, 0);
-        long userdata = intent.getLongExtra(DynamicAndroidClient.KEY_USERDATA_SIZE, 0);
+        mUserdataSize = intent.getLongExtra(DynamicAndroidClient.KEY_USERDATA_SIZE, 0);
 
-        mInstallTask = new InstallationAsyncTask(url, mSystemSize, userdata, mDynAndroid, this);
+        mInstallTask = new InstallationAsyncTask(
+                url, mSystemSize, mUserdataSize, mDynAndroid, this);
+
         mInstallTask.execute();
 
         // start fore ground
@@ -332,8 +335,8 @@
             case STATUS_IN_PROGRESS:
                 builder.setContentText(getString(R.string.notification_install_inprogress));
 
-                int max = (int) Math.max(mSystemSize >> 20, 1);
-                int progress = (int) mInstalledSize >> 20;
+                int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1);
+                int progress = (int) (mInstalledSize >> 20);
 
                 builder.setProgress(max, progress, false);
 
diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
index 3c759e9..03fc773 100644
--- a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
+++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
@@ -16,6 +16,7 @@
 
 package com.android.dynandroid;
 
+import android.gsi.GsiProgress;
 import android.os.AsyncTask;
 import android.os.DynamicAndroidManager;
 import android.util.Log;
@@ -63,8 +64,6 @@
     private final InstallStatusListener mListener;
     private DynamicAndroidManager.Session mInstallationSession;
 
-    private long mInstalledSize;
-    private long mReportedInstalledSize;
     private int mResult = NO_RESULT;
 
     private InputStream mStream;
@@ -89,8 +88,40 @@
         Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
 
         try {
-            // call start in background
-            mInstallationSession = mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize);
+            long installedSize = 0;
+            long reportedInstalledSize = 0;
+
+            long minStepToReport = (mSystemSize + mUserdataSize) / 100;
+
+            // init input stream before calling startInstallation(), which takes 90 seconds.
+            initInputStream();
+
+            Thread thread = new Thread(() -> {
+                mInstallationSession =
+                        mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize);
+            });
+
+
+            thread.start();
+
+            while (thread.isAlive()) {
+                if (isCancelled()) {
+                    boolean aborted = mDynamicAndroid.abort();
+                    Log.d(TAG, "Called DynamicAndroidManager.abort(), result = " + aborted);
+                    return RESULT_OK;
+                }
+
+                GsiProgress progress = mDynamicAndroid.getInstallationProgress();
+                installedSize = progress.bytes_processed;
+
+                if (installedSize > reportedInstalledSize + minStepToReport) {
+                    publishProgress(installedSize);
+                    reportedInstalledSize = installedSize;
+                }
+
+                Thread.sleep(10);
+            }
+
 
             if (mInstallationSession == null) {
                 Log.e(TAG, "Failed to start installation with requested size: "
@@ -99,12 +130,11 @@
                 return RESULT_ERROR_IO;
             }
 
-            initInputStream();
+            installedSize = mUserdataSize;
 
             byte[] bytes = new byte[READ_BUFFER_SIZE];
 
             int numBytesRead;
-            long minStepToReport = mSystemSize / 100;
 
             Log.d(TAG, "Start installation loop");
             while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
@@ -119,11 +149,11 @@
                     throw new IOException("Failed write() to DynamicAndroid");
                 }
 
-                mInstalledSize += numBytesRead;
+                installedSize += numBytesRead;
 
-                if (mInstalledSize > mReportedInstalledSize + minStepToReport) {
-                    publishProgress(mInstalledSize);
-                    mReportedInstalledSize = mInstalledSize;
+                if (installedSize > reportedInstalledSize + minStepToReport) {
+                    publishProgress(installedSize);
+                    reportedInstalledSize = installedSize;
                 }
             }
 
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 5f1f26d..f210840 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -36,6 +36,7 @@
 android_app {
     name: "NetworkStack",
     sdk_version: "system_current",
+    min_sdk_version: "28",
     certificate: "networkstack",
     privileged: true,
     static_libs: [
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 740c573..003f1e5 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.networkstack"
           android:sharedUserId="android.uid.networkstack"
-          android:versionCode="10"
+          android:versionCode="11"
           android:versionName="Q-initial">
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/packages/NetworkStackPermissionStub/Android.bp b/packages/NetworkStackPermissionStub/Android.bp
index dd70cf5..8cee92e 100644
--- a/packages/NetworkStackPermissionStub/Android.bp
+++ b/packages/NetworkStackPermissionStub/Android.bp
@@ -21,6 +21,7 @@
     // a classes.dex.
     srcs: ["src/**/*.java"],
     platform_apis: true,
+    min_sdk_version: "28",
     certificate: "networkstack",
     privileged: true,
     manifest: "AndroidManifest.xml",
diff --git a/packages/NetworkStackPermissionStub/AndroidManifest.xml b/packages/NetworkStackPermissionStub/AndroidManifest.xml
index 62bf3de..e83f050 100644
--- a/packages/NetworkStackPermissionStub/AndroidManifest.xml
+++ b/packages/NetworkStackPermissionStub/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.networkstack.permissionstub"
     android:sharedUserId="android.uid.networkstack"
-    android:versionCode="10"
+    android:versionCode="11"
     android:versionName="Q-initial">
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
     <!--
diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml
index 84c793f..672abf1 100644
--- a/packages/SystemUI/res/drawable/qs_detail_background.xml
+++ b/packages/SystemUI/res/drawable/qs_detail_background.xml
@@ -14,6 +14,20 @@
     limitations under the License.
 -->
 <transition xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@color/qs_detail_transition" />
-    <item android:drawable="?android:attr/colorPrimary" />
+    <item>
+        <inset>
+            <shape>
+                <solid android:color="@color/qs_detail_transition"/>
+                <corners android:radius="?android:attr/dialogCornerRadius" />
+            </shape>
+        </inset>
+    </item>
+    <item>
+        <inset>
+            <shape>
+                <solid android:color="?android:attr/colorPrimary"/>
+                <corners android:radius="?android:attr/dialogCornerRadius" />
+            </shape>
+        </inset>
+    </item>
 </transition>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml
index 480f523..235d0fc 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml
@@ -10,14 +10,12 @@
     android:gravity="top|right"
     android:clipChildren="false"
 >
-
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
         android:gravity="top|right"
         android:padding="0dp"
         android:orientation="vertical"
-        android:layoutDirection="ltr"
         android:layout_marginRight="@dimen/global_actions_grid_container_bottom_margin"
     >
         <!-- Grid of action items -->
@@ -26,7 +24,6 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:layoutDirection="ltr"
             android:layout_marginTop="@dimen/global_actions_grid_side_margin"
             android:translationZ="@dimen/global_actions_translate"
             android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
@@ -39,25 +36,21 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="ltr"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
             <LinearLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="ltr"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
             <LinearLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="ltr"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
         </com.android.systemui.globalactions.ListGridLayout>
-
         <!-- For separated items-->
         <LinearLayout
             android:id="@+id/separated_button"
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
index 4f86826..e028214 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
@@ -10,7 +10,6 @@
     android:gravity="top|left"
     android:clipChildren="false"
 >
-
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
@@ -37,11 +36,11 @@
             android:gravity="center"
             android:translationZ="@dimen/global_actions_translate"
         />
-
         <!-- Grid of action items -->
         <com.android.systemui.globalactions.ListGridLayout
             android:id="@android:id/list"
             android:layout_gravity="bottom|left"
+            android:gravity="right"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="vertical"
@@ -54,28 +53,22 @@
             android:background="?android:attr/colorBackgroundFloating"
         >
             <LinearLayout
-                android:layout_gravity="bottom"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="rtl"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
             <LinearLayout
-                android:layout_gravity="bottom"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="rtl"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
             <LinearLayout
-                android:layout_gravity="bottom"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:layoutDirection="rtl"
-                android:orientation="horizontal"
+                android:layoutDirection="locale"
             />
         </com.android.systemui.globalactions.ListGridLayout>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
index 729e96e..a620a8e 100644
--- a/packages/SystemUI/res/layout/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -10,7 +10,6 @@
     android:gravity="bottom|center"
     android:clipChildren="false"
 >
-
     <LinearLayout
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
@@ -35,14 +34,13 @@
             android:gravity="center"
             android:translationZ="@dimen/global_actions_translate"
         />
-
         <!-- Grid of action items -->
         <com.android.systemui.globalactions.ListGridLayout
             android:id="@android:id/list"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layoutDirection="rtl"
+            android:orientation="vertical"
+            android:gravity="right"
             android:layout_marginRight="@dimen/global_actions_grid_side_margin"
             android:translationZ="@dimen/global_actions_translate"
             android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
@@ -55,19 +53,19 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:orientation="vertical"
+                android:layoutDirection="locale"
             />
             <LinearLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:orientation="vertical"
+                android:layoutDirection="locale"
             />
             <LinearLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:visibility="gone"
-                android:orientation="vertical"
+                android:layoutDirection="locale"
             />
         </com.android.systemui.globalactions.ListGridLayout>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 863c1cc..bd9d3fb 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -44,7 +44,7 @@
             android:id="@+id/pkgname"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
             android:layout_marginStart="3dp"
             android:layout_marginEnd="2dp"
             android:singleLine="true"
@@ -54,7 +54,7 @@
             android:id="@+id/pkg_divider"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
             android:layout_marginStart="2dp"
             android:layout_marginEnd="2dp"
             android:text="@*android:string/notification_header_divider_symbol"
@@ -64,7 +64,7 @@
             android:id="@+id/delegate_name"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
             android:layout_marginStart="2dp"
             android:layout_marginEnd="2dp"
             android:ellipsize="end"
@@ -129,7 +129,7 @@
                     android:id="@+id/group_name"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
                     android:layout_marginStart="2dp"
                     android:layout_marginEnd="2dp"
                     android:ellipsize="end"
@@ -139,7 +139,7 @@
                     android:id="@+id/pkg_group_divider"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
                     android:layout_marginStart="2dp"
                     android:layout_marginEnd="2dp"
                     android:text="@*android:string/notification_header_divider_symbol"
@@ -151,7 +151,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_weight="1"
-                    style="@android:style/TextAppearance.Material.Notification.Title"
+                    style="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
                     android:layout_toEndOf="@id/pkg_group_divider"/>
             </RelativeLayout>
             <!-- Question prompt -->
@@ -159,7 +159,7 @@
                 android:id="@+id/block_prompt"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                style="@android:style/TextAppearance.Material.Notification" />
+                style="@*android:style/TextAppearance.DeviceDefault.Notification" />
         </LinearLayout>
 
         <!-- Settings and Done buttons -->
diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
index 586cdf3..716e127 100644
--- a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
+++ b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
@@ -1,27 +1,76 @@
 precision mediump float;
 
+// The actual wallpaper texture.
 uniform sampler2D uTexture;
-uniform float uCenterReveal;
+
+// The 85th percenile for the luminance histogram of the image (a value between 0 and 1).
+// This value represents the point in histogram that includes 85% of the pixels of the image.
+uniform float uPer85;
+
+// Reveal is the animation value that goes from 1 (the image is hidden) to 0 (the image is visible).
 uniform float uReveal;
+
+// The opacity of locked screen (constant value).
 uniform float uAod2Opacity;
 varying vec2 vTextureCoordinates;
 
+/*
+ * Calculates the relative luminance of the pixel.
+ */
 vec3 luminosity(vec3 color) {
     float lum = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
     return vec3(lum);
 }
 
 vec4 transform(vec3 diffuse) {
-    // TODO: Add well comments here, tracking on b/123615467.
+    // Getting the luminance for this pixel
     vec3 lum = luminosity(diffuse);
-    diffuse = mix(diffuse, lum, smoothstep(0., uCenterReveal, uReveal));
-    float val = mix(uReveal, uCenterReveal, step(uCenterReveal, uReveal));
-    diffuse = smoothstep(val, 1.0, diffuse);
-    diffuse *= uAod2Opacity * (1. - smoothstep(uCenterReveal, 1., uReveal));
+
+    /*
+     * while the reveal > per85, it shows the luminance image (B&W image)
+     * then when moving passed that value, the image gets colored.
+     */
+    float trans = smoothstep(0., uPer85, uReveal);
+    diffuse = mix(diffuse, lum, trans);
+
+    // 'lower' value represents the capped 'reveal' value to the range [0, per85]
+    float selector = step(uPer85, uReveal);
+    float lower = mix(uReveal, uPer85, selector);
+
+    /*
+     * Remaps image:
+     * - from reveal=1 to reveal=per85 => lower=per85, diffuse=luminance
+     *   That means that remaps black and white image pixel
+     *   from a possible values of [0,1] to [per85, 1] (if the pixel is darker than per85,
+     *   it's gonna be black, if it's between per85 and 1, it's gonna be gray
+     *   and if it's 1 it's gonna be white).
+     * - from reveal=per85 to reveal=0 => lower=reveal, 'diffuse' changes from luminance to color
+     *   That means that remaps each image pixel color (rgb)
+     *   from a possible values of [0,1] to [lower, 1] (if the pixel color is darker than 'lower',
+     *   it's gonna be 0, if it's between 'lower' and 1, it's gonna be remap to a value
+     *   between 0 and 1 and if it's 1 it's gonna be 1).
+     * - if reveal=0 => lower=0, diffuse=color image
+     *   The image is shown as it is, colored.
+     */
+    vec3 remaps = smoothstep(lower, 1., diffuse);
+
+    // Interpolate between diffuse and remaps using reveal to avoid over saturation.
+    diffuse = mix(diffuse, remaps, uReveal);
+
+    /*
+     * Fades in the pixel value:
+     * - if reveal=1 => fadeInOpacity=0
+     * - from reveal=1 to reveal=per85 => 0<=fadeInOpacity<=1
+     * - if reveal>per85 => fadeInOpacity=1
+     */
+    float fadeInOpacity = 1. - smoothstep(uPer85, 1., uReveal);
+    diffuse *= uAod2Opacity * fadeInOpacity;
+
     return vec4(diffuse.r, diffuse.g, diffuse.b, 1.);
 }
 
 void main() {
+    // gets the pixel value of the wallpaper for this uv coordinates on screen.
     vec4 fragColor = texture2D(uTexture, vTextureCoordinates);
     gl_FragColor = transform(fragColor.rgb);
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index f5451e9..26c5ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -90,7 +90,7 @@
     }
 
     @Override
-    public ViewGroup getParentView(boolean separated, int index, boolean reverse) {
+    public ViewGroup getParentView(boolean separated, int index, int rotation) {
         if (separated) {
             return getSeparatedView();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index 8c49d56..8259da6 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -57,12 +57,12 @@
      * @param separated Whether or not this index refers to a position in the separated or list
      *                  container.
      * @param index The index of the item within the container.
-     * @param reverse If the MultiListLayout contains sub-lists within the list container, reverse
-     *                the order that they are filled.
+     * @param rotation Specifies the rotation of the device, which is used in some cases to
+     *                 determine child ordering.
      * @return The parent ViewGroup which will be used to contain the specified item
      * after it has been added to the layout.
      */
-    public abstract ViewGroup getParentView(boolean separated, int index, boolean reverse);
+    public abstract ViewGroup getParentView(boolean separated, int index, int rotation);
 
     /**
      * Sets the divided view, which may have a differently-colored background.
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3fa6035..c16c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -96,6 +96,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that
@@ -1546,33 +1547,40 @@
             ArrayList<Action> listActions = mAdapter.getListActions(mShouldDisplaySeparatedButton);
             mGlobalActionsLayout.setExpectedListItemCount(listActions.size());
             mGlobalActionsLayout.setExpectedSeparatedItemCount(separatedActions.size());
+            int rotation = RotationUtils.getRotation(mContext);
+
+            boolean reverse = false; // should we add items to parents in the reverse order?
+            if (isGridEnabled(mContext)) {
+                if (rotation == RotationUtils.ROTATION_NONE
+                        || rotation == RotationUtils.ROTATION_SEASCAPE) {
+                    reverse = !reverse; // if we're in portrait or seascape, reverse items
+                }
+                if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+                        == View.LAYOUT_DIRECTION_RTL) {
+                    reverse = !reverse; // if we're in an RTL language, reverse items (again)
+                }
+            }
 
             for (int i = 0; i < mAdapter.getCount(); i++) {
                 Action action = mAdapter.getItem(i);
                 int separatedIndex = separatedActions.indexOf(action);
                 ViewGroup parent;
                 if (separatedIndex != -1) {
-                    parent = mGlobalActionsLayout.getParentView(true, separatedIndex, false);
+                    parent = mGlobalActionsLayout.getParentView(true, separatedIndex, rotation);
                 } else {
-                    boolean reverse = false;
-
-                    // If we're using the grid layout and we're in seascape, reverse the order
-                    // of sublists to make sure they render in the correct positions,
-                    // since we can't reverse vertical LinearLayouts through the layout xml.
-
-                    if (isGridEnabled(mContext) && RotationUtils.getRotation(mContext)
-                            == RotationUtils.ROTATION_SEASCAPE) {
-                        reverse = true;
-                    }
                     int listIndex = listActions.indexOf(action);
-                    parent = mGlobalActionsLayout.getParentView(false, listIndex, reverse);
+                    parent = mGlobalActionsLayout.getParentView(false, listIndex, rotation);
                 }
                 View v = mAdapter.getView(i, null, parent);
                 final int pos = i;
                 v.setOnClickListener(view -> mClickListener.onClick(this, pos));
                 v.setOnLongClickListener(view ->
                         mLongClickListener.onItemLongClick(null, v, pos, 0));
-                parent.addView(v);
+                if (reverse) {
+                    parent.addView(v, 0); // reverse order of items
+                } else {
+                    parent.addView(v);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
index 1d04277..036d8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.HardwareBgDrawable;
 import com.android.systemui.MultiListLayout;
+import com.android.systemui.util.leak.RotationUtils;
 
 /**
  * Grid-based implementation of the button layout created by the global actions dialog.
@@ -83,11 +84,18 @@
     }
 
     @Override
-    public ViewGroup getParentView(boolean separated, int index, boolean reverseOrder) {
+    public ViewGroup getParentView(boolean separated, int index, int rotation) {
         if (separated) {
             return getSeparatedView();
         } else {
-            return getListView().getParentView(index, reverseOrder);
+            switch (rotation) {
+                case RotationUtils.ROTATION_LANDSCAPE:
+                    return getListView().getParentView(index, false, true);
+                case RotationUtils.ROTATION_SEASCAPE:
+                    return getListView().getParentView(index, true, true);
+                default:
+                    return getListView().getParentView(index, false, false);
+            }
         }
     }
 
@@ -96,6 +104,6 @@
      */
     @Override
     public void setDivisionView(View v) {
-
+        // do nothing
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
index d5dcd74..4df1c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -62,11 +62,11 @@
     /**
      * Get the parent view associated with the item which should be placed at the given position.
      */
-    public ViewGroup getParentView(int index, boolean reverseSublists) {
+    public ViewGroup getParentView(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
         if (mRows == 0) {
             return null;
         }
-        int column = getParentViewIndex(index, reverseSublists);
+        int column = getParentViewIndex(index, reverseSublists, swapRowsAndColumns);
         return (ViewGroup) getChildAt(column);
     }
 
@@ -74,13 +74,18 @@
         return getChildCount() - (index + 1);
     }
 
-    private int getParentViewIndex(int index, boolean reverseSublists) {
-        int column = (int) Math.floor(index / mRows);
-        int columnCount = getChildCount();
-        if (reverseSublists) {
-            column = reverseSublistIndex(column);
+    private int getParentViewIndex(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
+        int sublistIndex;
+        ViewGroup row;
+        if (swapRowsAndColumns) {
+            sublistIndex = (int) Math.floor(index / mRows);
+        } else {
+            sublistIndex = index % mRows;
         }
-        return column;
+        if (reverseSublists) {
+            sublistIndex = reverseSublistIndex(sublistIndex);
+        }
+        return sublistIndex;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
index 19d85b1..a313336 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
@@ -50,7 +50,7 @@
 
     static final String A_POSITION = "aPosition";
     static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
-    static final String U_CENTER_REVEAL = "uCenterReveal";
+    static final String U_PER85 = "uPer85";
     static final String U_REVEAL = "uReveal";
     static final String U_AOD2OPACITY = "uAod2Opacity";
     static final String U_TEXTURE = "uTexture";
@@ -87,7 +87,7 @@
     private int mAttrPosition;
     private int mAttrTextureCoordinates;
     private int mUniAod2Opacity;
-    private int mUniCenterReveal;
+    private int mUniPer85;
     private int mUniReveal;
     private int mUniTexture;
     private int mTextureId;
@@ -131,7 +131,7 @@
 
     private void setupUniforms() {
         mUniAod2Opacity = mProgram.getUniformHandle(U_AOD2OPACITY);
-        mUniCenterReveal = mProgram.getUniformHandle(U_CENTER_REVEAL);
+        mUniPer85 = mProgram.getUniformHandle(U_PER85);
         mUniReveal = mProgram.getUniformHandle(U_REVEAL);
         mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
     }
@@ -144,8 +144,8 @@
                 return mAttrTextureCoordinates;
             case U_AOD2OPACITY:
                 return mUniAod2Opacity;
-            case U_CENTER_REVEAL:
-                return mUniCenterReveal;
+            case U_PER85:
+                return mUniPer85;
             case U_REVEAL:
                 return mUniReveal;
             case U_TEXTURE:
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 991b116..7295008 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -93,13 +93,13 @@
 
     @Override
     public void onDrawFrame(GL10 gl) {
-        float threshold = mImageProcessHelper.getPercentile85();
+        float per85 = mImageProcessHelper.getPercentile85();
         float reveal = mImageRevealHelper.getReveal();
 
         glClear(GL_COLOR_BUFFER_BIT);
 
         glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1);
-        glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_CENTER_REVEAL), threshold);
+        glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), per85);
         glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal);
 
         mWallpaper.useTexture();
diff --git a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
new file mode 100644
index 0000000..d7a2d9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.power
+
+import com.android.systemui.power.PowerUI.NO_ESTIMATE_AVAILABLE
+
+/**
+ * A simple data class to snapshot battery state when a particular check for the
+ * low battery warning is running in the background.
+ */
+data class BatteryStateSnapshot(
+    val batteryLevel: Int,
+    val isPowerSaver: Boolean,
+    val plugged: Boolean,
+    val bucket: Int,
+    val batteryStatus: Int,
+    val severeLevelThreshold: Int,
+    val lowLevelThreshold: Int,
+    val timeRemainingMillis: Long,
+    val severeThresholdMillis: Long,
+    val lowThresholdMillis: Long,
+    val isBasedOnUsage: Boolean
+) {
+    /**
+     * Returns whether hybrid warning logic/copy should be used for this snapshot
+     */
+    var isHybrid: Boolean = false
+        private set
+
+    init {
+        this.isHybrid = true
+    }
+
+    constructor(
+        batteryLevel: Int,
+        isPowerSaver: Boolean,
+        plugged: Boolean,
+        bucket: Int,
+        batteryStatus: Int,
+        severeLevelThreshold: Int,
+        lowLevelThreshold: Int
+    ) : this(
+        batteryLevel,
+        isPowerSaver,
+        plugged,
+        bucket,
+        batteryStatus,
+        severeLevelThreshold,
+        lowLevelThreshold,
+        NO_ESTIMATE_AVAILABLE.toLong(),
+        NO_ESTIMATE_AVAILABLE.toLong(),
+        NO_ESTIMATE_AVAILABLE.toLong(),
+        false
+    ) {
+        this.isHybrid = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
deleted file mode 100644
index 12a8f0a..0000000
--- a/packages/SystemUI/src/com/android/systemui/power/Estimate.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.android.systemui.power;
-
-public class Estimate {
-    public final long estimateMillis;
-    public final boolean isBasedOnUsage;
-
-    public Estimate(long estimateMillis, boolean isBasedOnUsage) {
-        this.estimateMillis = estimateMillis;
-        this.isBasedOnUsage = isBasedOnUsage;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.kt b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt
new file mode 100644
index 0000000..dca0d45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt
@@ -0,0 +1,3 @@
+package com.android.systemui.power
+
+data class Estimate(val estimateMillis: Long, val isBasedOnUsage: Boolean)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index fdb0b36..41bcab5 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -134,10 +134,6 @@
     private int mShowing;
 
     private long mWarningTriggerTimeMs;
-
-    private Estimate mEstimate;
-    private long mLowWarningThreshold;
-    private long mSevereWarningThreshold;
     private boolean mWarning;
     private boolean mShowAutoSaverSuggestion;
     private boolean mPlaySound;
@@ -148,6 +144,7 @@
     private SystemUIDialog mHighTempDialog;
     private SystemUIDialog mThermalShutdownDialog;
     @VisibleForTesting SystemUIDialog mUsbHighTempDialog;
+    private BatteryStateSnapshot mCurrentBatterySnapshot;
 
     /**
      */
@@ -195,17 +192,8 @@
     }
 
     @Override
-    public void updateEstimate(Estimate estimate) {
-        mEstimate = estimate;
-        if (estimate.estimateMillis <= mLowWarningThreshold) {
-            mWarningTriggerTimeMs = System.currentTimeMillis();
-        }
-    }
-
-    @Override
-    public void updateThresholds(long lowThreshold, long severeThreshold) {
-        mLowWarningThreshold = lowThreshold;
-        mSevereWarningThreshold = severeThreshold;
+    public void updateSnapshot(BatteryStateSnapshot snapshot) {
+        mCurrentBatterySnapshot = snapshot;
     }
 
     private void updateNotification() {
@@ -254,15 +242,17 @@
 
     protected void showWarningNotification() {
         final String percentage = NumberFormat.getPercentInstance()
-                .format((double) mBatteryLevel / 100.0);
+                .format((double) mCurrentBatterySnapshot.getBatteryLevel() / 100.0);
 
-        // get standard notification copy
+        // get shared standard notification copy
         String title = mContext.getString(R.string.battery_low_title);
-        String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+        String contentText;
 
-        // override notification copy if hybrid notification enabled
-        if (mEstimate != null) {
+        // get correct content text if notification is hybrid or not
+        if (mCurrentBatterySnapshot.isHybrid()) {
             contentText = getHybridContentString(percentage);
+        } else {
+            contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
         }
 
         final Notification.Builder nb =
@@ -282,8 +272,9 @@
         }
         // Make the notification red if the percentage goes below a certain amount or the time
         // remaining estimate is disabled
-        if (mEstimate == null || mBucket < 0
-                || mEstimate.estimateMillis < mSevereWarningThreshold) {
+        if (!mCurrentBatterySnapshot.isHybrid() || mBucket < 0
+                || mCurrentBatterySnapshot.getTimeRemainingMillis()
+                        < mCurrentBatterySnapshot.getSevereThresholdMillis()) {
             nb.setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError));
         }
 
@@ -324,10 +315,10 @@
 
     private String getHybridContentString(String percentage) {
         return PowerUtil.getBatteryRemainingStringFormatted(
-            mContext,
-            mEstimate.estimateMillis,
-            percentage,
-            mEstimate.isBasedOnUsage);
+                mContext,
+                mCurrentBatterySnapshot.getTimeRemainingMillis(),
+                percentage,
+                mCurrentBatterySnapshot.isBasedOnUsage());
     }
 
     private PendingIntent pendingBroadcast(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index e27c25e..1863860 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -55,6 +55,7 @@
 import java.util.concurrent.Future;
 
 public class PowerUI extends SystemUI {
+
     static final String TAG = "PowerUI";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
@@ -63,6 +64,7 @@
     static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
     private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
     private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();
+    public static final int NO_ESTIMATE_AVAILABLE = -1;
 
     private final Handler mHandler = new Handler();
     @VisibleForTesting
@@ -71,13 +73,9 @@
     private PowerManager mPowerManager;
     private WarningsUI mWarnings;
     private final Configuration mLastConfiguration = new Configuration();
-    private long mTimeRemaining = Long.MAX_VALUE;
     private int mPlugType = 0;
     private int mInvalidCharger = 0;
     private EnhancedEstimates mEnhancedEstimates;
-    private Estimate mLastEstimate;
-    private boolean mLowWarningShownThisChargeCycle;
-    private boolean mSevereWarningShownThisChargeCycle;
     private Future mLastShowWarningTask;
     private boolean mEnableSkinTemperatureWarning;
     private boolean mEnableUsbTemperatureAlarm;
@@ -87,6 +85,10 @@
 
     private long mScreenOffTime = -1;
 
+    @VisibleForTesting boolean mLowWarningShownThisChargeCycle;
+    @VisibleForTesting boolean mSevereWarningShownThisChargeCycle;
+    @VisibleForTesting BatteryStateSnapshot mCurrentBatteryStateSnapshot;
+    @VisibleForTesting BatteryStateSnapshot mLastBatteryStateSnapshot;
     @VisibleForTesting IThermalService mThermalService;
 
     @VisibleForTesting int mBatteryLevel = 100;
@@ -205,6 +207,7 @@
                 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
                 final int oldInvalidCharger = mInvalidCharger;
                 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
+                mLastBatteryStateSnapshot = mCurrentBatteryStateSnapshot;
 
                 final boolean plugged = mPlugType != 0;
                 final boolean oldPlugged = oldPlugType != 0;
@@ -233,16 +236,22 @@
                     mWarnings.dismissInvalidChargerWarning();
                 } else if (mWarnings.isInvalidChargerWarningShowing()) {
                     // if invalid charger is showing, don't show low battery
+                    if (DEBUG) {
+                        Slog.d(TAG, "Bad Charger");
+                    }
                     return;
                 }
 
                 // Show the correct version of low battery warning if needed
                 if (mLastShowWarningTask != null) {
                     mLastShowWarningTask.cancel(true);
+                    if (DEBUG) {
+                        Slog.d(TAG, "cancelled task");
+                    }
                 }
                 mLastShowWarningTask = ThreadUtils.postOnBackgroundThread(() -> {
-                    maybeShowBatteryWarning(
-                            oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
+                    maybeShowBatteryWarningV2(
+                            plugged, bucket);
                 });
 
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -257,53 +266,150 @@
         }
     }
 
-    protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
-            int oldBucket, int bucket) {
-        boolean isPowerSaver = mPowerManager.isPowerSaveMode();
-        // only play SFX when the dialog comes up or the bucket changes
-        final boolean playSound = bucket != oldBucket || oldPlugged;
+    protected void maybeShowBatteryWarningV2(boolean plugged, int bucket) {
         final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
-        if (hybridEnabled) {
-            Estimate estimate = mLastEstimate;
-            if (estimate == null || mBatteryLevel != oldBatteryLevel) {
-                estimate = mEnhancedEstimates.getEstimate();
-                mLastEstimate = estimate;
-            }
-            // Turbo is not always booted once SysUI is running so we have to make sure we actually
-            // get data back
-            if (estimate != null) {
-                mTimeRemaining = estimate.estimateMillis;
-                mWarnings.updateEstimate(estimate);
-                mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
-                        mEnhancedEstimates.getSevereWarningThreshold());
+        final boolean isPowerSaverMode = mPowerManager.isPowerSaveMode();
 
-                // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
-                // notification again
-                if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
-                        && mTimeRemaining > SIX_HOURS_MILLIS) {
-                    mLowWarningShownThisChargeCycle = false;
-                    mSevereWarningShownThisChargeCycle = false;
-                }
+        // Stick current battery state into an immutable container to determine if we should show
+        // a warning.
+        if (DEBUG) {
+            Slog.d(TAG, "evaluating which notification to show");
+        }
+        if (hybridEnabled) {
+            if (DEBUG) {
+                Slog.d(TAG, "using hybrid");
+            }
+            Estimate estimate = refreshEstimateIfNeeded();
+            mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
+                    plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
+                    mLowBatteryReminderLevels[0], estimate.getEstimateMillis(),
+                    mEnhancedEstimates.getSevereWarningThreshold(),
+                    mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage());
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "using standard");
+            }
+            mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
+                    plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
+                    mLowBatteryReminderLevels[0]);
+        }
+
+        mWarnings.updateSnapshot(mCurrentBatteryStateSnapshot);
+        if (mCurrentBatteryStateSnapshot.isHybrid()) {
+            maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
+        } else {
+            maybeShowBatteryWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
+        }
+    }
+
+    // updates the time estimate if we don't have one or battery level has changed.
+    @VisibleForTesting
+    Estimate refreshEstimateIfNeeded() {
+        if (mLastBatteryStateSnapshot == null
+                || mLastBatteryStateSnapshot.getTimeRemainingMillis() == NO_ESTIMATE_AVAILABLE
+                || mBatteryLevel != mLastBatteryStateSnapshot.getBatteryLevel()) {
+            final Estimate estimate = mEnhancedEstimates.getEstimate();
+            if (DEBUG) {
+                Slog.d(TAG, "updated estimate: " + estimate.getEstimateMillis());
+            }
+            return estimate;
+        }
+        return new Estimate(mLastBatteryStateSnapshot.getTimeRemainingMillis(),
+                mLastBatteryStateSnapshot.isBasedOnUsage());
+    }
+
+    @VisibleForTesting
+    void maybeShowHybridWarning(BatteryStateSnapshot currentSnapshot,
+            BatteryStateSnapshot lastSnapshot) {
+        // if we are now over 45% battery & 6 hours remaining so we can trigger hybrid
+        // notification again
+        if (currentSnapshot.getBatteryLevel() >= CHARGE_CYCLE_PERCENT_RESET
+                && currentSnapshot.getTimeRemainingMillis() > SIX_HOURS_MILLIS) {
+            mLowWarningShownThisChargeCycle = false;
+            mSevereWarningShownThisChargeCycle = false;
+            if (DEBUG) {
+                Slog.d(TAG, "Charge cycle reset! Can show warnings again");
             }
         }
 
-        if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
-                mTimeRemaining, isPowerSaver, mBatteryStatus)) {
-            mWarnings.showLowBatteryWarning(playSound);
+        final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
+                || lastSnapshot.getPlugged();
 
+        if (shouldShowHybridWarning(currentSnapshot)) {
+            mWarnings.showLowBatteryWarning(playSound);
             // mark if we've already shown a warning this cycle. This will prevent the notification
             // trigger from spamming users by only showing low/critical warnings once per cycle
-            if (hybridEnabled) {
-                if (mTimeRemaining <= mEnhancedEstimates.getSevereWarningThreshold()
-                        || mBatteryLevel <= mLowBatteryReminderLevels[1]) {
-                    mSevereWarningShownThisChargeCycle = true;
-                    mLowWarningShownThisChargeCycle = true;
-                } else {
-                    mLowWarningShownThisChargeCycle = true;
+            if (currentSnapshot.getTimeRemainingMillis()
+                    <= currentSnapshot.getSevereLevelThreshold()
+                    || currentSnapshot.getBatteryLevel() <= mLowBatteryReminderLevels[1]) {
+                mSevereWarningShownThisChargeCycle = true;
+                mLowWarningShownThisChargeCycle = true;
+                if (DEBUG) {
+                    Slog.d(TAG, "Severe warning marked as shown this cycle");
                 }
+            } else {
+                Slog.d(TAG, "Low warning marked as shown this cycle");
+                mLowWarningShownThisChargeCycle = true;
             }
-        } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
-                isPowerSaver)) {
+
+        } else if (shouldDismissHybridWarning(currentSnapshot)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Dismissing warning");
+            }
+            mWarnings.dismissLowBatteryWarning();
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Updating warning");
+            }
+            mWarnings.updateLowBatteryWarning();
+        }
+    }
+
+    @VisibleForTesting
+    boolean shouldShowHybridWarning(BatteryStateSnapshot snapshot) {
+        if (snapshot.getPlugged()
+                || snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+            Slog.d(TAG, "can't show warning due to - plugged: " + snapshot.getPlugged()
+                    + " status unknown: "
+                    + (snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN));
+            return false;
+        }
+
+        // Only show the low warning once per charge cycle & no battery saver
+        final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
+                && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis()
+                || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold());
+
+        // Only show the severe warning once per charge cycle
+        final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
+                && (snapshot.getTimeRemainingMillis() < snapshot.getSevereThresholdMillis()
+                || snapshot.getBatteryLevel() <= snapshot.getSevereLevelThreshold());
+
+        final boolean canShow = canShowWarning || canShowSevereWarning;
+        if (DEBUG) {
+            Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:"
+                    + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
+                    + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
+                    + "\n" + snapshot.toString());
+        }
+        return canShow;
+    }
+
+    @VisibleForTesting
+    boolean shouldDismissHybridWarning(BatteryStateSnapshot snapshot) {
+        return snapshot.getPlugged()
+                || snapshot.getTimeRemainingMillis() > snapshot.getLowThresholdMillis();
+    }
+
+    protected void maybeShowBatteryWarning(
+            BatteryStateSnapshot currentSnapshot,
+            BatteryStateSnapshot lastSnapshot) {
+        final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
+                || lastSnapshot.getPlugged();
+
+        if (shouldShowLowBatteryWarning(currentSnapshot, lastSnapshot)) {
+            mWarnings.showLowBatteryWarning(playSound);
+        } else if (shouldDismissLowBatteryWarning(currentSnapshot, lastSnapshot)) {
             mWarnings.dismissLowBatteryWarning();
         } else {
             mWarnings.updateLowBatteryWarning();
@@ -311,64 +417,25 @@
     }
 
     @VisibleForTesting
-    boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
-            int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) {
-        if (mEnhancedEstimates.isHybridNotificationEnabled()) {
-            // triggering logic when enhanced estimate is available
-            return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus);
-        }
-        // legacy triggering logic
-        return !plugged
-                && !isPowerSaver
-                && (((bucket < oldBucket || oldPlugged) && bucket < 0))
-                && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+    boolean shouldShowLowBatteryWarning(
+            BatteryStateSnapshot currentSnapshot,
+            BatteryStateSnapshot lastSnapshot) {
+        return !currentSnapshot.getPlugged()
+                && !currentSnapshot.isPowerSaver()
+                && (((currentSnapshot.getBucket() < lastSnapshot.getBucket()
+                        || lastSnapshot.getPlugged())
+                && currentSnapshot.getBucket() < 0))
+                && currentSnapshot.getBatteryStatus() != BatteryManager.BATTERY_STATUS_UNKNOWN;
     }
 
     @VisibleForTesting
-    boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
-            long timeRemaining, boolean isPowerSaver) {
-        final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
-        final boolean hybridWouldDismiss = hybridEnabled
-                && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
-        final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
-        return (isPowerSaver && !hybridEnabled)
-                || plugged
-                || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
-                        || hybridWouldDismiss));
-    }
-
-    private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
-            int batteryStatus) {
-        if (plugged || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
-            return false;
-        }
-        int warnLevel = mLowBatteryReminderLevels[0];
-        int critLevel = mLowBatteryReminderLevels[1];
-
-        // Only show the low warning once per charge cycle & no battery saver
-        final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !isPowerSaver
-                && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
-                || mBatteryLevel <= warnLevel);
-
-        // Only show the severe warning once per charge cycle
-        final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
-                && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
-                || mBatteryLevel <= critLevel);
-
-        final boolean canShow = canShowWarning || canShowSevereWarning;
-        if (DEBUG) {
-            Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith values: "
-                    + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
-                    + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
-                    + " mEnhancedEstimates.timeremaining: " + timeRemaining
-                    + " mBatteryLevel: " + mBatteryLevel
-                    + " canShowWarning: " + canShowWarning
-                    + " canShowSevereWarning: " + canShowSevereWarning
-                    + " plugged: " + plugged
-                    + " batteryStatus: " + batteryStatus
-                    + " isPowerSaver: " + isPowerSaver);
-        }
-        return canShowWarning || canShowSevereWarning;
+    boolean shouldDismissLowBatteryWarning(
+            BatteryStateSnapshot currentSnapshot,
+            BatteryStateSnapshot lastSnapshot) {
+        return currentSnapshot.isPowerSaver()
+                || currentSnapshot.getPlugged()
+                || (currentSnapshot.getBucket() > lastSnapshot.getBucket()
+                        && currentSnapshot.getBucket() > 0);
     }
 
     private void initTemperature() {
@@ -453,13 +520,21 @@
         mWarnings.dump(pw);
     }
 
+    /**
+     * The interface to allow PowerUI to communicate with whatever implementation of WarningsUI
+     * is being used by the system.
+     */
     public interface WarningsUI {
+
+        /**
+         * Updates battery and screen info for determining whether to trigger battery warnings or
+         * not.
+         * @param batteryLevel The current battery level
+         * @param bucket The current battery bucket
+         * @param screenOffTime How long the screen has been off in millis
+         */
         void update(int batteryLevel, int bucket, long screenOffTime);
 
-        void updateEstimate(Estimate estimate);
-
-        void updateThresholds(long lowThreshold, long severeThreshold);
-
         void dismissLowBatteryWarning();
 
         void showLowBatteryWarning(boolean playSound);
@@ -486,6 +561,12 @@
         void dump(PrintWriter pw);
 
         void userSwitched();
+
+        /**
+         * Updates the snapshot of battery state used for evaluating battery warnings
+         * @param snapshot object containing relevant values for making battery warning decisions.
+         */
+        void updateSnapshot(BatteryStateSnapshot snapshot);
     }
 
     // Thermal event received from thermal service manager subsystem
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index af3c96f..3fa3e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -226,7 +226,7 @@
 
         String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
         return PowerUtil.getBatteryRemainingShortStringFormatted(
-                mContext, mEstimate.estimateMillis);
+                mContext, mEstimate.getEstimateMillis());
     }
 
     private void updateEstimateInBackground() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 5876ae1..58c9311 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -30,6 +30,7 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.os.BatteryManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +58,9 @@
         // Test Instance.
         mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
         mPowerNotificationWarnings = new PowerNotificationWarnings(mContext);
+        BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
+                BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
+        mPowerNotificationWarnings.updateSnapshot(snapshot);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 0aed63d..f51e473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -17,8 +17,7 @@
 import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
 import static android.provider.Settings.Global.SHOW_USB_TEMPERATURE_ALARM;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.anyObject;
@@ -29,7 +28,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.content.Intent;
 import android.os.BatteryManager;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
@@ -42,22 +40,20 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
 
-import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.statusbar.phone.StatusBar;
 
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.time.Duration;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -75,6 +71,7 @@
     private static final int OLD_BATTERY_LEVEL_NINE = 9;
     private static final int OLD_BATTERY_LEVEL_10 = 10;
     private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15);
+    public static final int BATTERY_LEVEL_10 = 10;
     private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
     private EnhancedEstimates mEnhancedEstimates;
@@ -176,368 +173,333 @@
     }
 
     @Test
-    public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold())
-                .thenReturn(Duration.ofHours(1).toMillis());
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+    public void testMaybeShowHybridWarning() {
         mPowerUI.start();
 
-        // unplugged device that would not show the non-hybrid notification but would show the
-        // hybrid but the threshold has been overriden to be too low
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
+        // verify low warning shown this cycle noticed
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+        BatteryStateSnapshot lastState = state.get();
+        state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+        state.mBatteryLevel = 15;
+
+        mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+        assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
+        assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
+
+        // verify severe warning noticed this cycle
+        lastState = state.get();
+        state.mBatteryLevel = 1;
+        state.mTimeRemainingMillis = Duration.ofMinutes(10).toMillis();
+
+        mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+        assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
+        assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isTrue();
+
+        // verify getting past threshold resets values
+        lastState = state.get();
+        state.mBatteryLevel = 100;
+        state.mTimeRemainingMillis = Duration.ofDays(1).toMillis();
+
+        mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+        assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isFalse();
+        assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
     }
 
     @Test
-    public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold())
-                .thenReturn(Duration.ofHours(5).toMillis());
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+    public void testShouldShowHybridWarning_lowLevelWarning() {
         mPowerUI.start();
+        mPowerUI.mLowWarningShownThisChargeCycle = false;
+        mPowerUI.mSevereWarningShownThisChargeCycle = false;
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
 
-        // unplugged device that would not show the non-hybrid notification but would show the
-        // hybrid since the threshold has been overriden to be much higher
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertTrue(shouldShow);
+        // sanity check to make sure we can show for a valid config
+        state.mBatteryLevel = 10;
+        state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+        boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Shouldn't show if plugged in
+        state.mPlugged = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Shouldn't show if battery is unknown
+        state.mPlugged = false;
+        state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+        // Already shown both warnings
+        mPowerUI.mLowWarningShownThisChargeCycle = true;
+        mPowerUI.mSevereWarningShownThisChargeCycle = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Can show low warning
+        mPowerUI.mLowWarningShownThisChargeCycle = false;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Can't show if above the threshold for time & battery
+        state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
+        state.mBatteryLevel = 100;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Battery under low percentage threshold but not time
+        state.mBatteryLevel = 10;
+        state.mLowLevelThreshold = 50;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Should also trigger if both level and time remaining under low threshold
+        state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // battery saver should block the low level warning though
+        state.mIsPowerSaver = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
     }
 
     @Test
-    public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+    public void testShouldShowHybridWarning_severeLevelWarning() {
         mPowerUI.start();
+        mPowerUI.mLowWarningShownThisChargeCycle = false;
+        mPowerUI.mSevereWarningShownThisChargeCycle = false;
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
 
-        // unplugged device that would not show the non-hybrid notification but would show the
-        // hybrid
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertTrue(shouldShow);
+        // sanity check to make sure we can show for a valid config
+        state.mBatteryLevel = 1;
+        state.mTimeRemainingMillis = Duration.ofMinutes(1).toMillis();
+        boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Shouldn't show if plugged in
+        state.mPlugged = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Shouldn't show if battery is unknown
+        state.mPlugged = false;
+        state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+        // Already shown both warnings
+        mPowerUI.mLowWarningShownThisChargeCycle = true;
+        mPowerUI.mSevereWarningShownThisChargeCycle = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Can show severe warning
+        mPowerUI.mSevereWarningShownThisChargeCycle = false;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Can't show if above the threshold for time & battery
+        state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
+        state.mBatteryLevel = 100;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
+
+        // Battery under low percentage threshold but not time
+        state.mBatteryLevel = 1;
+        state.mSevereLevelThreshold = 5;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // Should also trigger if both level and time remaining under low threshold
+        state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
+
+        // battery saver should not block the severe level warning though
+        state.mIsPowerSaver = true;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isTrue();
     }
 
     @Test
-    public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.mBatteryLevel = 10;
+    public void testShouldDismissHybridWarning() {
         mPowerUI.start();
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
 
-        // unplugged device that would show the non-hybrid notification and the hybrid
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertTrue(shouldShow);
+        // We should dismiss if the device is plugged in
+        state.mPlugged = true;
+        state.mTimeRemainingMillis = Duration.ofHours(1).toMillis();
+        state.mLowThresholdMillis = Duration.ofHours(2).toMillis();
+        boolean shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+        assertThat(shouldDismiss).isTrue();
+
+        // If not plugged in and below the threshold we should not dismiss
+        state.mPlugged = false;
+        shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+        assertThat(shouldDismiss).isFalse();
+
+        // If we go over the low warning threshold we should dismiss
+        state.mTimeRemainingMillis = Duration.ofHours(3).toMillis();
+        shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+        assertThat(shouldDismiss).isTrue();
     }
 
     @Test
-    public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.mBatteryLevel = 10;
-        mPowerUI.start();
-
-        // unplugged device that would show the non-hybrid but not the hybrid
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertTrue(shouldShow);
-    }
-
-    @Test
-    public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.start();
-
-        // unplugged device that would show the neither due to battery level being good
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
-    }
-
-    @Test
-    public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.start();
-
-        // plugged device that would show the neither due to being plugged
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
-   }
-
-    @Test
-    public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnknown_returnsNoShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.start();
-
-        // Unknown battery status device that would show the neither due to the battery status being
-        // unknown
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
-        assertFalse(shouldShow);
-    }
-
-    @Test
-    public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        mPowerUI.start();
-
-        // BatterySaverEnabled device that would show the neither due to battery saver
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
-    }
-
-    @Test
-    public void testShouldShowLowBatteryWarning_onlyShowsOncePerChargeCycle() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        when(mEnhancedEstimates.getEstimate())
-                .thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
-        mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
-
-        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
-                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-
-        // reduce battery level to handle time based trigger -> level trigger interactions
-        mPowerUI.mBatteryLevel = 10;
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabledLegacy() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // device that gets power saver turned on should dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
-        assertTrue(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldNotDismissLowBatteryWarning_dismissWhenPowerSaverEnabledHybrid() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // device that gets power saver turned on should dismiss
-        boolean shouldDismiss =
-            mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
-        assertFalse(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // device that gets plugged in should dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertTrue(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // would dismiss hybrid but not non-hybrid should not dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertFalse(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // would dismiss non-hybrid but not hybrid should not dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertFalse(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // should not dismiss when both would not dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertFalse(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        //should dismiss if both would dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertTrue(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
-        // would dismiss non-hybrid with hybrid disabled should dismiss
-        boolean shouldDismiss =
-                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
-        assertTrue(shouldDismiss);
-    }
-
-    @Test
-    public void testShouldDismissLowBatteryWarning_powerSaverModeEnabled()
-            throws InterruptedException {
-        when(mPowerManager.isPowerSaveMode()).thenReturn(true);
-
-        mPowerUI.start();
-        mPowerUI.mReceiver.onReceive(mContext,
-                new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-
-        CountDownLatch latch = new CountDownLatch(1);
-        ThreadUtils.postOnBackgroundThread(() -> latch.countDown());
-        latch.await(5, TimeUnit.SECONDS);
-
-        verify(mMockWarnings).dismissLowBatteryWarning();
-    }
-
-    @Test
-    public void testShouldNotDismissLowBatteryWarning_powerSaverModeDisabled()
-            throws InterruptedException {
-        when(mPowerManager.isPowerSaveMode()).thenReturn(false);
-
-        mPowerUI.start();
-        mPowerUI.mReceiver.onReceive(mContext,
-                new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-
-        CountDownLatch latch = new CountDownLatch(1);
-        ThreadUtils.postOnBackgroundThread(() -> latch.countDown());
-        latch.await(5, TimeUnit.SECONDS);
-
-        verify(mMockWarnings, never()).dismissLowBatteryWarning();
-    }
-
-    @Test
-    public void testSevereWarning_countsAsLowAndSevere_WarningOnlyShownOnce() {
-        mPowerUI.start();
-        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
-        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
-        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-        when(mEnhancedEstimates.getEstimate())
-                .thenReturn(new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true));
-        mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
-
-        // reduce battery level to handle time based trigger -> level trigger interactions
-        mPowerUI.mBatteryLevel = 5;
-        boolean shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, BELOW_SEVERE_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertTrue(shouldShow);
-
-        // actually run the end to end since it handles changing the internal state.
-        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
-                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-
-        shouldShow =
-                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
-                        ABOVE_WARNING_BUCKET, VERY_BELOW_SEVERE_HYBRID_THRESHOLD,
-                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
-        assertFalse(shouldShow);
-    }
-
-    @Test
-    public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
+    public void testRefreshEstimateIfNeeded_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
         mPowerUI.start();
         Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
         when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
         when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
         when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
-        mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+        mPowerUI.mBatteryLevel = 10;
 
-        // we expect that the first time it will query even if the level is the same
+        // we expect that the first time it will query since there is no last battery snapshot.
+        // However an invalid estimate (-1) is returned.
+        Estimate refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+        assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
+        BatteryStateSnapshot snapshot = new BatteryStateSnapshot(
+                BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD,
+                0, 0, -1, 0, 0, false);
+        mPowerUI.mLastBatteryStateSnapshot = snapshot;
+
+        // query again since the estimate was -1
+        estimate = new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true);
+        when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+        refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+        assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
+        snapshot = new BatteryStateSnapshot(
+                BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0,
+                0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false);
+        mPowerUI.mLastBatteryStateSnapshot = snapshot;
+
+        // Battery level hasn't changed, so we don't query again
+        estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
+        when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+        refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+        assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
+
+        // Battery level changes so we update again
         mPowerUI.mBatteryLevel = 9;
-        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
-                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-        verify(mEnhancedEstimates, times(1)).getEstimate();
+        refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+        assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
+    }
 
-        // We should NOT query again if the battery level hasn't changed
-        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
-                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-        verify(mEnhancedEstimates, times(1)).getEstimate();
+    @Test
+    public void testShouldShowStandardWarning() {
+        mPowerUI.start();
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+        state.mIsHybrid = false;
+        BatteryStateSnapshot lastState = state.get();
 
-        // Battery level has changed, so we should query again
-        mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
-                ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-        verify(mEnhancedEstimates, times(2)).getEstimate();
+        // sanity check to make sure we can show for a valid config
+        state.mBatteryLevel = 10;
+        state.mBucket = -1;
+        boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isTrue();
+        lastState = state.get();
+
+        // Shouldn't show if plugged in
+        state.mPlugged = true;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isFalse();
+
+        state.mPlugged = false;
+        // Shouldn't show if battery saver
+        state.mIsPowerSaver = true;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isFalse();
+
+        state.mIsPowerSaver = false;
+        // Shouldn't show if battery is unknown
+        state.mPlugged = false;
+        state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isFalse();
+
+        state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+        // show if plugged -> unplugged, bucket -1 -> -1
+        state.mPlugged = true;
+        state.mBucket = -1;
+        lastState = state.get();
+        state.mPlugged = false;
+        state.mBucket = -1;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isTrue();
+
+        // don't show if plugged -> unplugged, bucket 0 -> 0
+        state.mPlugged = true;
+        state.mBucket = 0;
+        lastState = state.get();
+        state.mPlugged = false;
+        state.mBucket = 0;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isFalse();
+
+        // show if unplugged -> unplugged, bucket 0 -> -1
+        state.mPlugged = false;
+        state.mBucket = 0;
+        lastState = state.get();
+        state.mPlugged = false;
+        state.mBucket = -1;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isTrue();
+
+        // don't show if unplugged -> unplugged, bucket -1 -> 1
+        state.mPlugged = false;
+        state.mBucket = -1;
+        lastState = state.get();
+        state.mPlugged = false;
+        state.mBucket = 1;
+        shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldShow).isFalse();
+    }
+
+    @Test
+    public void testShouldDismissStandardWarning() {
+        mPowerUI.start();
+        BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+        state.mIsHybrid = false;
+        BatteryStateSnapshot lastState = state.get();
+
+        // should dismiss if battery saver
+        state.mIsPowerSaver = true;
+        boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldDismiss).isTrue();
+
+        state.mIsPowerSaver = false;
+        // should dismiss if plugged
+        state.mPlugged = true;
+        shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldDismiss).isTrue();
+
+        state.mPlugged = false;
+        // should dismiss if bucket 0 -> 1
+        state.mBucket = 0;
+        lastState = state.get();
+        state.mBucket = 1;
+        shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldDismiss).isTrue();
+
+        // shouldn't dismiss if bucket -1 -> 0
+        state.mBucket = -1;
+        lastState = state.get();
+        state.mBucket = 0;
+        shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldDismiss).isFalse();
+
+        // should dismiss if powersaver & bucket 0 -> 1
+        state.mIsPowerSaver = true;
+        state.mBucket = 0;
+        lastState = state.get();
+        state.mBucket = 1;
+        shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+        assertThat(shouldDismiss).isTrue();
     }
 
     private Temperature getEmergencyStatusTemp(int type, String name) {
@@ -556,4 +518,35 @@
         mPowerUI.mComponents = mContext.getComponents();
         mPowerUI.mThermalService = mThermalServiceMock;
     }
+
+    /**
+     * A simple wrapper class that sets values by default and makes them not final to improve
+     * test clarity.
+     */
+    private class BatteryStateSnapshotWrapper {
+        public int mBatteryLevel = 100;
+        public boolean mIsPowerSaver = false;
+        public boolean mPlugged = false;
+        public long mSevereThresholdMillis = Duration.ofHours(1).toMillis();
+        public long mLowThresholdMillis = Duration.ofHours(3).toMillis();
+        public int mSevereLevelThreshold = 5;
+        public int mLowLevelThreshold = 15;
+        public int mBucket = 1;
+        public int mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+        public long mTimeRemainingMillis = Duration.ofHours(24).toMillis();
+        public boolean mIsBasedOnUsage = true;
+        public boolean mIsHybrid = true;
+
+        public BatteryStateSnapshot get() {
+            if (mIsHybrid) {
+                return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
+                        mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold,
+                        mTimeRemainingMillis, mSevereThresholdMillis, mLowThresholdMillis,
+                        mIsBasedOnUsage);
+            } else {
+                return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
+                        mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold);
+            }
+        }
+    }
 }
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 0a6e923..91b161d 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7078,6 +7078,15 @@
     // Open: Settings will show the conditional when Grayscale mode is on
     SETTINGS_CONDITION_GRAYSCALE_MODE = 1683;
 
+    // ACTION: Individual contextual card loading time
+    ACTION_CONTEXTUAL_CARD_LOAD = 1684;
+
+    //ACTION: Contextual card loading timeout
+    ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT = 1685;
+
+    //ACTION: Log result for each card's eligibility check
+    ACTION_CONTEXTUAL_CARD_ELIGIBILITY = 1686;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
index a18686d..9b70272 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.contentsuggestions;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -73,6 +74,13 @@
             throw new PackageManager.NameNotFoundException(
                     "Could not get service for " + serviceComponent);
         }
+        if (!Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE.equals(si.permission)) {
+            Slog.w(TAG, "ContentSuggestionsService from '" + si.packageName
+                    + "' does not require permission "
+                    + Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE);
+            throw new SecurityException("Service does not require permission "
+                    + Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE);
+        }
         return si;
     }
 
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 18c2722..d266635 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -38,9 +38,11 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.am.SettingsToPropertiesMapper;
 import com.android.server.utils.FlagNamespaceUtils;
 
 import java.io.File;
+import java.util.Arrays;
 
 /**
  * Utilities to help rescue the system from crash loops. Callers are expected to
@@ -158,6 +160,7 @@
      * opportunity to reset any settings depending on our rescue level.
      */
     public static void onSettingsProviderPublished(Context context) {
+        handleNativeRescuePartyResets();
         executeRescueLevel(context);
     }
 
@@ -176,6 +179,13 @@
         return SystemClock.elapsedRealtime();
     }
 
+    private static void handleNativeRescuePartyResets() {
+        if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
+            FlagNamespaceUtils.resetDeviceConfig(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+                    Arrays.asList(SettingsToPropertiesMapper.getResetNativeCategories()));
+        }
+    }
+
     /**
      * Escalate to the next rescue level. After incrementing the level you'll
      * probably want to call {@link #executeRescueLevel(Context)}.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 415a892..f9fcef6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -27,6 +27,8 @@
 import android.provider.DeviceConfig.OnPropertyChangedListener;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
@@ -235,6 +237,8 @@
     // Controlled by Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED
     volatile boolean mFlagBackgroundActivityStartsEnabled;
 
+    volatile ArraySet<String> mPackageNamesWhitelistedForBgActivityStarts = new ArraySet<>();
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -273,6 +277,10 @@
                 Settings.Global.getUriFor(
                         Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED);
 
+    private static final Uri BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI =
+                Settings.Global.getUriFor(
+                        Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST);
+
     private final OnPropertyChangedListener mOnDeviceConfigChangedListener =
             new OnPropertyChangedListener() {
                 @Override
@@ -293,9 +301,12 @@
         mResolver.registerContentObserver(ACTIVITY_MANAGER_CONSTANTS_URI, false, this);
         mResolver.registerContentObserver(ACTIVITY_STARTS_LOGGING_ENABLED_URI, false, this);
         mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_ENABLED_URI, false, this);
+        mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI,
+                false, this);
         updateConstants();
         updateActivityStartsLoggingEnabled();
         updateBackgroundActivityStartsEnabled();
+        updateBackgroundActivityStartsPackageNamesWhitelist();
         DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 ActivityThread.currentApplication().getMainExecutor(),
                 mOnDeviceConfigChangedListener);
@@ -325,6 +336,8 @@
             updateActivityStartsLoggingEnabled();
         } else if (BACKGROUND_ACTIVITY_STARTS_ENABLED_URI.equals(uri)) {
             updateBackgroundActivityStartsEnabled();
+        } else if (BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI.equals(uri)) {
+            updateBackgroundActivityStartsPackageNamesWhitelist();
         }
     }
 
@@ -414,6 +427,21 @@
                 Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, 1) == 1;
     }
 
+    private void updateBackgroundActivityStartsPackageNamesWhitelist() {
+        final String setting = Settings.Global.getString(mResolver,
+                Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST);
+        if (TextUtils.isEmpty(setting)) {
+            return;
+        }
+        ArraySet<String> newSet = new ArraySet<>();
+        SimpleStringSplitter splitter = new SimpleStringSplitter(':');
+        splitter.setString(setting);
+        while (splitter.hasNext()) {
+            newSet.add(splitter.next());
+        }
+        mPackageNamesWhitelistedForBgActivityStarts = newSet;
+    }
+
     private void updateMaxCachedProcesses() {
         String maxCachedProcessesFlag = DeviceConfig.getProperty(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccb9d82..0a68ce0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17862,6 +17862,10 @@
             return mConstants.mFlagActivityStartsLoggingEnabled;
         }
 
+        public boolean isPackageNameWhitelistedForBgActivityStarts(String packageName) {
+            return mConstants.mPackageNamesWhitelistedForBgActivityStarts.contains(packageName);
+        }
+
         public boolean isBackgroundActivityStartsEnabled() {
             return mConstants.mFlagBackgroundActivityStartsEnabled;
         }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5da1ce6..194549f 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -167,7 +168,7 @@
      * booting.
      * @return
      */
-    public static String[] getResetNativeCategories() {
+    public static @NonNull String[] getResetNativeCategories() {
         if (!isNativeFlagsResetPerformed()) {
             return new String[0];
         }
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 280bc02..d723c7b 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -146,7 +146,7 @@
      * Whether history is enabled.
      */
     @GuardedBy("mInMemoryLock")
-    private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+    private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
 
     /**
      * This granularity has been chosen to allow clean delineation for intervals
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index f2e2782..a53797d 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -7,13 +7,7 @@
             "name": "FrameworksServicesTests",
             "options": [
                 {
-                    "include-filter": "com.android.server.appop.AppOpsUpgradeTest"
-                },
-                {
-                    "include-filter": "com.android.server.appop.AppOpsServiceTest"
-                },
-                {
-                    "include-filter": "com.android.server.appop.AppOpsActiveWatcherTest"
+                    "include-filter": "com.android.server.appop"
                 }
             ]
         }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 71ec5b0..9ab9975 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -142,6 +142,7 @@
 
     // these need to match ElapsedRealtimeFlags enum in types.hal
     private static final int ELAPSED_REALTIME_HAS_TIMESTAMP_NS = 1;
+    private static final int ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS = 2;
 
     // IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
     private static final int GPS_DELETE_EPHEMERIS = 0x0001;
@@ -768,15 +769,18 @@
         float bearingAccuracyDegrees = location.getBearingAccuracyDegrees();
         long timestamp = location.getTime();
 
-        int elapsedRealtimeFlags = ELAPSED_REALTIME_HAS_TIMESTAMP_NS;
+        int elapsedRealtimeFlags = ELAPSED_REALTIME_HAS_TIMESTAMP_NS
+                | (location.hasElapsedRealtimeUncertaintyNanos()
+                        ? ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS : 0);
         long elapsedRealtimeNanos = location.getElapsedRealtimeNanos();
+        long elapsedRealtimeUncertaintyNanos = location.getElapsedRealtimeUncertaintyNanos();
 
         native_inject_best_location(
                 gnssLocationFlags, latitudeDegrees, longitudeDegrees,
                 altitudeMeters, speedMetersPerSec, bearingDegrees,
                 horizontalAccuracyMeters, verticalAccuracyMeters,
                 speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
-                elapsedRealtimeFlags, elapsedRealtimeNanos);
+                elapsedRealtimeFlags, elapsedRealtimeNanos, elapsedRealtimeUncertaintyNanos);
     }
 
     /** Returns true if the location request is too frequent. */
@@ -2170,7 +2174,8 @@
             double altitudeMeters, float speedMetersPerSec, float bearingDegrees,
             float horizontalAccuracyMeters, float verticalAccuracyMeters,
             float speedAccuracyMetersPerSecond, float bearingAccuracyDegrees,
-            long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos);
+            long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos,
+            long elapsedRealtimeUncertaintyNanos);
 
     private native void native_inject_location(double latitude, double longitude, float accuracy);
 
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index f52f3a3..e241ba6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -599,6 +599,8 @@
                     }
                 }
             }
+
+            mPackageHealthObserver.onBootCompleted();
         });
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index d24f217..d8f07fe 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -26,6 +26,8 @@
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PowerManager;
@@ -39,6 +41,12 @@
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
 
@@ -50,14 +58,19 @@
 public final class RollbackPackageHealthObserver implements PackageHealthObserver {
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
-    private Context mContext;
-    private Handler mHandler;
+    private static final int INVALID_ROLLBACK_ID = -1;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final File mLastStagedRollbackIdFile;
 
     RollbackPackageHealthObserver(Context context) {
         mContext = context;
         HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
         handlerThread.start();
         mHandler = handlerThread.getThreadHandler();
+        File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
+        dataDir.mkdirs();
+        mLastStagedRollbackIdFile = new File(dataDir, "last-staged-rollback-id");
         PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
     }
 
@@ -112,15 +125,19 @@
                 int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                         RollbackManager.STATUS_FAILURE);
                 if (status == RollbackManager.STATUS_SUCCESS) {
-                    StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
-                            StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
-                            moduleMetadataPackage.getPackageName(),
-                            moduleMetadataPackage.getVersionCode());
                     if (rollback.isStaged()) {
                         int rollbackId = rollback.getRollbackId();
                         BroadcastReceiver listener =
-                                listenForStagedSessionReady(rollbackManager, rollbackId);
-                        handleStagedSessionChange(rollbackManager, rollbackId, listener);
+                                listenForStagedSessionReady(rollbackManager, rollbackId,
+                                        moduleMetadataPackage);
+                        handleStagedSessionChange(rollbackManager, rollbackId, listener,
+                                moduleMetadataPackage);
+                    } else {
+                        StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                                StatsLog
+                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+                                moduleMetadataPackage.getPackageName(),
+                                moduleMetadataPackage.getVersionCode());
                     }
                 } else {
                     StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
@@ -152,6 +169,71 @@
         PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
     }
 
+    /** Verifies the rollback state after a reboot. */
+    public void onBootCompleted() {
+        int rollbackId = popLastStagedRollbackId();
+        if (rollbackId == INVALID_ROLLBACK_ID) {
+            // No staged rollback before reboot
+            return;
+        }
+
+        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+        RollbackInfo rollback = null;
+        for (RollbackInfo info : rollbackManager.getRecentlyCommittedRollbacks()) {
+            if (rollbackId == info.getRollbackId()) {
+                rollback = info;
+                break;
+            }
+        }
+
+        if (rollback == null) {
+            Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
+            return;
+        }
+
+        String moduleMetadataPackageName = getModuleMetadataPackageName();
+        if (moduleMetadataPackageName == null) {
+            // Only log mainline staged rollbacks
+            return;
+        }
+
+        // Use the version of the metadata package that was installed before
+        // we rolled back for logging purposes.
+        VersionedPackage moduleMetadataPackage = null;
+        for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+            if (moduleMetadataPackageName.equals(packageRollback.getPackageName())) {
+                moduleMetadataPackage = packageRollback.getVersionRolledBackFrom();
+                break;
+            }
+        }
+
+        if (moduleMetadataPackage == null) {
+            // Only log mainline staged rollbacks
+            return;
+        }
+
+        int sessionId = rollback.getCommittedSessionId();
+        PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
+            return;
+        }
+        if (sessionInfo.isStagedSessionApplied()) {
+            StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                    StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+                    moduleMetadataPackage.getPackageName(),
+                    moduleMetadataPackage.getVersionCode());
+        } else if (sessionInfo.isStagedSessionReady()) {
+            // TODO: What do for staged session ready but not applied
+        } else {
+            StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                    StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+                    moduleMetadataPackage.getPackageName(),
+                    moduleMetadataPackage.getVersionCode());
+        }
+    }
+
     private Pair<RollbackInfo, Boolean> getAvailableRollback(RollbackManager rollbackManager,
             VersionedPackage failedPackage, VersionedPackage moduleMetadataPackage) {
         for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
@@ -174,12 +256,20 @@
         return null;
     }
 
-    private VersionedPackage getModuleMetadataPackage() {
+    private String getModuleMetadataPackageName() {
         String packageName = mContext.getResources().getString(
                 R.string.config_defaultModuleMetadataProvider);
         if (TextUtils.isEmpty(packageName)) {
             return null;
         }
+        return packageName;
+    }
+
+    private VersionedPackage getModuleMetadataPackage() {
+        String packageName = getModuleMetadataPackageName();
+        if (packageName == null) {
+            return null;
+        }
 
         try {
             return new VersionedPackage(packageName, mContext.getPackageManager().getPackageInfo(
@@ -191,12 +281,12 @@
     }
 
     private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
-            int rollbackId) {
+            int rollbackId, VersionedPackage moduleMetadataPackage) {
         BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 handleStagedSessionChange(rollbackManager,
-                        rollbackId, this /* BroadcastReceiver */);
+                        rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage);
             }
         };
         IntentFilter sessionUpdatedFilter =
@@ -206,7 +296,7 @@
     }
 
     private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
-            BroadcastReceiver listener) {
+            BroadcastReceiver listener, VersionedPackage moduleMetadataPackage) {
         PackageInstaller packageInstaller =
                 mContext.getPackageManager().getPackageInstaller();
         List<RollbackInfo> recentRollbacks =
@@ -220,11 +310,52 @@
                         packageInstaller.getSessionInfo(sessionId);
                 if (sessionInfo.isStagedSessionReady()) {
                     mContext.unregisterReceiver(listener);
+                    saveLastStagedRollbackId(rollbackId);
+                    StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                            StatsLog
+                            .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
+                            moduleMetadataPackage.getPackageName(),
+                            moduleMetadataPackage.getVersionCode());
                     mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
                 } else if (sessionInfo.isStagedSessionFailed()) {
+                    StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+                            StatsLog
+                            .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+                            moduleMetadataPackage.getPackageName(),
+                            moduleMetadataPackage.getVersionCode());
                     mContext.unregisterReceiver(listener);
                 }
             }
         }
     }
+
+    private void saveLastStagedRollbackId(int stagedRollbackId) {
+        try {
+            FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
+            PrintWriter pw = new PrintWriter(fos);
+            pw.println(stagedRollbackId);
+            pw.flush();
+            FileUtils.sync(fos);
+            pw.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to save last staged rollback id", e);
+            mLastStagedRollbackIdFile.delete();
+        }
+    }
+
+    private int popLastStagedRollbackId() {
+        int rollbackId = INVALID_ROLLBACK_ID;
+        if (!mLastStagedRollbackIdFile.exists()) {
+            return rollbackId;
+        }
+
+        try {
+            rollbackId = Integer.parseInt(
+                    IoUtils.readFileAsString(mLastStagedRollbackIdFile.getAbsolutePath()).trim());
+        } catch (IOException | NumberFormatException e) {
+            Slog.e(TAG, "Failed to retrieve last staged rollback id", e);
+        }
+        mLastStagedRollbackIdFile.delete();
+        return rollbackId;
+    }
 }
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index ef77140..a5d291f 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -27,12 +27,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
+import android.service.textclassifier.ITextClassifierCallback;
 import android.service.textclassifier.ITextClassifierService;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
 import android.service.textclassifier.TextClassifierService;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -132,7 +128,7 @@
     @Override
     public void onSuggestSelection(
             TextClassificationSessionId sessionId,
-            TextSelection.Request request, ITextSelectionCallback callback)
+            TextSelection.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
@@ -155,7 +151,7 @@
     @Override
     public void onClassifyText(
             TextClassificationSessionId sessionId,
-            TextClassification.Request request, ITextClassificationCallback callback)
+            TextClassification.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
@@ -178,7 +174,7 @@
     @Override
     public void onGenerateLinks(
             TextClassificationSessionId sessionId,
-            TextLinks.Request request, ITextLinksCallback callback)
+            TextLinks.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
@@ -241,7 +237,7 @@
     public void onDetectLanguage(
             TextClassificationSessionId sessionId,
             TextLanguage.Request request,
-            ITextLanguageCallback callback) throws RemoteException {
+            ITextClassifierCallback callback) throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
         validateInput(mContext, request.getCallingPackageName());
@@ -264,7 +260,7 @@
     public void onSuggestConversationActions(
             TextClassificationSessionId sessionId,
             ConversationActions.Request request,
-            IConversationActionsCallback callback) throws RemoteException {
+            ITextClassifierCallback callback) throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
         validateInput(mContext, request.getCallingPackageName());
diff --git a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
index f26121e..f8c7447 100644
--- a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
+++ b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.provider.DeviceConfig;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.RescueParty;
 
 import java.util.ArrayList;
@@ -41,20 +42,23 @@
     /**
      * Name of the special namespace in DeviceConfig table used for communicating resets.
      */
-    private static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace";
+    @VisibleForTesting
+    public static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace";
     /**
      * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY}, holding all known {@link
      * DeviceConfig} namespaces, as a {@link #DELIMITER} separated String. It's updated after the
      * first time flags are written to the new namespace in the {@link DeviceConfig}.
      */
-    private static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces";
+    @VisibleForTesting
+    public static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces";
     /**
      * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY} with integer counter
      * suffix added to it, holding {@link DeviceConfig} namespace value whose flags were recently
      * reset by the {@link RescueParty}. It's updated by {@link RescueParty} every time given
      * namespace flags are reset.
      */
-    private static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package";
+    @VisibleForTesting
+    public static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package";
     private static final String DELIMITER = ":";
     /**
      * Maximum value of the counter used in combination with {@link #RESET_PLATFORM_PACKAGE_FLAG}
@@ -97,11 +101,25 @@
      * Reset all namespaces in DeviceConfig with consumed resetMode.
      */
     public static void resetDeviceConfig(int resetMode) {
-        List<String> allKnownNamespaces = getAllKnownDeviceConfigNamespacesList();
-        for (String namespace : allKnownNamespaces) {
+        resetDeviceConfig(resetMode, getAllKnownDeviceConfigNamespacesList());
+    }
+
+    /**
+     * Reset all consumed namespaces in DeviceConfig with consumed resetMode.
+     */
+    public static void resetDeviceConfig(int resetMode, List<String> namespacesList) {
+        for (String namespace : namespacesList) {
             DeviceConfig.resetToDefaults(resetMode, namespace);
         }
-        addToKnownResetNamespaces(allKnownNamespaces);
+        addToKnownResetNamespaces(namespacesList);
+    }
+
+    /**
+     * Resets known reset namespaces flag counter for tests only.
+     */
+    @VisibleForTesting
+    public static void resetKnownResetNamespacesFlagCounterForTest() {
+        sKnownResetNamespacesFlagCounter = -1;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bb1e001..e53fde9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -474,7 +474,13 @@
         int wallpaperId;
 
         if (wallpaper.equals(mFallbackWallpaper)) {
-            extractDefaultImageWallpaperColors();
+            synchronized (mLock) {
+                if (mFallbackWallpaper.primaryColors != null) return;
+            }
+            final WallpaperColors colors = extractDefaultImageWallpaperColors();
+            synchronized (mLock) {
+                mFallbackWallpaper.primaryColors = colors;
+            }
             return;
         }
 
@@ -499,23 +505,7 @@
             }
         } else if (defaultImageWallpaper) {
             // There is no crop and source file because this is default image wallpaper.
-            try (final InputStream is =
-                         WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
-                if (is != null) {
-                    try {
-                        final BitmapFactory.Options options = new BitmapFactory.Options();
-                        final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
-                        if (bitmap != null) {
-                            colors = WallpaperColors.fromBitmap(bitmap);
-                            bitmap.recycle();
-                        }
-                    } catch (OutOfMemoryError e) {
-                        Slog.w(TAG, "Can't decode default wallpaper stream", e);
-                    }
-                }
-            } catch (IOException e) {
-                Slog.w(TAG, "Can't close default wallpaper stream", e);
-            }
+            colors = extractDefaultImageWallpaperColors();
         }
 
         if (colors == null) {
@@ -535,37 +525,41 @@
         }
     }
 
-    private void extractDefaultImageWallpaperColors() {
+    private WallpaperColors extractDefaultImageWallpaperColors() {
+        if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
+
         synchronized (mLock) {
-            if (mFallbackWallpaper.primaryColors != null) return;
+            if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors;
         }
 
-        if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
         WallpaperColors colors = null;
-        final InputStream is = WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM);
-        if (is != null) {
-            try {
-                final BitmapFactory.Options options = new BitmapFactory.Options();
-                final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
-                if (bitmap != null) {
-                    colors = WallpaperColors.fromBitmap(bitmap);
-                    bitmap.recycle();
-                }
-            } catch (OutOfMemoryError e) {
-                Slog.w(TAG, "Can't decode default wallpaper stream", e);
-            } finally {
-                IoUtils.closeQuietly(is);
+        try (InputStream is = WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
+            if (is == null) {
+                Slog.w(TAG, "Can't open default wallpaper stream");
+                return null;
             }
+
+            final BitmapFactory.Options options = new BitmapFactory.Options();
+            final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+            if (bitmap != null) {
+                colors = WallpaperColors.fromBitmap(bitmap);
+                bitmap.recycle();
+            }
+        } catch (OutOfMemoryError e) {
+            Slog.w(TAG, "Can't decode default wallpaper stream", e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Can't close default wallpaper stream", e);
         }
 
         if (colors == null) {
             Slog.e(TAG, "Extract default image wallpaper colors failed");
-            return;
+        } else {
+            synchronized (mLock) {
+                mCacheDefaultImageWallpaperColors = colors;
+            }
         }
 
-        synchronized (mLock) {
-            mFallbackWallpaper.primaryColors = colors;
-        }
+        return colors;
     }
 
     /**
@@ -815,6 +809,12 @@
     private final ComponentName mImageWallpaper;
 
     /**
+     * Default image wallpaper shall never changed after system service started, caching it when we
+     * first read the image file.
+     */
+    private WallpaperColors mCacheDefaultImageWallpaperColors;
+
+    /**
      * Name of the default wallpaper component; might be different from mImageWallpaper
      */
     private final ComponentName mDefaultWallpaperComponent;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9f04166..23bed7b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1009,6 +1009,12 @@
         if (mService.isDeviceOwner(callingPackage)) {
             return false;
         }
+        // don't abort if the callingPackage is temporarily whitelisted
+        if (mService.isPackageNameWhitelistedForBgActivityStarts(callingPackage)) {
+            Slog.w(TAG, "Background activity start for " + callingPackage
+                    + " temporarily whitelisted. This will not be supported in future Q builds.");
+            return false;
+        }
         // anything that has fallen through would currently be aborted
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d747198..3255bc6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5210,6 +5210,10 @@
         return mAmInternal.isBackgroundActivityStartsEnabled();
     }
 
+    boolean isPackageNameWhitelistedForBgActivityStarts(String packageName) {
+        return mAmInternal.isPackageNameWhitelistedForBgActivityStarts(packageName);
+    }
+
     void enableScreenAfterBoot(boolean booted) {
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
                 SystemClock.uptimeMillis());
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index d39f20c..298b664 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -491,6 +491,10 @@
         SET(ElapsedRealtimeNanos, location.elapsedRealtime.timestampNs);
     }
 
+    if (flags & ElapsedRealtimeFlags::HAS_TIME_UNCERTAINTY_NS) {
+        SET(ElapsedRealtimeUncertaintyNanos, location.elapsedRealtime.timeUncertaintyNs);
+    }
+
     return object.get();
 }
 
@@ -521,7 +525,8 @@
         jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
         jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
         jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
-        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos) {
+        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+        jlong elapsedRealtimeUncertaintyNanos) {
     GnssLocation_V2_0 location;
     location.v1_0 = createGnssLocation_V1_0(
             gnssLocationFlags, latitudeDegrees, longitudeDegrees, altitudeMeters,
@@ -531,6 +536,7 @@
 
     location.elapsedRealtime.flags = static_cast<uint16_t>(elapsedRealtimeFlags);
     location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
+    location.elapsedRealtime.timeUncertaintyNs = static_cast<uint64_t>(elapsedRealtimeUncertaintyNanos);
 
     return location;
 }
@@ -1887,7 +1893,8 @@
         jfloat bearingAccuracyDegrees,
         jlong timestamp,
         jint elapsedRealtimeFlags,
-        jlong elapsedRealtimeNanos) {
+        jlong elapsedRealtimeNanos,
+        jlong elapsedRealtimeUncertaintyNanos) {
     if (gnssHal_V2_0 != nullptr) {
         GnssLocation_V2_0 location = createGnssLocation_V2_0(
                 gnssLocationFlags,
@@ -1902,7 +1909,8 @@
                 bearingAccuracyDegrees,
                 timestamp,
                 elapsedRealtimeFlags,
-                elapsedRealtimeNanos);
+                elapsedRealtimeNanos,
+                elapsedRealtimeUncertaintyNanos);
         auto result = gnssHal_V2_0->injectBestLocation_2_0(location);
 
         if (!result.isOk() || !result) {
@@ -2813,7 +2821,7 @@
             android_location_GnssLocationProvider_read_nmea)},
     {"native_inject_time", "(JJI)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_time)},
-    {"native_inject_best_location", "(IDDDFFFFFFJIJ)V", reinterpret_cast<void *>(
+    {"native_inject_best_location", "(IDDDFFFFFFJIJJ)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_best_location)},
     {"native_inject_location", "(DDF)V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_inject_location)},
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a19d5d5..eba00812 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1881,19 +1881,6 @@
         mSystemServiceManager.startService(IncidentCompanionService.class);
         traceEnd();
 
-        if (safeMode) {
-            traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation");
-            mActivityManagerService.enterSafeMode();
-            // Disable the JIT for the system_server process
-            VMRuntime.getRuntime().disableJitCompilation();
-            traceEnd();
-        } else {
-            // Enable the JIT for the system_server process
-            traceBeginAndSlog("StartJitCompilation");
-            VMRuntime.getRuntime().startJitCompilation();
-            traceEnd();
-        }
-
         // MMS service broker
         traceBeginAndSlog("StartMmsService");
         mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index b13735c..36825af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -35,9 +35,12 @@
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.utils.FlagNamespaceUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -56,6 +59,10 @@
 public class RescuePartyTest {
     private static final int PERSISTENT_APP_UID = 12;
     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
+    private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
+    private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
+    private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
+            {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
 
     private MockitoSession mSession;
 
@@ -73,9 +80,11 @@
                 ExtendedMockito.mockitoSession().initMocks(
                         this)
                         .strictness(Strictness.LENIENT)
+                        .spyStatic(DeviceConfig.class)
                         .spyStatic(SystemProperties.class)
                         .spyStatic(Settings.Global.class)
                         .spyStatic(Settings.Secure.class)
+                        .spyStatic(SettingsToPropertiesMapper.class)
                         .spyStatic(RecoverySystem.class)
                         .spyStatic(RescueParty.class)
                         .startMocking();
@@ -121,8 +130,17 @@
                 }
         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
 
+        // Mock DeviceConfig
+        doAnswer((Answer<Boolean>) invocationOnMock -> true)
+                .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
+                        anyBoolean()));
+        doAnswer((Answer<Void>) invocationOnMock -> null)
+                .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
+
+
         doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
         RescueParty.resetAllThresholds();
+        FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest();
 
         SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
                 Integer.toString(RescueParty.LEVEL_NONE));
@@ -278,10 +296,32 @@
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
 
+    @Test
+    public void testNativeRescuePartyResets() {
+        doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
+        doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
+                () -> SettingsToPropertiesMapper.getResetNativeCategories());
+
+        RescueParty.onSettingsProviderPublished(mMockContext);
+
+        verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+                FAKE_NATIVE_NAMESPACE1));
+        verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+                FAKE_NATIVE_NAMESPACE2));
+
+        ExtendedMockito.verify(
+                () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
+                        FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 0,
+                        FAKE_NATIVE_NAMESPACE1, /*makeDefault=*/true));
+        ExtendedMockito.verify(
+                () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
+                        FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 1,
+                        FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
+    }
+
     private void verifySettingsResets(int resetMode) {
         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
-                resetMode,
-                UserHandle.USER_SYSTEM));
+                resetMode, UserHandle.USER_SYSTEM));
         verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
                 eq(resetMode), anyInt()));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 606ab31..d02db7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -561,7 +561,7 @@
         runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
     }
 
     /**
@@ -576,7 +576,7 @@
                 "disallowed_unsupportedUsecase_aborted", true,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
     }
 
     /**
@@ -591,61 +591,66 @@
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
                 Process.ROOT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", false,
                 Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
                 Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingUidHasVisibleWindow_notAborted", false,
                 UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingUidProcessStateTop_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_realCallingUidHasVisibleWindow_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, true, PROCESS_STATE_TOP + 1,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_realCallingUidProcessStateTop_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP,
-                false, false, false, false, false);
+                false, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_hasForegroundActivities_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                true, false, false, false, false);
+                true, false, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsRecents_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, true, false, false, false);
+                false, true, false, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsWhitelisted_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, true, false, false);
+                false, false, true, false, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callerIsInstrumentingWithBackgroundActivityStartPrivileges_notAborted",
                 false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, true, false);
+                false, false, false, true, false, false);
         runAndVerifyBackgroundActivityStartsSubtest(
                 "disallowed_callingPackageNameIsDeviceOwner_notAborted", false,
                 UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
                 UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
-                false, false, false, false, true);
+                false, false, false, false, true, false);
+        runAndVerifyBackgroundActivityStartsSubtest(
+                "disallowed_callingPackageNameIsTempWhitelisted_notAborted", false,
+                UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
+                UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+                false, false, false, false, false, true);
     }
 
     private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -654,7 +659,7 @@
             boolean hasForegroundActivities, boolean callerIsRecents,
             boolean callerIsTempWhitelisted,
             boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges,
-            boolean isCallingPackageNameDeviceOwner) {
+            boolean isCallingPackageNameDeviceOwner, boolean isCallingPackageTempWhitelisted) {
         // window visibility
         doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
                 .isAnyNonToastWindowVisibleForUid(callingUid);
@@ -680,11 +685,15 @@
         // caller is instrumenting with background activity starts privileges
         callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
                 callerIsInstrumentingWithBackgroundActivityStartPrivileges);
-        // calling package name is whitelisted
+        // calling package name is the device owner
         doReturn(isCallingPackageNameDeviceOwner).when(mService).isDeviceOwner(any());
+        // calling package name is temporarily whitelisted
+        doReturn(isCallingPackageTempWhitelisted).when(mService)
+                .isPackageNameWhitelistedForBgActivityStarts("com.whatever.dude");
 
         final ActivityOptions options = spy(ActivityOptions.makeBasic());
         ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK)
+                .setCallingPackage("com.whatever.dude")
                 .setCaller(caller)
                 .setCallingUid(callingUid)
                 .setRealCallingUid(realCallingUid)
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 9d46300..67477cf 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3829,6 +3829,42 @@
          */
         public static final String CARRIER_ID = "carrier_id";
 
+        /**
+         * The skip 464xlat flag. Flag works as follows.
+         * {@link #SKIP_464XLAT_DEFAULT}: the APN will skip only APN is IMS and no internet.
+         * {@link #SKIP_464XLAT_DISABLE}: the APN will NOT skip 464xlat
+         * {@link #SKIP_464XLAT_ENABLE}: the APN will skip 464xlat
+         * <p>Type: INTEGER</p>
+         *
+         * @hide
+         */
+        public static final String SKIP_464XLAT = "skip_464xlat";
+
+        /**
+         * Possible value for the {@link #SKIP_464XLAT} field.
+         * <p>Type: INTEGER</p>
+         *
+         * @hide
+         */
+        public static final int SKIP_464XLAT_DEFAULT = -1;
+
+        /**
+         * Possible value for the {@link #SKIP_464XLAT} field.
+         * <p>Type: INTEGER</p>
+         *
+         * @hide
+         */
+        public static final int SKIP_464XLAT_DISABLE = 0;
+
+        /**
+         * Possible value for the {@link #SKIP_464XLAT} field.
+         * <p>Type: INTEGER</p>
+         *
+         * @hide
+         */
+        public static final int SKIP_464XLAT_ENABLE = 1;
+
+
         /** @hide */
         @IntDef({
                 UNEDITED,
@@ -3839,6 +3875,16 @@
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface EditStatus {}
+
+        /** @hide */
+        @IntDef({
+                SKIP_464XLAT_DEFAULT,
+                SKIP_464XLAT_DISABLE,
+                SKIP_464XLAT_ENABLE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Skip464XlatStatus {}
+
     }
 
     /**
diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index 4fdfcbe0..48c07e8 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.TelephonyManager.PrefNetworkMode;
 
 import com.android.internal.telephony.RILConstants;
 
@@ -170,7 +171,8 @@
     };
 
     @UnsupportedAppUsage
-    public static int getRafFromNetworkType(int type) {
+    @TelephonyManager.NetworkTypeBitMask
+    public static int getRafFromNetworkType(@PrefNetworkMode int type) {
         switch (type) {
             case RILConstants.NETWORK_MODE_WCDMA_PREF:
                 return GSM | WCDMA;
@@ -279,6 +281,7 @@
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @PrefNetworkMode
     public static int getNetworkTypeFromRaf(int raf) {
         raf = getAdjustedRaf(raf);
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index aaf5f33..0b003dc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6861,12 +6861,12 @@
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @param subId the id of the subscription to set the preferred network type for.
-     * @param networkType the preferred network type, defined in RILConstants.java.
+     * @param networkType the preferred network type
      * @return true on success; false on any failure.
      * @hide
      */
     @UnsupportedAppUsage
-    public boolean setPreferredNetworkType(int subId, int networkType) {
+    public boolean setPreferredNetworkType(int subId, @PrefNetworkMode int networkType) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -10366,6 +10366,10 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
      * calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * Note: with only carrier privileges, it is not allowed to switch from multi-sim
+     * to single-sim
+     *
      * @param numOfSims number of live SIMs we want to switch to
      * @throws android.os.RemoteException
      */
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 9d072f0..be6f383 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -61,6 +61,7 @@
     private static final String V4_FORMAT_REGEX = "^\\[ApnSettingV4\\]\\s*";
     private static final String V5_FORMAT_REGEX = "^\\[ApnSettingV5\\]\\s*";
     private static final String V6_FORMAT_REGEX = "^\\[ApnSettingV6\\]\\s*";
+    private static final String V7_FORMAT_REGEX = "^\\[ApnSettingV7\\]\\s*";
 
     /**
      * Default value for mtu if it's not set. Moved from PhoneConstants.
@@ -286,6 +287,8 @@
     private boolean mPermanentFailed = false;
     private final int mCarrierId;
 
+    private final int mSkip464Xlat;
+
     /**
      * Returns the MTU size of the mobile interface to which the APN connected.
      *
@@ -623,6 +626,17 @@
         return mCarrierId;
     }
 
+    /**
+     * Returns the skip464xlat flag for this APN.
+     *
+     * @return SKIP_464XLAT_DEFAULT, SKIP_464XLAT_DISABLE or SKIP_464XLAT_ENABLE
+     * @hide
+     */
+    @Carriers.Skip464XlatStatus
+    public int getSkip464Xlat() {
+        return mSkip464Xlat;
+    }
+
     private ApnSetting(Builder builder) {
         this.mEntryName = builder.mEntryName;
         this.mApnName = builder.mApnName;
@@ -651,6 +665,7 @@
         this.mMvnoMatchData = builder.mMvnoMatchData;
         this.mApnSetId = builder.mApnSetId;
         this.mCarrierId = builder.mCarrierId;
+        this.mSkip464Xlat = builder.mSkip464Xlat;
     }
 
     /**
@@ -662,7 +677,7 @@
             int authType, int mApnTypeBitmask, int protocol, int roamingProtocol,
             boolean carrierEnabled, int networkTypeBitmask, int profileId,
             boolean modemCognitive, int maxConns, int waitTime, int maxConnsTime, int mtu,
-            int mvnoType, String mvnoMatchData, int apnSetId, int carrierId) {
+            int mvnoType, String mvnoMatchData, int apnSetId, int carrierId, int skip464xlat) {
         return new Builder()
             .setId(id)
             .setOperatorNumeric(operatorNumeric)
@@ -691,6 +706,7 @@
             .setMvnoMatchData(mvnoMatchData)
             .setApnSetId(apnSetId)
             .setCarrierId(carrierId)
+            .setSkip464Xlat(skip464xlat)
             .buildWithoutCheck();
     }
 
@@ -708,7 +724,8 @@
             mmsc, mmsProxyAddress, mmsProxyPort, user, password, authType, mApnTypeBitmask,
             protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, profileId,
             modemCognitive, maxConns, waitTime, maxConnsTime, mtu, mvnoType, mvnoMatchData,
-            Carriers.NO_APN_SET_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+            Carriers.NO_APN_SET_ID, TelephonyManager.UNKNOWN_CARRIER_ID,
+            Carriers.SKIP_464XLAT_DEFAULT);
     }
 
     /**
@@ -767,7 +784,8 @@
             cursor.getString(cursor.getColumnIndexOrThrow(
                 Telephony.Carriers.MVNO_MATCH_DATA)),
             cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)),
-            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)));
+            cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)),
+            cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)));
     }
 
     /**
@@ -780,7 +798,7 @@
             apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
             apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
             apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId,
-            apn.mCarrierId);
+            apn.mCarrierId, apn.mSkip464Xlat);
     }
 
     /**
@@ -829,6 +847,13 @@
      *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
      *   <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>
      *
+     * v7 format:
+     *   [ApnSettingV7] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
+     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
+     *   <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
+     *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
+     *   <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>, <skip464xlat>
+     *
      * Note that the strings generated by {@link #toString()} do not contain the username
      * and password and thus cannot be read by this method.
      *
@@ -841,7 +866,10 @@
 
         int version;
         // matches() operates on the whole string, so append .* to the regex.
-        if (data.matches(V6_FORMAT_REGEX + ".*")) {
+        if (data.matches(V7_FORMAT_REGEX + ".*")) {
+            version = 7;
+            data = data.replaceFirst(V7_FORMAT_REGEX, "");
+        } else if (data.matches(V6_FORMAT_REGEX + ".*")) {
             version = 6;
             data = data.replaceFirst(V6_FORMAT_REGEX, "");
         } else if (data.matches(V5_FORMAT_REGEX + ".*")) {
@@ -887,6 +915,7 @@
         String mvnoMatchData = "";
         int apnSetId = Carriers.NO_APN_SET_ID;
         int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        int skip464xlat = Carriers.SKIP_464XLAT_DEFAULT;
         if (version == 1) {
             typeArray = new String[a.length - 13];
             System.arraycopy(a, 13, typeArray, 0, a.length - 13);
@@ -933,6 +962,12 @@
             if (a.length > 28) {
                 carrierId = Integer.parseInt(a[28]);
             }
+            if (a.length > 29) {
+                try {
+                    skip464xlat = Integer.parseInt(a[29]);
+                } catch (NumberFormatException e) {
+                }
+            }
         }
 
         // If both bearerBitmask and networkTypeBitmask were specified, bearerBitmask would be
@@ -948,7 +983,7 @@
             getProtocolIntFromString(protocol), getProtocolIntFromString(roamingProtocol),
             carrierEnabled, networkTypeBitmask, profileId, modemCognitive, maxConns, waitTime,
             maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId,
-            carrierId);
+            carrierId, skip464xlat);
     }
 
     /**
@@ -984,7 +1019,7 @@
      */
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("[ApnSettingV6] ")
+        sb.append("[ApnSettingV7] ")
                 .append(mEntryName)
                 .append(", ").append(mId)
                 .append(", ").append(mOperatorNumeric)
@@ -1012,6 +1047,7 @@
         sb.append(", ").append(mNetworkTypeBitmask);
         sb.append(", ").append(mApnSetId);
         sb.append(", ").append(mCarrierId);
+        sb.append(", ").append(mSkip464Xlat);
         return sb.toString();
     }
 
@@ -1105,7 +1141,8 @@
             && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
             && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
             && Objects.equals(mApnSetId, other.mApnSetId)
-            && Objects.equals(mCarrierId, other.mCarrierId);
+            && Objects.equals(mCarrierId, other.mCarrierId)
+            && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
     }
 
     /**
@@ -1151,7 +1188,8 @@
             && Objects.equals(mMvnoType, other.mMvnoType)
             && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
             && Objects.equals(mApnSetId, other.mApnSetId)
-            && Objects.equals(mCarrierId, other.mCarrierId);
+            && Objects.equals(mCarrierId, other.mCarrierId)
+            && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
     }
 
     /**
@@ -1179,7 +1217,8 @@
             && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
             && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
             && Objects.equals(mApnSetId, other.mApnSetId)
-            && Objects.equals(mCarrierId, other.mCarrierId);
+            && Objects.equals(mCarrierId, other.mCarrierId)
+            && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
     }
 
     // Equal or one is null.
@@ -1226,6 +1265,7 @@
         apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
         apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
         apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
+        apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
 
         return apnValue;
     }
@@ -1385,6 +1425,7 @@
         dest.writeInt(mNetworkTypeBitmask);
         dest.writeInt(mApnSetId);
         dest.writeInt(mCarrierId);
+        dest.writeInt(mSkip464Xlat);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1408,11 +1449,12 @@
         final int networkTypeBitmask = in.readInt();
         final int apnSetId = in.readInt();
         final int carrierId = in.readInt();
+        final int skip464xlat = in.readInt();
 
         return makeApnSetting(id, operatorNumeric, entryName, apnName,
-            proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
-            protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
-            0, 0, 0, 0, mvnoType, null, apnSetId, carrierId);
+                proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
+                protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
+                0, 0, 0, 0, mvnoType, null, apnSetId, carrierId, skip464xlat);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApnSetting> CREATOR =
@@ -1489,6 +1531,7 @@
         private String mMvnoMatchData;
         private int mApnSetId;
         private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
 
         /**
          * Default constructor for Builder.
@@ -1831,6 +1874,17 @@
         }
 
         /**
+         * Sets skip464xlat flag for this APN.
+         *
+         * @param skip464xlat skip464xlat for this APN
+         * @hide
+         */
+        public Builder setSkip464Xlat(@Carriers.Skip464XlatStatus int skip464xlat) {
+            this.mSkip464Xlat = skip464xlat;
+            return this;
+        }
+
+        /**
          * Builds {@link ApnSetting} from this builder.
          *
          * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 4780f30..7e183db 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -379,6 +379,83 @@
             // Check that the data has expired after the expiration time (with a buffer of 1 second)
             Thread.sleep(expirationTime / 2);
             assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+
+        } finally {
+            DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
+                    DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
+                    Long.toString(defaultExpirationTime), false /* makeDefault*/);
+            RollbackTestUtils.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test that changing time on device does not affect the duration of time that we keep
+     * rollback available
+     */
+    @Test
+    public void testTimeChangeDoesNotAffectLifetime() throws Exception {
+        long expirationTime = TimeUnit.SECONDS.toMillis(30);
+        long defaultExpirationTime = TimeUnit.HOURS.toMillis(48);
+        RollbackManager rm = RollbackTestUtils.getRollbackManager();
+
+        try {
+            RollbackTestUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.MANAGE_ROLLBACKS,
+                    Manifest.permission.WRITE_DEVICE_CONFIG,
+                    Manifest.permission.SET_TIME);
+
+            DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
+                    DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
+                    Long.toString(expirationTime), false /* makeDefault*/);
+
+            // Pull the new expiration time from DeviceConfig
+            rm.reloadPersistedData();
+
+            // Install app A with rollback enabled
+            RollbackTestUtils.uninstall(TEST_APP_A);
+            RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
+            assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+
+            Thread.sleep(expirationTime / 2);
+
+            // Install app B with rollback enabled
+            RollbackTestUtils.uninstall(TEST_APP_B);
+            RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
+            assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+            // 1 second buffer
+            Thread.sleep(1000);
+
+            try {
+                // Change the time
+                RollbackTestUtils.forwardTimeBy(expirationTime);
+
+                // 1 second buffer to allow Rollback Manager to handle time change before loading
+                // persisted data
+                Thread.sleep(1000);
+
+                // Load timestamps from storage
+                rm.reloadPersistedData();
+
+                // Wait until rollback for app A has expired
+                // This will trigger an expiration run that should expire app A but not B
+                Thread.sleep(expirationTime / 2);
+                assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+
+                // Rollback for app B should not be expired
+                RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                        rm.getAvailableRollbacks(), TEST_APP_B);
+                assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollback);
+
+                // Wait until rollback for app B has expired
+                Thread.sleep(expirationTime / 2);
+                assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B));
+            } finally {
+                RollbackTestUtils.forwardTimeBy(-expirationTime);
+            }
         } finally {
             DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
                     DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index e8cbd60..ed8a533 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
+import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -63,6 +64,17 @@
         return rm;
     }
 
+    private static void setTime(long millis) {
+        Context context = InstrumentationRegistry.getContext();
+        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        am.setTime(millis);
+    }
+
+    static void forwardTimeBy(long offsetMillis) {
+        setTime(System.currentTimeMillis() + offsetMillis);
+        Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis");
+    }
+
     /**
      * Returns the version of the given package installed on device.
      * Returns -1 if the package is not currently installed.
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 295e3de..52707c2 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -196,8 +196,10 @@
 
             if "implements" in raw:
                 self.implements = raw[raw.index("implements")+1]
+                self.implements_all = [self.implements]
             else:
                 self.implements = None
+                self.implements_all = []
         else:
             raise ValueError("Unknown signature format: " + sig_format)
 
@@ -224,7 +226,7 @@
 
 
 class Package():
-    NAME = re.compile("package(?: .*)? ([A-Za-z.]+)")
+    NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
 
     def __init__(self, line, raw, blame):
         self.line = line
@@ -364,12 +366,12 @@
         self.parse_matching_paren("<", ">")
         extends = self.parse_extends()
         clazz.extends = extends[0] if extends else None
-        implements = self.parse_implements()
-        clazz.implements = implements[0] if implements else None
+        clazz.implements_all = self.parse_implements()
         # The checks assume that interfaces are always found in implements, which isn't true for
         # subinterfaces.
-        if not implements and "interface" in clazz.split:
-            clazz.implements = clazz.extends
+        if not clazz.implements_all and "interface" in clazz.split:
+            clazz.implements_all = [clazz.extends]
+        clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
         self.parse_token("{")
         self.parse_eof()
 
@@ -1249,6 +1251,7 @@
 def verify_collections(clazz):
     """Verifies that collection types are interfaces."""
     if clazz.fullname == "android.os.Bundle": return
+    if clazz.fullname == "android.os.Parcel": return
 
     bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
            "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
@@ -1284,9 +1287,9 @@
                 error(clazz, m, "S1", "Methods must not throw generic exceptions")
 
             if t in ["android.os.RemoteException"]:
-                if clazz.name == "android.content.ContentProviderClient": continue
-                if clazz.name == "android.os.Binder": continue
-                if clazz.name == "android.os.IBinder": continue
+                if clazz.fullname == "android.content.ContentProviderClient": continue
+                if clazz.fullname == "android.os.Binder": continue
+                if clazz.fullname == "android.os.IBinder": continue
 
                 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
 
@@ -1653,8 +1656,8 @@
 
 def verify_closable(clazz):
     """Verifies that classes are AutoClosable."""
-    if clazz.implements == "java.lang.AutoCloseable": return
-    if clazz.implements == "java.io.Closeable": return
+    if "java.lang.AutoCloseable" in clazz.implements_all: return
+    if "java.io.Closeable" in clazz.implements_all: return
 
     for m in clazz.methods:
         if len(m.args) > 0: continue
@@ -1853,6 +1856,9 @@
 
 def verify_pfd(clazz):
     """Verify that android APIs use PFD over FD."""
+    if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
+        return
+
     examine = clazz.ctors + clazz.methods
     for m in examine:
         if m.typ == "java.io.FileDescriptor":
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
index f34492d..5cb43db 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -242,6 +242,10 @@
         cls = self._cls("class Class {")
         return apilint.Field(cls, 1, raw, '', sig_format=2)
 
+    def test_parse_package(self):
+        pkg = apilint.Package(999, "package wifi.p2p {", None)
+        self.assertEquals("wifi.p2p", pkg.name)
+
     def test_class(self):
         cls = self._cls("@Deprecated @IntRange(from=1, to=2) public static abstract class Some.Name extends Super<Class> implements Interface<Class> {")
         self.assertTrue('deprecated' in cls.split)