Merge "Move biometrics into biometric namespace"
diff --git a/Android.bp b/Android.bp
index e096c9d..1b81306 100644
--- a/Android.bp
+++ b/Android.bp
@@ -207,6 +207,8 @@
         "core/java/android/hardware/usb/IUsbSerialReader.aidl",
         "core/java/android/net/ICaptivePortal.aidl",
         "core/java/android/net/IConnectivityManager.aidl",
+        "core/java/android/hardware/ISensorPrivacyListener.aidl",
+        "core/java/android/hardware/ISensorPrivacyManager.aidl",
         "core/java/android/net/IIpConnectivityMetrics.aidl",
         "core/java/android/net/IEthernetManager.aidl",
         "core/java/android/net/IEthernetServiceListener.aidl",
@@ -1190,6 +1192,7 @@
 metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
     "--hide-package com.android.okhttp " +
     "--hide-package com.android.org.conscrypt --hide-package com.android.server " +
+    "--error UnhiddenSystemApi " +
     "--hide RequiresPermission " +
     "--hide MissingPermission --hide BroadcastBehavior " +
     "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
@@ -1606,6 +1609,7 @@
     dex_mapping_filename: "dex-mapping.txt",
     args: metalava_framework_docs_args +
         " --hide ReferencesHidden " +
+        " --hide UnhiddenSystemApi " +
         " --show-unannotated " +
         " --show-annotation android.annotation.SystemApi " +
         " --show-annotation android.annotation.TestApi "
diff --git a/Android.mk b/Android.mk
index 92e33e9..e3cc275 100644
--- a/Android.mk
+++ b/Android.mk
@@ -72,6 +72,11 @@
 	$(hide) mkdir -p $(OUT_DOCS)/offline-sdk
 	( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1
 
+# Run this for checkbuild
+checkbuild: doc-comment-check-docs
+# Check comment when you are updating the API
+update-api: doc-comment-check-docs
+
 # ==== hiddenapi lists =======================================
 .KATI_RESTAT: $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)
 $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
new file mode 100644
index 0000000..a482c4a
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.textclassifier;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLanguage;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Random;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class TextClassifierPerfTest {
+    /** Request contains meaning text, rather than garbled text. */
+    private static final int ACTUAL_REQUEST = 0;
+    private static final String RANDOM_CHAR_SET = "abcdefghijklmnopqrstuvwxyz0123456789";
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameters(name = "size={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][]{{ACTUAL_REQUEST}, {10}, {100}, {1000}});
+    }
+
+    private TextClassifier mTextClassifier;
+    private final int mSize;
+
+    public TextClassifierPerfTest(int size) {
+        mSize = size;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TextClassificationManager textClassificationManager =
+                context.getSystemService(TextClassificationManager.class);
+        mTextClassifier = textClassificationManager.getTextClassifier();
+    }
+
+    @Test
+    public void testSuggestConversationActions() {
+        String text = mSize == ACTUAL_REQUEST ? "Where are you?" : generateRandomString(mSize);
+        ConversationActions.Request request = createConversationActionsRequest(text);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.suggestConversationActions(request);
+        }
+    }
+
+    @Test
+    public void testDetectLanguage() {
+        String text = mSize == ACTUAL_REQUEST
+                ? "これは日本語のテキストです" : generateRandomString(mSize);
+        TextLanguage.Request request = createTextLanguageRequest(text);
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mTextClassifier.detectLanguage(request);
+        }
+    }
+
+    private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(
+                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        .setText(text)
+                        .build();
+        return new ConversationActions.Request.Builder(Collections.singletonList(message))
+                .build();
+    }
+
+    private static TextLanguage.Request createTextLanguageRequest(CharSequence text) {
+        return new TextLanguage.Request.Builder(text).build();
+    }
+
+    private static String generateRandomString(int length) {
+        Random random = new Random();
+        StringBuilder stringBuilder = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            int index = random.nextInt(RANDOM_CHAR_SET.length());
+            stringBuilder.append(RANDOM_CHAR_SET.charAt(index));
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index e20f2c0..9138843 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -417,7 +417,7 @@
     field public static final int clipOrientation = 16843274; // 0x101020a
     field public static final int clipToPadding = 16842987; // 0x10100eb
     field public static final int closeIcon = 16843905; // 0x1010481
-    field public static final int codes = 16843330; // 0x1010242
+    field public static final deprecated int codes = 16843330; // 0x1010242
     field public static final int collapseColumns = 16843083; // 0x101014b
     field public static final int collapseContentDescription = 16843984; // 0x10104d0
     field public static final int collapseIcon = 16844031; // 0x10104ff
@@ -718,7 +718,7 @@
     field public static final int homeAsUpIndicator = 16843531; // 0x101030b
     field public static final int homeLayout = 16843549; // 0x101031d
     field public static final int horizontalDivider = 16843053; // 0x101012d
-    field public static final int horizontalGap = 16843327; // 0x101023f
+    field public static final deprecated int horizontalGap = 16843327; // 0x101023f
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
@@ -726,7 +726,7 @@
     field public static final int hotSpotY = 16844056; // 0x1010518
     field public static final int hyphenationFrequency = 16843998; // 0x10104de
     field public static final int icon = 16842754; // 0x1010002
-    field public static final int iconPreview = 16843337; // 0x1010249
+    field public static final deprecated int iconPreview = 16843337; // 0x1010249
     field public static final int iconSpaceReserved = 16844129; // 0x1010561
     field public static final int iconTint = 16844126; // 0x101055e
     field public static final int iconTintMode = 16844127; // 0x101055f
@@ -787,12 +787,12 @@
     field public static final int isGame = 16843764; // 0x10103f4
     field public static final int isIndicator = 16843079; // 0x1010147
     field public static final int isLightTheme = 16844176; // 0x1010590
-    field public static final int isModifier = 16843334; // 0x1010246
-    field public static final int isRepeatable = 16843336; // 0x1010248
+    field public static final deprecated int isModifier = 16843334; // 0x1010246
+    field public static final deprecated int isRepeatable = 16843336; // 0x1010248
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSplitRequired = 16844177; // 0x1010591
     field public static final int isStatic = 16844122; // 0x101055a
-    field public static final int isSticky = 16843335; // 0x1010247
+    field public static final deprecated int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
     field public static final int isolatedSplits = 16844107; // 0x101054b
     field public static final int itemBackground = 16843056; // 0x1010130
@@ -802,27 +802,27 @@
     field public static final int justificationMode = 16844135; // 0x1010567
     field public static final int keepScreenOn = 16843286; // 0x1010216
     field public static final int key = 16843240; // 0x10101e8
-    field public static final int keyBackground = 16843315; // 0x1010233
-    field public static final int keyEdgeFlags = 16843333; // 0x1010245
-    field public static final int keyHeight = 16843326; // 0x101023e
-    field public static final int keyIcon = 16843340; // 0x101024c
-    field public static final int keyLabel = 16843339; // 0x101024b
-    field public static final int keyOutputText = 16843338; // 0x101024a
-    field public static final int keyPreviewHeight = 16843321; // 0x1010239
-    field public static final int keyPreviewLayout = 16843319; // 0x1010237
-    field public static final int keyPreviewOffset = 16843320; // 0x1010238
+    field public static final deprecated int keyBackground = 16843315; // 0x1010233
+    field public static final deprecated int keyEdgeFlags = 16843333; // 0x1010245
+    field public static final deprecated int keyHeight = 16843326; // 0x101023e
+    field public static final deprecated int keyIcon = 16843340; // 0x101024c
+    field public static final deprecated int keyLabel = 16843339; // 0x101024b
+    field public static final deprecated int keyOutputText = 16843338; // 0x101024a
+    field public static final deprecated int keyPreviewHeight = 16843321; // 0x1010239
+    field public static final deprecated int keyPreviewLayout = 16843319; // 0x1010237
+    field public static final deprecated int keyPreviewOffset = 16843320; // 0x1010238
     field public static final int keySet = 16843739; // 0x10103db
-    field public static final int keyTextColor = 16843318; // 0x1010236
-    field public static final int keyTextSize = 16843316; // 0x1010234
-    field public static final int keyWidth = 16843325; // 0x101023d
+    field public static final deprecated int keyTextColor = 16843318; // 0x1010236
+    field public static final deprecated int keyTextSize = 16843316; // 0x1010234
+    field public static final deprecated int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
-    field public static final int keyboardMode = 16843341; // 0x101024d
+    field public static final deprecated int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
     field public static final int killAfterRestore = 16843420; // 0x101029c
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
-    field public static final int labelTextSize = 16843317; // 0x1010235
+    field public static final deprecated int labelTextSize = 16843317; // 0x1010235
     field public static final int languageTag = 16844040; // 0x1010508
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
@@ -940,6 +940,7 @@
     field public static final int menuCategory = 16843230; // 0x10101de
     field public static final int mimeType = 16842790; // 0x1010026
     field public static final int min = 16844089; // 0x1010539
+    field public static final int minAspectRatio = 16844193; // 0x10105a1
     field public static final int minDate = 16843583; // 0x101033f
     field public static final int minEms = 16843098; // 0x101015a
     field public static final int minHeight = 16843072; // 0x1010140
@@ -1046,12 +1047,12 @@
     field public static final int pointerIcon = 16844041; // 0x1010509
     field public static final int popupAnimationStyle = 16843465; // 0x10102c9
     field public static final int popupBackground = 16843126; // 0x1010176
-    field public static final int popupCharacters = 16843332; // 0x1010244
+    field public static final deprecated int popupCharacters = 16843332; // 0x1010244
     field public static final int popupElevation = 16843916; // 0x101048c
     field public static final int popupEnterTransition = 16844063; // 0x101051f
     field public static final int popupExitTransition = 16844064; // 0x1010520
-    field public static final int popupKeyboard = 16843331; // 0x1010243
-    field public static final int popupLayout = 16843323; // 0x101023b
+    field public static final deprecated int popupKeyboard = 16843331; // 0x1010243
+    field public static final deprecated int popupLayout = 16843323; // 0x101023b
     field public static final int popupMenuStyle = 16843520; // 0x1010300
     field public static final int popupTheme = 16843945; // 0x10104a9
     field public static final int popupWindowStyle = 16842870; // 0x1010076
@@ -1150,7 +1151,7 @@
     field public static final int roundIcon = 16844076; // 0x101052c
     field public static final int rowCount = 16843637; // 0x1010375
     field public static final int rowDelay = 16843216; // 0x10101d0
-    field public static final int rowEdgeFlags = 16843329; // 0x1010241
+    field public static final deprecated int rowEdgeFlags = 16843329; // 0x1010241
     field public static final int rowHeight = 16843058; // 0x1010132
     field public static final int rowOrderPreserved = 16843638; // 0x1010376
     field public static final int saveEnabled = 16842983; // 0x10100e7
@@ -1286,7 +1287,7 @@
     field public static final int state_focused = 16842908; // 0x101009c
     field public static final int state_hovered = 16843623; // 0x1010367
     field public static final int state_last = 16842918; // 0x10100a6
-    field public static final int state_long_pressable = 16843324; // 0x101023c
+    field public static final deprecated int state_long_pressable = 16843324; // 0x101023c
     field public static final int state_middle = 16842917; // 0x10100a5
     field public static final int state_multiline = 16843597; // 0x101034d
     field public static final int state_pressed = 16842919; // 0x10100a7
@@ -1524,9 +1525,9 @@
     field public static final int versionCodeMajor = 16844150; // 0x1010576
     field public static final int versionMajor = 16844151; // 0x1010577
     field public static final int versionName = 16843292; // 0x101021c
-    field public static final int verticalCorrection = 16843322; // 0x101023a
+    field public static final deprecated int verticalCorrection = 16843322; // 0x101023a
     field public static final int verticalDivider = 16843054; // 0x101012e
-    field public static final int verticalGap = 16843328; // 0x1010240
+    field public static final deprecated int verticalGap = 16843328; // 0x1010240
     field public static final int verticalScrollbarPosition = 16843572; // 0x1010334
     field public static final int verticalSpacing = 16843029; // 0x1010115
     field public static final int viewportHeight = 16843779; // 0x1010403
@@ -1894,7 +1895,7 @@
     field public static final int input = 16908297; // 0x1020009
     field public static final int inputArea = 16908318; // 0x102001e
     field public static final int inputExtractEditText = 16908325; // 0x1020025
-    field public static final int keyboardView = 16908326; // 0x1020026
+    field public static final deprecated int keyboardView = 16908326; // 0x1020026
     field public static final int list = 16908298; // 0x102000a
     field public static final int list_container = 16908351; // 0x102003f
     field public static final int mask = 16908334; // 0x102002e
@@ -2605,7 +2606,7 @@
     field public static final int Widget_Holo_WebView = 16973993; // 0x10300a9
     field public static final int Widget_ImageButton = 16973862; // 0x1030026
     field public static final int Widget_ImageWell = 16973861; // 0x1030025
-    field public static final int Widget_KeyboardView = 16973911; // 0x1030057
+    field public static final deprecated int Widget_KeyboardView = 16973911; // 0x1030057
     field public static final int Widget_ListPopupWindow = 16973957; // 0x1030085
     field public static final int Widget_ListView = 16973870; // 0x103002e
     field public static final int Widget_ListView_DropDown = 16973872; // 0x1030030
@@ -4432,11 +4433,12 @@
   }
 
   public final class AutomaticZenRule implements android.os.Parcelable {
-    ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
-    ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, boolean);
+    ctor public deprecated AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
+    ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
     method public int describeContents();
     method public android.net.Uri getConditionId();
+    method public android.content.ComponentName getConfigurationActivity();
     method public long getCreationTime();
     method public int getInterruptionFilter();
     method public java.lang.String getName();
@@ -4444,6 +4446,7 @@
     method public android.service.notification.ZenPolicy getZenPolicy();
     method public boolean isEnabled();
     method public void setConditionId(android.net.Uri);
+    method public void setConfigurationActivity(android.content.ComponentName);
     method public void setEnabled(boolean);
     method public void setInterruptionFilter(int);
     method public void setName(java.lang.String);
@@ -5772,16 +5775,19 @@
     method public void notifyAsPackage(java.lang.String, java.lang.String, int, android.app.Notification);
     method public boolean removeAutomaticZenRule(java.lang.String);
     method public void revokeNotificationDelegate();
+    method public void setAutomaticZenRuleState(java.lang.String, android.service.notification.Condition);
     method public final void setInterruptionFilter(int);
     method public void setNotificationDelegate(java.lang.String);
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
     method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
+    field public static final java.lang.String ACTION_AUTOMATIC_ZEN_RULE = "android.app.action.AUTOMATIC_ZEN_RULE";
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+    field public static final java.lang.String EXTRA_AUTOMATIC_RULE_ID = "android.app.extra.AUTOMATIC_RULE_ID";
     field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
     field public static final java.lang.String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID = "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID";
     field public static final java.lang.String EXTRA_NOTIFICATION_CHANNEL_ID = "android.app.extra.NOTIFICATION_CHANNEL_ID";
@@ -5797,6 +5803,8 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String META_DATA_AUTOMATIC_RULE_TYPE = "android.app.automatic.ruleType";
+    field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.app.zen.automatic.ruleInstanceLimit";
   }
 
   public static class NotificationManager.Policy implements android.os.Parcelable {
@@ -13573,6 +13581,46 @@
     ctor public BitmapShader(android.graphics.Bitmap, android.graphics.Shader.TileMode, android.graphics.Shader.TileMode);
   }
 
+  public final class BlendMode extends java.lang.Enum {
+    method public static android.graphics.BlendMode valueOf(java.lang.String);
+    method public static final android.graphics.BlendMode[] values();
+    enum_constant public static final android.graphics.BlendMode CLEAR;
+    enum_constant public static final android.graphics.BlendMode COLOR;
+    enum_constant public static final android.graphics.BlendMode COLOR_BURN;
+    enum_constant public static final android.graphics.BlendMode COLOR_DODGE;
+    enum_constant public static final android.graphics.BlendMode DARKEN;
+    enum_constant public static final android.graphics.BlendMode DIFFERENCE;
+    enum_constant public static final android.graphics.BlendMode DST;
+    enum_constant public static final android.graphics.BlendMode DST_ATOP;
+    enum_constant public static final android.graphics.BlendMode DST_IN;
+    enum_constant public static final android.graphics.BlendMode DST_OUT;
+    enum_constant public static final android.graphics.BlendMode DST_OVER;
+    enum_constant public static final android.graphics.BlendMode EXCLUSION;
+    enum_constant public static final android.graphics.BlendMode HARD_LIGHT;
+    enum_constant public static final android.graphics.BlendMode HUE;
+    enum_constant public static final android.graphics.BlendMode LIGHTEN;
+    enum_constant public static final android.graphics.BlendMode LUMINOSITY;
+    enum_constant public static final android.graphics.BlendMode MODULATE;
+    enum_constant public static final android.graphics.BlendMode MULTIPLY;
+    enum_constant public static final android.graphics.BlendMode OVERLAY;
+    enum_constant public static final android.graphics.BlendMode PLUS;
+    enum_constant public static final android.graphics.BlendMode SATURATION;
+    enum_constant public static final android.graphics.BlendMode SCREEN;
+    enum_constant public static final android.graphics.BlendMode SOFT_LIGHT;
+    enum_constant public static final android.graphics.BlendMode SRC;
+    enum_constant public static final android.graphics.BlendMode SRC_ATOP;
+    enum_constant public static final android.graphics.BlendMode SRC_IN;
+    enum_constant public static final android.graphics.BlendMode SRC_OUT;
+    enum_constant public static final android.graphics.BlendMode SRC_OVER;
+    enum_constant public static final android.graphics.BlendMode XOR;
+  }
+
+  public final class BlendModeColorFilter extends android.graphics.ColorFilter {
+    ctor public BlendModeColorFilter(int, android.graphics.BlendMode);
+    method public int getColor();
+    method public android.graphics.BlendMode getMode();
+  }
+
   public class BlurMaskFilter extends android.graphics.MaskFilter {
     ctor public BlurMaskFilter(float, android.graphics.BlurMaskFilter.Blur);
   }
@@ -13635,7 +13683,8 @@
     method public void drawBitmapMesh(android.graphics.Bitmap, int, int, float[], int, int[], int, android.graphics.Paint);
     method public void drawCircle(float, float, float, android.graphics.Paint);
     method public void drawColor(int);
-    method public void drawColor(int, android.graphics.PorterDuff.Mode);
+    method public deprecated void drawColor(int, android.graphics.PorterDuff.Mode);
+    method public void drawColor(int, android.graphics.BlendMode);
     method public void drawDoubleRoundRect(android.graphics.RectF, float, float, android.graphics.RectF, float, float, android.graphics.Paint);
     method public void drawDoubleRoundRect(android.graphics.RectF, float[], android.graphics.RectF, float[], android.graphics.Paint);
     method public void drawLine(float, float, float, float, android.graphics.Paint);
@@ -14257,6 +14306,7 @@
     method public float descent();
     method public boolean equalsForTextMeasurement(android.graphics.Paint);
     method public int getAlpha();
+    method public android.graphics.BlendMode getBlendMode();
     method public int getColor();
     method public android.graphics.ColorFilter getColorFilter();
     method public boolean getFillPath(android.graphics.Path, android.graphics.Path);
@@ -14311,7 +14361,7 @@
     method public float getUnderlinePosition();
     method public float getUnderlineThickness();
     method public float getWordSpacing();
-    method public android.graphics.Xfermode getXfermode();
+    method public deprecated android.graphics.Xfermode getXfermode();
     method public boolean hasGlyph(java.lang.String);
     method public final boolean isAntiAlias();
     method public final boolean isDither();
@@ -14331,6 +14381,7 @@
     method public void setARGB(int, int, int, int);
     method public void setAlpha(int);
     method public void setAntiAlias(boolean);
+    method public void setBlendMode(android.graphics.BlendMode);
     method public void setColor(int);
     method public android.graphics.ColorFilter setColorFilter(android.graphics.ColorFilter);
     method public void setDither(boolean);
@@ -14364,7 +14415,7 @@
     method public android.graphics.Typeface setTypeface(android.graphics.Typeface);
     method public void setUnderlineText(boolean);
     method public void setWordSpacing(float);
-    method public android.graphics.Xfermode setXfermode(android.graphics.Xfermode);
+    method public deprecated android.graphics.Xfermode setXfermode(android.graphics.Xfermode);
     field public static final int ANTI_ALIAS_FLAG = 1; // 0x1
     field public static final int CURSOR_AFTER = 0; // 0x0
     field public static final int CURSOR_AT = 4; // 0x4
@@ -14646,7 +14697,7 @@
     enum_constant public static final android.graphics.PorterDuff.Mode XOR;
   }
 
-  public class PorterDuffColorFilter extends android.graphics.ColorFilter {
+  public deprecated class PorterDuffColorFilter extends android.graphics.ColorFilter {
     ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode);
   }
 
@@ -14811,6 +14862,7 @@
     method public int getAmbientShadowColor();
     method public int getBottom();
     method public float getCameraDistance();
+    method public boolean getClipToBounds();
     method public boolean getClipToOutline();
     method public float getElevation();
     method public int getHeight();
@@ -14831,6 +14883,7 @@
     method public float getTranslationY();
     method public float getTranslationZ();
     method public long getUniqueId();
+    method public boolean getUseCompositingLayer();
     method public int getWidth();
     method public boolean hasDisplayList();
     method public boolean hasIdentityMatrix();
@@ -15202,7 +15255,7 @@
     method public final void setCallback(android.graphics.drawable.Drawable.Callback);
     method public void setChangingConfigurations(int);
     method public abstract void setColorFilter(android.graphics.ColorFilter);
-    method public void setColorFilter(int, android.graphics.PorterDuff.Mode);
+    method public deprecated void setColorFilter(int, android.graphics.PorterDuff.Mode);
     method public deprecated void setDither(boolean);
     method public void setFilterBitmap(boolean);
     method public void setHotspot(float, float);
@@ -16541,6 +16594,7 @@
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> REQUEST_PIPELINE_MAX_DEPTH;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE;
+    field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES;
     field public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN;
@@ -17111,6 +17165,18 @@
     field public static final float MINIMUM_GAIN_FACTOR = 1.0f;
   }
 
+  public final class MandatoryStreamCombination {
+    method public java.lang.String getDescription();
+    method public java.util.List<android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation> getStreamsInformation();
+    method public boolean isReprocessable();
+  }
+
+  public static final class MandatoryStreamCombination.MandatoryStreamInformation {
+    method public java.util.List<android.util.Size> getAvailableSizes();
+    method public int getFormat();
+    method public boolean isInput();
+  }
+
   public final class MeteringRectangle {
     ctor public MeteringRectangle(int, int, int, int, int);
     ctor public MeteringRectangle(android.graphics.Point, android.util.Size, int);
@@ -22326,7 +22392,7 @@
     field public int visibleTopInsets;
   }
 
-  public class Keyboard {
+  public deprecated class Keyboard {
     ctor public Keyboard(android.content.Context, int);
     ctor public Keyboard(android.content.Context, int, int, int, int);
     ctor public Keyboard(android.content.Context, int, int);
@@ -22400,7 +22466,7 @@
     field public int verticalGap;
   }
 
-  public class KeyboardView extends android.view.View implements android.view.View.OnClickListener {
+  public deprecated class KeyboardView extends android.view.View implements android.view.View.OnClickListener {
     ctor public KeyboardView(android.content.Context, android.util.AttributeSet);
     ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int);
     ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int, int);
@@ -26278,9 +26344,12 @@
     field public static final int SUCCESS = 0; // 0x0
   }
 
-  public static class AudioEffect.Descriptor {
+  public static final class AudioEffect.Descriptor implements android.os.Parcelable {
     ctor public AudioEffect.Descriptor();
     ctor public AudioEffect.Descriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR;
     field public java.lang.String connectMode;
     field public java.lang.String implementor;
     field public java.lang.String name;
@@ -29618,8 +29687,8 @@
 
   public class DiscoverySession implements java.lang.AutoCloseable {
     method public void close();
-    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
-    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+    method public deprecated android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+    method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
 
@@ -29704,6 +29773,21 @@
     field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1
   }
 
+  public static class WifiAwareManager.NetworkSpecifierBuilder {
+    ctor public WifiAwareManager.NetworkSpecifierBuilder();
+    method public android.net.NetworkSpecifier build();
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession);
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle);
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String);
+  }
+
+  public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo {
+    method public int describeContents();
+    method public java.net.Inet6Address getPeerIpv6Addr();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR;
+  }
+
   public class WifiAwareSession implements java.lang.AutoCloseable {
     method public void close();
     method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
@@ -36310,6 +36394,12 @@
     field public static final java.lang.String CACHED_NUMBER_TYPE = "numbertype";
     field public static final java.lang.String CACHED_PHOTO_ID = "photo_id";
     field public static final java.lang.String CACHED_PHOTO_URI = "photo_uri";
+    field public static final java.lang.String CALL_ID_APP_NAME = "call_id_app_name";
+    field public static final java.lang.String CALL_ID_DESCRIPTION = "call_id_description";
+    field public static final java.lang.String CALL_ID_DETAILS = "call_id_details";
+    field public static final java.lang.String CALL_ID_NAME = "call_id_name";
+    field public static final java.lang.String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence";
+    field public static final java.lang.String CALL_ID_PACKAGE_NAME = "call_id_package_name";
     field public static final java.lang.String CALL_SCREENING_APP_NAME = "call_screening_app_name";
     field public static final java.lang.String CALL_SCREENING_COMPONENT_NAME = "call_screening_component_name";
     field public static final android.net.Uri CONTENT_FILTER_URI;
@@ -37491,25 +37581,37 @@
     method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
     method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String);
     method public static android.net.Uri copyDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static android.net.Uri createDocument(android.content.ContentInterface, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
+    method public static deprecated android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public static android.content.IntentSender createWebLinkIntent(android.content.ContentInterface, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
+    method public static deprecated android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
     method public static boolean deleteDocument(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static void ejectRoot(android.content.ContentInterface, android.net.Uri);
+    method public static deprecated void ejectRoot(android.content.ContentResolver, android.net.Uri);
     method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static java.lang.String getDocumentId(android.net.Uri);
     method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
+    method public static deprecated android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public static java.lang.String getRootId(android.net.Uri);
     method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
     method public static java.lang.String getTreeDocumentId(android.net.Uri);
     method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
     method public static boolean isRootUri(android.content.Context, android.net.Uri);
     method public static boolean isRootsUri(android.content.Context, android.net.Uri);
     method public static boolean isTreeUri(android.net.Uri);
     method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static boolean removeDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static deprecated boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
     method public static android.net.Uri renameDocument(android.content.ContentInterface, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
+    method public static deprecated android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
     field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
     field public static final java.lang.String EXTRA_ERROR = "error";
     field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
@@ -40917,10 +41019,10 @@
     field public final java.lang.String summary;
   }
 
-  public abstract class ConditionProviderService extends android.app.Service {
+  public abstract deprecated class ConditionProviderService extends android.app.Service {
     ctor public ConditionProviderService();
-    method public final void notifyCondition(android.service.notification.Condition);
-    method public final void notifyConditions(android.service.notification.Condition...);
+    method public final deprecated void notifyCondition(android.service.notification.Condition);
+    method public final deprecated void notifyConditions(android.service.notification.Condition...);
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onConnected();
     method public void onRequestConditions(int);
@@ -40928,10 +41030,10 @@
     method public abstract void onUnsubscribe(android.net.Uri);
     method public static final void requestRebind(android.content.ComponentName);
     method public final void requestUnbind();
-    field public static final java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
-    field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
-    field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
-    field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
+    field public static final deprecated java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
+    field public static final deprecated java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+    field public static final deprecated java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
+    field public static final deprecated java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
 
@@ -41007,12 +41109,12 @@
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
-    method public boolean audiblyAlerted();
     method public boolean canShowBadge();
     method public android.app.NotificationChannel getChannel();
     method public int getImportance();
     method public java.lang.CharSequence getImportanceExplanation();
     method public java.lang.String getKey();
+    method public long getLastAudiblyAlertedMillis();
     method public java.lang.String getOverrideGroupKey();
     method public int getRank();
     method public int getSuppressedVisualEffects();
@@ -42148,7 +42250,9 @@
     field public static final int SIOCGIFBRDADDR;
     field public static final int SIOCGIFDSTADDR;
     field public static final int SIOCGIFNETMASK;
+    field public static final int SOCK_CLOEXEC;
     field public static final int SOCK_DGRAM;
+    field public static final int SOCK_NONBLOCK;
     field public static final int SOCK_RAW;
     field public static final int SOCK_SEQPACKET;
     field public static final int SOCK_STREAM;
@@ -42455,6 +42559,7 @@
     method public static java.lang.String capabilitiesToString(int);
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public int getCallCapabilities();
+    method public android.telecom.CallIdentification getCallIdentification();
     method public int getCallProperties();
     method public java.lang.String getCallerDisplayName();
     method public int getCallerDisplayNamePresentation();
@@ -42533,6 +42638,34 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
+  public final class CallIdentification implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getCallScreeningAppName();
+    method public java.lang.String getCallScreeningPackageName();
+    method public java.lang.String getDescription();
+    method public java.lang.String getDetails();
+    method public java.lang.String getName();
+    method public int getNuisanceConfidence();
+    method public android.graphics.drawable.Icon getPhoto();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; // 0xffffffff
+    field public static final int CONFIDENCE_LIKELY_NUISANCE = 1; // 0x1
+    field public static final int CONFIDENCE_NOT_NUISANCE = -2; // 0xfffffffe
+    field public static final int CONFIDENCE_NUISANCE = 2; // 0x2
+    field public static final int CONFIDENCE_UNKNOWN = 0; // 0x0
+    field public static final android.os.Parcelable.Creator<android.telecom.CallIdentification> CREATOR;
+  }
+
+  public static class CallIdentification.Builder {
+    ctor public CallIdentification.Builder();
+    method public android.telecom.CallIdentification build();
+    method public android.telecom.CallIdentification.Builder setDescription(java.lang.String);
+    method public android.telecom.CallIdentification.Builder setDetails(java.lang.String);
+    method public android.telecom.CallIdentification.Builder setName(java.lang.String);
+    method public android.telecom.CallIdentification.Builder setNuisanceConfidence(int);
+    method public android.telecom.CallIdentification.Builder setPhoto(android.graphics.drawable.Icon);
+  }
+
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -42548,6 +42681,7 @@
     ctor public CallScreeningService();
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onScreenCall(android.telecom.Call.Details);
+    method public final void provideCallIdentification(android.telecom.Call.Details, android.telecom.CallIdentification);
     method public final void respondToCall(android.telecom.Call.Details, android.telecom.CallScreeningService.CallResponse);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
   }
@@ -43142,7 +43276,6 @@
     method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
     method public boolean handleMmi(java.lang.String);
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
-    method public boolean isDefaultCallScreeningApp(android.content.ComponentName);
     method public boolean isInCall();
     method public boolean isInManagedCall();
     method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
@@ -43151,7 +43284,6 @@
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
     method public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
-    method public void requestChangeDefaultCallScreeningApp(android.content.ComponentName);
     method public void showInCallScreen(boolean);
     method public void silenceRinger();
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -43229,9 +43361,12 @@
 
   public static final class VideoProfile.CameraCapabilities implements android.os.Parcelable {
     ctor public VideoProfile.CameraCapabilities(int, int);
+    ctor public VideoProfile.CameraCapabilities(int, int, boolean, float);
     method public int describeContents();
     method public int getHeight();
+    method public float getMaxZoom();
     method public int getWidth();
+    method public boolean isZoomSupported();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telecom.VideoProfile.CameraCapabilities> CREATOR;
   }
@@ -44141,6 +44276,7 @@
   public class SubscriptionInfo implements android.os.Parcelable {
     method public android.graphics.Bitmap createIconBitmap(android.content.Context);
     method public int describeContents();
+    method public int getCarrierId();
     method public java.lang.CharSequence getCarrierName();
     method public java.lang.String getCountryIso();
     method public int getDataRoaming();
@@ -44188,6 +44324,8 @@
     method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method public boolean removeSubscriptionsFromGroup(int[]);
+    method public boolean setMetered(boolean, int);
+    method public boolean setOpportunistic(boolean, int);
     method public java.lang.String setSubscriptionGroup(int[]);
     method public void setSubscriptionOverrideCongested(int, boolean, long);
     method public void setSubscriptionOverrideUnmetered(int, boolean, long);
@@ -46806,7 +46944,7 @@
     ctor public deprecated Scene(android.view.ViewGroup, android.view.ViewGroup);
     method public void enter();
     method public void exit();
-    method public static android.transition.Scene getCurrentScene(android.view.View);
+    method public static android.transition.Scene getCurrentScene(android.view.ViewGroup);
     method public static android.transition.Scene getSceneForLayout(android.view.ViewGroup, int, android.content.Context);
     method public android.view.ViewGroup getSceneRoot();
     method public void setEnterAction(java.lang.Runnable);
@@ -48905,7 +49043,9 @@
     method public float getPressure();
     method public float getPressure(int);
     method public float getRawX();
+    method public float getRawX(int);
     method public float getRawY();
+    method public float getRawY(int);
     method public float getSize();
     method public float getSize(int);
     method public int getSource();
@@ -49441,6 +49581,7 @@
     method public android.graphics.Rect getClipBounds();
     method public boolean getClipBounds(android.graphics.Rect);
     method public final boolean getClipToOutline();
+    method public final android.view.contentcapture.ContentCaptureSession getContentCaptureSession();
     method public java.lang.CharSequence getContentDescription();
     method public final android.content.Context getContext();
     method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
@@ -49777,6 +49918,7 @@
     method public void setClickable(boolean);
     method public void setClipBounds(android.graphics.Rect);
     method public void setClipToOutline(boolean);
+    method public void setContentCaptureSession(android.view.contentcapture.ContentCaptureSession);
     method public void setContentDescription(java.lang.CharSequence);
     method public void setContextClickable(boolean);
     method public void setDefaultFocusHighlightEnabled(boolean);
@@ -52037,17 +52179,56 @@
 
 package android.view.contentcapture {
 
+  public final class ContentCaptureContext implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureContext> CREATOR;
+  }
+
+  public static final class ContentCaptureContext.Builder {
+    ctor public ContentCaptureContext.Builder();
+    method public android.view.contentcapture.ContentCaptureContext build();
+    method public android.view.contentcapture.ContentCaptureContext.Builder setExtras(android.os.Bundle);
+    method public android.view.contentcapture.ContentCaptureContext.Builder setUri(android.net.Uri);
+  }
+
   public final class ContentCaptureManager {
+    method public android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
     method public android.content.ComponentName getServiceComponentName();
     method public boolean isContentCaptureEnabled();
-    method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
+    method public void removeUserData(android.view.contentcapture.UserDataRemovalRequest);
+    method public void setContentCaptureEnabled(boolean);
+  }
+
+  public final class ContentCaptureSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public void destroy();
+    method public android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
     method public void notifyViewAppeared(android.view.ViewStructure);
     method public void notifyViewDisappeared(android.view.autofill.AutofillId);
     method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
-    method public void setContentCaptureEnabled(boolean);
     field public static final int FLAG_USER_INPUT = 1; // 0x1
   }
 
+  public final class ContentCaptureSessionId implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureSessionId> CREATOR;
+  }
+
+  public final class UserDataRemovalRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.contentcapture.UserDataRemovalRequest> CREATOR;
+  }
+
+  public static final class UserDataRemovalRequest.Builder {
+    ctor public UserDataRemovalRequest.Builder();
+    method public android.view.contentcapture.UserDataRemovalRequest.Builder addUri(android.net.Uri, boolean);
+    method public android.view.contentcapture.UserDataRemovalRequest build();
+    method public android.view.contentcapture.UserDataRemovalRequest.Builder forEverything();
+  }
+
 }
 
 package android.view.inputmethod {
@@ -52534,9 +52715,10 @@
 package android.view.textclassifier {
 
   public final class ConversationActions implements android.os.Parcelable {
-    ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>);
+    ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>, java.lang.String);
     method public int describeContents();
     method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions();
+    method public java.lang.String getId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR;
     field public static final java.lang.String HINT_FOR_IN_APP = "in_app";
@@ -52597,6 +52779,7 @@
     method public int describeContents();
     method public java.lang.String getCallingPackageName();
     method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation();
+    method public java.lang.String getConversationId();
     method public java.util.List<java.lang.String> getHints();
     method public int getMaxSuggestions();
     method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig();
@@ -52607,6 +52790,7 @@
   public static final class ConversationActions.Request.Builder {
     ctor public ConversationActions.Request.Builder(java.util.List<android.view.textclassifier.ConversationActions.Message>);
     method public android.view.textclassifier.ConversationActions.Request build();
+    method public android.view.textclassifier.ConversationActions.Request.Builder setConversationId(java.lang.String);
     method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>);
     method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int);
     method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig);
@@ -52769,6 +52953,7 @@
     method public default int getMaxGenerateLinksTextLength();
     method public default boolean isDestroyed();
     method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent);
+    method public default void onTextClassifierEvent(android.view.textclassifier.TextClassifierEvent);
     method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request);
     method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
@@ -52790,6 +52975,7 @@
     field public static final java.lang.String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
     field public static final java.lang.String WIDGET_TYPE_EDITTEXT = "edittext";
     field public static final java.lang.String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
+    field public static final java.lang.String WIDGET_TYPE_NOTIFICATION = "notification";
     field public static final java.lang.String WIDGET_TYPE_TEXTVIEW = "textview";
     field public static final java.lang.String WIDGET_TYPE_UNKNOWN = "unknown";
     field public static final java.lang.String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
@@ -52807,6 +52993,68 @@
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR;
   }
 
+  public final class TextClassifierEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int[] getActionIndices();
+    method public java.lang.String getEntityType();
+    method public int getEventCategory();
+    method public android.view.textclassifier.TextClassificationContext getEventContext();
+    method public int getEventIndex();
+    method public long getEventTime();
+    method public int getEventType();
+    method public android.os.Bundle getExtras();
+    method public java.lang.String getLanguage();
+    method public int getRelativeSuggestedWordEndIndex();
+    method public int getRelativeSuggestedWordStartIndex();
+    method public int getRelativeWordEndIndex();
+    method public int getRelativeWordStartIndex();
+    method public java.lang.String getResultId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3
+    field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4
+    field public static final int CATEGORY_LINKIFY = 2; // 0x2
+    field public static final int CATEGORY_SELECTION = 1; // 0x1
+    field public static final int CATEGORY_UNDEFINED = 0; // 0x0
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifierEvent> CREATOR;
+    field public static final int TYPE_ACTIONS_SHOWN = 6; // 0x6
+    field public static final int TYPE_AUTO_SELECTION = 5; // 0x5
+    field public static final int TYPE_COPY_ACTION = 9; // 0x9
+    field public static final int TYPE_CUT_ACTION = 11; // 0xb
+    field public static final int TYPE_LINK_CLICKED = 7; // 0x7
+    field public static final int TYPE_MANUAL_REPLY = 19; // 0x13
+    field public static final int TYPE_OTHER_ACTION = 16; // 0x10
+    field public static final int TYPE_OVERTYPE = 8; // 0x8
+    field public static final int TYPE_PASTE_ACTION = 10; // 0xa
+    field public static final int TYPE_SELECTION_DESTROYED = 15; // 0xf
+    field public static final int TYPE_SELECTION_DRAG = 14; // 0xe
+    field public static final int TYPE_SELECTION_MODIFIED = 2; // 0x2
+    field public static final int TYPE_SELECTION_RESET = 18; // 0x12
+    field public static final int TYPE_SELECTION_STARTED = 1; // 0x1
+    field public static final int TYPE_SELECT_ALL = 17; // 0x11
+    field public static final int TYPE_SHARE_ACTION = 12; // 0xc
+    field public static final int TYPE_SMART_ACTION = 13; // 0xd
+    field public static final int TYPE_SMART_SELECTION_MULTI = 4; // 0x4
+    field public static final int TYPE_SMART_SELECTION_SINGLE = 3; // 0x3
+    field public static final int TYPE_UNDEFINED = 0; // 0x0
+  }
+
+  public static final class TextClassifierEvent.Builder {
+    ctor public TextClassifierEvent.Builder(int, int);
+    method public android.view.textclassifier.TextClassifierEvent build();
+    method public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(int...);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(java.lang.String);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(android.view.textclassifier.TextClassificationContext);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setExtras(android.os.Bundle);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setLanguage(java.lang.String);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeSuggestedWordEndIndex(int);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeSuggestedWordStartIndex(int);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int);
+    method public android.view.textclassifier.TextClassifierEvent.Builder setResultId(java.lang.String);
+  }
+
   public final class TextLanguage implements android.os.Parcelable {
     method public int describeContents();
     method public float getConfidenceScore(android.icu.util.ULocale);
@@ -56182,6 +56430,7 @@
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
     method public boolean isFallbackLineSpacing();
+    method public final boolean isHorizontallyScrolling();
     method public boolean isInputMethodTarget();
     method public boolean isSingleLine();
     method public boolean isSuggestionsEnabled();
diff --git a/api/removed.txt b/api/removed.txt
index e3e8b63..f7106d2 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -508,21 +508,6 @@
     field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
   }
 
-  public final class DocumentsContract {
-    method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
-    method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
-    method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
-    method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
-    method public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
-    method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
-  }
-
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
     field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index da6840c..7d10b098 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@
     field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
     field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
+    field public static final java.lang.String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
     field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
     field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
     field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
@@ -852,6 +853,7 @@
     method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
     method public boolean addRoleHolderFromController(java.lang.String, java.lang.String);
     method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
+    method public java.util.List<java.lang.String> getHeldRolesFromController(java.lang.String);
     method public java.util.List<java.lang.String> getRoleHolders(java.lang.String);
     method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle);
     method public void removeOnRoleHoldersChangedListenerAsUser(android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle);
@@ -2762,7 +2764,9 @@
     method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
     method public void flushGnssBatch();
     method public int getGnssBatchSize();
+    method public java.lang.String getLocationControllerExtraPackage();
     method public java.lang.String getNetworkProviderPackage();
+    method public boolean isLocationControllerExtraPackageEnabled();
     method public boolean isLocationEnabledForUser(android.os.UserHandle);
     method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle);
     method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler);
@@ -2770,6 +2774,8 @@
     method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
     method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper);
     method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent);
+    method public void setLocationControllerExtraPackage(java.lang.String);
+    method public void setLocationControllerExtraPackageEnabled(boolean);
     method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
     method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
     method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
@@ -3707,6 +3713,7 @@
     method public boolean isWifiScannerSupported();
     method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
     method public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
+    method public void setDeviceMobilityState(int);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
     method public boolean startScan(android.os.WorkSource);
     method public void unregisterNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback);
@@ -3714,6 +3721,10 @@
     field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
     field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
     field public static final java.lang.String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
+    field public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; // 0x1
+    field public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; // 0x2
+    field public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3; // 0x3
+    field public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0; // 0x0
     field public static final java.lang.String EXTRA_CHANGE_REASON = "changeReason";
     field public static final java.lang.String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
@@ -3884,7 +3895,11 @@
 package android.net.wifi.aware {
 
   public class DiscoverySession implements java.lang.AutoCloseable {
-    method public android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+    method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+  }
+
+  public static class WifiAwareManager.NetworkSpecifierBuilder {
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPmk(byte[]);
   }
 
   public class WifiAwareSession implements java.lang.AutoCloseable {
@@ -4528,6 +4543,18 @@
     field public static final java.lang.String STATE = "state";
   }
 
+  public final class DeviceConfig {
+    method public static void addOnPropertyChangedListener(java.lang.String, java.util.concurrent.Executor, android.provider.DeviceConfig.OnPropertyChangedListener);
+    method public static java.lang.String getProperty(java.lang.String, java.lang.String);
+    method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener);
+    method public static void resetToDefaults(int, java.lang.String);
+    method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean);
+  }
+
+  public static abstract interface DeviceConfig.OnPropertyChangedListener {
+    method public abstract void onPropertyChanged(java.lang.String, java.lang.String, java.lang.String);
+  }
+
   public final class DocumentsContract {
     method public static boolean isManageMode(android.net.Uri);
     method public static android.net.Uri setManageMode(android.net.Uri);
@@ -4647,6 +4674,7 @@
 
   public final class Settings {
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+    field public static final java.lang.String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
   }
 
@@ -4984,34 +5012,16 @@
     ctor public ContentCaptureService();
     method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
     method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
-    method public void onActivitySnapshot(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.SnapshotData);
-    method public abstract void onContentCaptureEventsRequest(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
-    method public void onCreateInteractionSession(android.service.contentcapture.InteractionContext, android.service.contentcapture.InteractionSessionId);
-    method public void onDestroyInteractionSession(android.service.contentcapture.InteractionSessionId);
+    method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData);
+    method public abstract void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
+    method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
+    method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
     method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
     method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
     method public final void setPackageContentCaptureEnabled(java.lang.String, boolean);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService";
   }
 
-  public final class InteractionContext implements android.os.Parcelable {
-    method public int describeContents();
-    method public android.content.ComponentName getActivityComponent();
-    method public int getDisplayId();
-    method public int getFlags();
-    method public int getTaskId();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionContext> CREATOR;
-    field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
-    field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
-  }
-
-  public final class InteractionSessionId implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionSessionId> CREATOR;
-  }
-
   public final class SnapshotData implements android.os.Parcelable {
     method public int describeContents();
     method public android.app.assist.AssistContent getAssistContent();
@@ -5357,9 +5367,10 @@
     method public void onDestroyTextClassificationSession(android.view.textclassifier.TextClassificationSessionId);
     method public void onDetectLanguage(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLanguage.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
     method public abstract void onGenerateLinks(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextLinks.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
-    method public void onSelectionEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.SelectionEvent);
+    method public deprecated void onSelectionEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.SelectionEvent);
     method public void onSuggestConversationActions(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.ConversationActions.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
     method public abstract void onSuggestSelection(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextSelection.Request, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
+    method public void onTextClassifierEvent(android.view.textclassifier.TextClassificationSessionId, android.view.textclassifier.TextClassifierEvent);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService";
   }
 
@@ -5657,7 +5668,7 @@
     method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String);
     method public boolean isInEmergencyCall();
     method public boolean isRinging();
-    method public boolean setDefaultDialer(java.lang.String);
+    method public deprecated boolean setDefaultDialer(java.lang.String);
     field public static final java.lang.String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
     field public static final java.lang.String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
     field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
@@ -5729,16 +5740,15 @@
   public abstract class NetworkService extends android.app.Service {
     ctor public NetworkService();
     method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int);
-    field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
     field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
   }
 
-  public class NetworkService.NetworkServiceProvider {
+  public abstract class NetworkService.NetworkServiceProvider implements java.lang.AutoCloseable {
     ctor public NetworkService.NetworkServiceProvider(int);
+    method public abstract void close();
     method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback);
     method public final int getSlotId();
     method public final void notifyNetworkRegistrationStateChanged();
-    method protected void onDestroy();
   }
 
   public class NetworkServiceCallback {
@@ -5822,6 +5832,7 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method public void requestEmbeddedSubscriptionInfoListRefresh();
+    method public void requestEmbeddedSubscriptionInfoListRefresh(int);
     method public void setDefaultDataSubId(int);
     method public void setDefaultSmsSubId(int);
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -6078,20 +6089,19 @@
   public abstract class DataService extends android.app.Service {
     ctor public DataService();
     method public abstract android.telephony.data.DataService.DataServiceProvider createDataServiceProvider(int);
-    field public static final java.lang.String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID";
     field public static final java.lang.String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService";
     field public static final int REQUEST_REASON_HANDOVER = 3; // 0x3
     field public static final int REQUEST_REASON_NORMAL = 1; // 0x1
     field public static final int REQUEST_REASON_SHUTDOWN = 2; // 0x2
   }
 
-  public class DataService.DataServiceProvider {
+  public abstract class DataService.DataServiceProvider implements java.lang.AutoCloseable {
     ctor public DataService.DataServiceProvider(int);
+    method public abstract void close();
     method public void deactivateDataCall(int, int, android.telephony.data.DataServiceCallback);
     method public void getDataCallList(android.telephony.data.DataServiceCallback);
     method public final int getSlotId();
     method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
-    method protected void onDestroy();
     method public void setDataProfile(java.util.List<android.telephony.data.DataProfile>, boolean, android.telephony.data.DataServiceCallback);
     method public void setInitialAttachApn(android.telephony.data.DataProfile, boolean, android.telephony.data.DataServiceCallback);
     method public void setupDataCall(int, android.telephony.data.DataProfile, boolean, boolean, int, android.net.LinkProperties, android.telephony.data.DataServiceCallback);
@@ -6559,6 +6569,7 @@
     field public static final int CODE_RADIO_SETUP_FAILURE = 1509; // 0x5e5
     field public static final int CODE_RADIO_UPLINK_FAILURE = 1508; // 0x5e4
     field public static final int CODE_REGISTRATION_ERROR = 1000; // 0x3e8
+    field public static final int CODE_REJECTED_ELSEWHERE = 1017; // 0x3f9
     field public static final int CODE_REJECT_1X_COLLISION = 1603; // 0x643
     field public static final int CODE_REJECT_CALL_ON_OTHER_SUB = 1602; // 0x642
     field public static final int CODE_REJECT_CALL_TYPE_NOT_ALLOWED = 1605; // 0x645
@@ -6582,26 +6593,39 @@
     field public static final int CODE_REJECT_VT_AVPF_NOT_ALLOWED = 1619; // 0x653
     field public static final int CODE_REJECT_VT_TTY_NOT_ALLOWED = 1615; // 0x64f
     field public static final int CODE_REMOTE_CALL_DECLINE = 1404; // 0x57c
+    field public static final int CODE_SESSION_MODIFICATION_FAILED = 1517; // 0x5ed
     field public static final int CODE_SIP_ALTERNATE_EMERGENCY_CALL = 1514; // 0x5ea
+    field public static final int CODE_SIP_AMBIGUOUS = 376; // 0x178
     field public static final int CODE_SIP_BAD_ADDRESS = 337; // 0x151
     field public static final int CODE_SIP_BAD_REQUEST = 331; // 0x14b
     field public static final int CODE_SIP_BUSY = 338; // 0x152
+    field public static final int CODE_SIP_CALL_OR_TRANS_DOES_NOT_EXIST = 372; // 0x174
     field public static final int CODE_SIP_CLIENT_ERROR = 342; // 0x156
+    field public static final int CODE_SIP_EXTENSION_REQUIRED = 370; // 0x172
     field public static final int CODE_SIP_FORBIDDEN = 332; // 0x14c
     field public static final int CODE_SIP_GLOBAL_ERROR = 362; // 0x16a
+    field public static final int CODE_SIP_INTERVAL_TOO_BRIEF = 371; // 0x173
+    field public static final int CODE_SIP_LOOP_DETECTED = 373; // 0x175
+    field public static final int CODE_SIP_METHOD_NOT_ALLOWED = 366; // 0x16e
     field public static final int CODE_SIP_NOT_ACCEPTABLE = 340; // 0x154
     field public static final int CODE_SIP_NOT_FOUND = 333; // 0x14d
     field public static final int CODE_SIP_NOT_REACHABLE = 341; // 0x155
     field public static final int CODE_SIP_NOT_SUPPORTED = 334; // 0x14e
+    field public static final int CODE_SIP_PROXY_AUTHENTICATION_REQUIRED = 367; // 0x16f
     field public static final int CODE_SIP_REDIRECTED = 321; // 0x141
     field public static final int CODE_SIP_REQUEST_CANCELLED = 339; // 0x153
+    field public static final int CODE_SIP_REQUEST_ENTITY_TOO_LARGE = 368; // 0x170
+    field public static final int CODE_SIP_REQUEST_PENDING = 377; // 0x179
     field public static final int CODE_SIP_REQUEST_TIMEOUT = 335; // 0x14f
+    field public static final int CODE_SIP_REQUEST_URI_TOO_LARGE = 369; // 0x171
     field public static final int CODE_SIP_SERVER_ERROR = 354; // 0x162
     field public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351; // 0x15f
     field public static final int CODE_SIP_SERVER_TIMEOUT = 353; // 0x161
     field public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; // 0x160
     field public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; // 0x150
+    field public static final int CODE_SIP_TOO_MANY_HOPS = 374; // 0x176
     field public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157
+    field public static final int CODE_SIP_UNDECIPHERABLE = 378; // 0x17a
     field public static final int CODE_SIP_USER_MARKED_UNWANTED = 365; // 0x16d
     field public static final int CODE_SIP_USER_REJECTED = 361; // 0x169
     field public static final int CODE_SUPP_SVC_CANCELLED = 1202; // 0x4b2
@@ -6611,9 +6635,11 @@
     field public static final int CODE_TIMEOUT_NO_ANSWER = 202; // 0xca
     field public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203; // 0xcb
     field public static final int CODE_UNSPECIFIED = 0; // 0x0
+    field public static final int CODE_USER_CANCELLED_SESSION_MODIFICATION = 512; // 0x200
     field public static final int CODE_USER_DECLINE = 504; // 0x1f8
     field public static final int CODE_USER_IGNORE = 503; // 0x1f7
     field public static final int CODE_USER_NOANSWER = 502; // 0x1f6
+    field public static final int CODE_USER_REJECTED_SESSION_MODIFICATION = 511; // 0x1ff
     field public static final int CODE_USER_TERMINATED = 501; // 0x1f5
     field public static final int CODE_USER_TERMINATED_BY_REMOTE = 510; // 0x1fe
     field public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821; // 0x335
@@ -7264,6 +7290,17 @@
 
 package android.view.contentcapture {
 
+  public final class ContentCaptureContext implements android.os.Parcelable {
+    method public android.content.ComponentName getActivityComponent();
+    method public int getDisplayId();
+    method public android.os.Bundle getExtras();
+    method public int getFlags();
+    method public int getTaskId();
+    method public android.net.Uri getUri();
+    field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
+    field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
+  }
+
   public final class ContentCaptureEvent implements android.os.Parcelable {
     method public int describeContents();
     method public long getEventTime();
@@ -7274,13 +7311,20 @@
     method public android.view.contentcapture.ViewNode getViewNode();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR;
-    field public static final deprecated int TYPE_ACTIVITY_PAUSED = 3; // 0x3
-    field public static final deprecated int TYPE_ACTIVITY_RESUMED = 2; // 0x2
-    field public static final deprecated int TYPE_ACTIVITY_STARTED = 1; // 0x1
-    field public static final deprecated int TYPE_ACTIVITY_STOPPED = 4; // 0x4
-    field public static final int TYPE_VIEW_APPEARED = 5; // 0x5
-    field public static final int TYPE_VIEW_DISAPPEARED = 6; // 0x6
-    field public static final int TYPE_VIEW_TEXT_CHANGED = 7; // 0x7
+    field public static final int TYPE_VIEW_APPEARED = 1; // 0x1
+    field public static final int TYPE_VIEW_DISAPPEARED = 2; // 0x2
+    field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
+  }
+
+  public final class UserDataRemovalRequest implements android.os.Parcelable {
+    method public java.lang.String getPackageName();
+    method public java.util.List<android.view.contentcapture.UserDataRemovalRequest.UriRequest> getUriRequests();
+    method public boolean isForEverything();
+  }
+
+  public final class UserDataRemovalRequest.UriRequest {
+    method public android.net.Uri getUri();
+    method public boolean isRecursive();
   }
 
   public final class ViewNode extends android.app.assist.AssistStructure.ViewNode {
diff --git a/api/test-current.txt b/api/test-current.txt
index 627ef22..46e7683 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -655,11 +655,6 @@
     method public android.media.BufferingParams.Builder setResumePlaybackMarkMs(int);
   }
 
-  public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
-    method public android.media.BufferingParams getBufferingParams();
-    method public void setBufferingParams(android.media.BufferingParams);
-  }
-
   public final class PlaybackParams implements android.os.Parcelable {
     method public int getAudioStretchMode();
     method public android.media.PlaybackParams setAudioStretchMode(int);
@@ -1197,7 +1192,7 @@
     field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
 
-  public abstract class ConditionProviderService extends android.app.Service {
+  public abstract deprecated class ConditionProviderService extends android.app.Service {
     method public boolean isBound();
   }
 
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 834658d..373677e 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -16,13 +16,17 @@
 
 package com.android.commands.bu;
 
+import android.annotation.UserIdInt;
 import android.app.backup.IBackupManager;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -33,35 +37,50 @@
     int mNextArg;
     IBackupManager mBackupManager;
 
+    @VisibleForTesting
+    Backup(IBackupManager backupManager) {
+        mBackupManager = backupManager;
+    }
+
+    Backup() {
+        mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+    }
+
     public static void main(String[] args) {
-        Log.d(TAG, "Beginning: " + args[0]);
-        mArgs = args;
         try {
-            new Backup().run();
+            new Backup().run(args);
         } catch (Exception e) {
             Log.e(TAG, "Error running backup/restore", e);
         }
         Log.d(TAG, "Finished.");
     }
 
-    public void run() {
-        mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+    public void run(String[] args) {
         if (mBackupManager == null) {
             Log.e(TAG, "Can't obtain Backup Manager binder");
             return;
         }
 
+        Log.d(TAG, "Beginning: " + args[0]);
+        mArgs = args;
+
+        int userId = parseUserId();
+        if (!isBackupActiveForUser(userId)) {
+            Log.e(TAG, "BackupManager is not available for user " + userId);
+            return;
+        }
+
         String arg = nextArg();
         if (arg.equals("backup")) {
-            doBackup(OsConstants.STDOUT_FILENO);
+            doBackup(OsConstants.STDOUT_FILENO, userId);
         } else if (arg.equals("restore")) {
-            doRestore(OsConstants.STDIN_FILENO);
+            doRestore(OsConstants.STDIN_FILENO, userId);
         } else {
             showUsage();
         }
     }
 
-    private void doBackup(int socketFd) {
+    private void doBackup(int socketFd, @UserIdInt int userId) {
         ArrayList<String> packages = new ArrayList<String>();
         boolean saveApks = false;
         boolean saveObbs = false;
@@ -105,6 +124,10 @@
                     doKeyValue = true;
                 } else if ("-nokeyvalue".equals(arg)) {
                     doKeyValue = false;
+                } else if ("-user".equals(arg)) {
+                    // User ID has been processed in run(), ignore the next argument.
+                    nextArg();
+                    continue;
                 } else {
                     Log.w(TAG, "Unknown backup flag " + arg);
                     continue;
@@ -128,7 +151,7 @@
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
             String[] packArray = new String[packages.size()];
-            mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+            mBackupManager.adbBackup(userId, fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
                     allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for backup");
@@ -143,12 +166,12 @@
         }
     }
 
-    private void doRestore(int socketFd) {
+    private void doRestore(int socketFd, @UserIdInt int userId) {
         // No arguments to restore
         ParcelFileDescriptor fd = null;
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
-            mBackupManager.adbRestore(fd);
+            mBackupManager.adbRestore(userId, fd);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for restore");
         } finally {
@@ -160,11 +183,31 @@
         }
     }
 
+    private @UserIdInt int parseUserId() {
+        for (int argNumber = 0; argNumber < mArgs.length - 1; argNumber++) {
+            if ("-user".equals(mArgs[argNumber])) {
+                return UserHandle.parseUserArg(mArgs[argNumber + 1]);
+            }
+        }
+
+        return UserHandle.USER_SYSTEM;
+    }
+
+    private boolean isBackupActiveForUser(int userId) {
+        try {
+            return mBackupManager.isBackupServiceActive(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not access BackupManager: " + e.toString());
+            return false;
+        }
+    }
+
     private static void showUsage() {
-        System.err.println(" backup [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all]");
-        System.err.println("        [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
+        System.err.println(" backup [-user USER_ID] [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared]");
+        System.err.println("        [-all] [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
         System.err.println("     write an archive of the device's data to FILE [default=backup.adb]");
         System.err.println("     package list optional if -all/-shared are supplied");
+        System.err.println("     -user: user ID for which to perform the operation (default - system user)");
         System.err.println("     -apk/-noapk: do/don't back up .apk files (default -noapk)");
         System.err.println("     -obb/-noobb: do/don't back up .obb files (default -noobb)");
         System.err.println("     -shared|-noshared: do/don't back up shared storage (default -noshared)");
@@ -172,7 +215,8 @@
         System.err.println("     -system|-nosystem: include system apps in -all (default -system)");
         System.err.println("     -keyvalue|-nokeyvalue: include apps that perform key/value backups.");
         System.err.println("         (default -nokeyvalue)");
-        System.err.println(" restore FILE             restore device contents from FILE");
+        System.err.println(" restore [-user USER_ID] FILE       restore device contents from FILE");
+        System.err.println("     -user: user ID for which to perform the operation (default - system user)");
     }
 
     private String nextArg() {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 783c8c4..be5a5bf 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -282,14 +282,31 @@
                 StorageManager.DEBUG_VIRTUAL_DISK);
     }
 
-    public void runIsolatedStorage() throws RemoteException {
-        final boolean enableIsolatedStorage = Boolean.parseBoolean(nextArg());
+    public void runIsolatedStorage() {
+        final int value;
+        final int mask = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
+                | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
+        switch (nextArg()) {
+            case "on":
+            case "true":
+                value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON;
+                break;
+            case "off":
+                value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
+                break;
+            case "default":
+            case "false":
+                value = 0;
+                break;
+            default:
+                return;
+        }
+
         // Toggling isolated-storage state will result in a device reboot. So to avoid this command
         // from erroring out (DeadSystemException), call setDebugFlags() in a separate thread.
         new Thread(() -> {
             try {
-                mSm.setDebugFlags(enableIsolatedStorage ? StorageManager.DEBUG_ISOLATED_STORAGE : 0,
-                        StorageManager.DEBUG_ISOLATED_STORAGE);
+                mSm.setDebugFlags(value, mask);
             } catch (RemoteException e) {
                 Log.e(TAG, "Encountered an error!", e);
             }
@@ -334,7 +351,7 @@
         System.err.println("");
         System.err.println("       sm set-emulate-fbe [true|false]");
         System.err.println("");
-        System.err.println("       sm set-isolated-storage [true|false]");
+        System.err.println("       sm set-isolated-storage [on|off|default]");
         System.err.println("");
         return 1;
     }
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index beb4dc3..5899e0cf 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -169,6 +169,8 @@
         DocsUIRootVisitedReported docs_ui_root_visited = 110;
         DocsUIStartupMsReported docs_ui_startup_ms = 111;
         DocsUIUserActionReported docs_ui_user_action_reported = 112;
+        WifiEnabledStateChanged wifi_enabled_state_changed = 113;
+        WifiRunningStateChanged wifi_running_state_changed = 114;
     }
 
     // Pulled events will start at field 10000.
@@ -871,6 +873,39 @@
 }
 
 /**
+ * Logs when Wifi is toggled on/off.
+ * Note that Wifi may still perform certain functions (e.g. location scanning) even when disabled.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message WifiEnabledStateChanged {
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 1;
+}
+
+/**
+ * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode.
+ * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'.
+ * Note that Wifi Scanning is monitored separately in WifiScanStateChanged.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message WifiRunningStateChanged {
+    repeated AttributionNode attribution_node = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
  * Logs wifi locks held by an app.
  *
  * Logged from:
@@ -1222,7 +1257,7 @@
         STATE_CONNECTED = 1;
     }
     optional State state = 6;
-    optional int64 last_connect_duration_ms = 7;
+    optional int64 last_connect_duration_millis = 7;
 }
 
 
@@ -2547,15 +2582,19 @@
 
 /*
  * Logs the memory stats for a process.
+ *
+ * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService).
  */
 message ProcessMemoryState {
     // The uid if available. -1 means not available.
     optional int32 uid = 1 [(is_uid) = true];
 
     // The process name.
+    // Usually package name, "system" for system server.
+    // Provided by ActivityManagerService.
     optional string process_name = 2;
 
-    // oom adj score.
+    // Current OOM score adjustment. Value read from ProcessRecord.
     optional int32 oom_adj_score = 3;
 
     // # of page-faults
@@ -2565,12 +2604,18 @@
     optional int64 page_major_fault = 5;
 
     // RSS
+    // Value is read from /proc/PID/stat, field 24. Or from memory.stat, field
+    // total_rss if per-app memory cgroups are enabled.
     optional int64 rss_in_bytes = 6;
 
     // CACHE
+    // Value is read from memory.stat, field total_cache if per-app memory
+    // cgroups are enabled. Otherwise, 0.
     optional int64 cache_in_bytes = 7;
 
     // SWAP
+    // Value is read from memory.stat, field total_swap if per-app memory
+    // cgroups are enabled. Otherwise, 0.
     optional int64 swap_in_bytes = 8;
 
     // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0.
@@ -2583,12 +2628,15 @@
 
 /*
  * Logs the memory stats for a native process (from procfs).
+ *
+ * Pulled from StatsCompanionService for selected native processes.
  */
 message NativeProcessMemoryState {
     // The uid if available. -1 means not available.
     optional int32 uid = 1 [(is_uid) = true];
 
     // The process name.
+    // Value read from /proc/PID/cmdline.
     optional string process_name = 2;
 
     // # of page-faults
@@ -2598,6 +2646,7 @@
     optional int64 page_major_fault = 4;
 
     // RSS
+    // Value read from /proc/PID/stat, field 24.
     optional int64 rss_in_bytes = 5;
 
     // Deprecated: use ProcessMemoryHighWaterMark atom instead. Always 0.
@@ -2610,13 +2659,19 @@
 
 /*
  * Logs the memory high-water mark for a process.
- * Recorded in ActivityManagerService.
+ *
+ * Pulled from StatsCompanionService for all managed processes (from ActivityManagerServie)
+ * and for selected native processes.
+ *
+ * Pulling this atom resets high-water mark counters for all processes.
  */
 message ProcessMemoryHighWaterMark {
     // The uid if available. -1 means not available.
     optional int32 uid = 1 [(is_uid) = true];
 
-    // The process name. Provided by ActivityManagerService or read from /proc/PID/cmdline.
+    // The process name.
+    // Usually package name or process cmdline.
+    // Provided by ActivityManagerService or read from /proc/PID/cmdline.
     optional string process_name = 2;
 
     // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in
@@ -3593,4 +3648,4 @@
  */
 message DocsUIInvalidScopedAccessRequestReported {
     optional android.stats.docsui.InvalidScopedAccess type = 1;
-}
\ No newline at end of file
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 16b7e79..5fea90b 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -106,14 +106,14 @@
         // Add to set.
         mConfigs[key.GetUid()].insert(key);
 
-        for (sp<ConfigListener> listener : mListeners) {
+        for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
     }
 
     const int64_t timestampNs = getElapsedRealtimeNs();
     // Tell everyone
-    for (sp<ConfigListener> listener : broadcastList) {
+    for (const sp<ConfigListener>& listener : broadcastList) {
         listener->OnConfigUpdated(timestampNs, key, config);
     }
 }
@@ -137,7 +137,7 @@
         if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end()) {
             // Remove from map
             uidIt->second.erase(key);
-            for (sp<ConfigListener> listener : mListeners) {
+            for (const sp<ConfigListener>& listener : mListeners) {
                 broadcastList.push_back(listener);
             }
         }
@@ -153,7 +153,7 @@
         remove_saved_configs(key);
     }
 
-    for (sp<ConfigListener> listener:broadcastList) {
+    for (const sp<ConfigListener>& listener:broadcastList) {
         listener->OnConfigRemoved(key);
     }
 }
@@ -183,7 +183,7 @@
 
         mConfigs.erase(uidIt);
 
-        for (sp<ConfigListener> listener : mListeners) {
+        for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
     }
@@ -191,7 +191,7 @@
     // Remove separately so if they do anything in the callback they can't mess up our iteration.
     for (auto& key : removed) {
         // Tell everyone
-        for (sp<ConfigListener> listener:broadcastList) {
+        for (const sp<ConfigListener>& listener:broadcastList) {
             listener->OnConfigRemoved(key);
         }
     }
@@ -213,7 +213,7 @@
         }
 
         mConfigReceivers.clear();
-        for (sp<ConfigListener> listener : mListeners) {
+        for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
     }
@@ -221,7 +221,7 @@
     // Remove separately so if they do anything in the callback they can't mess up our iteration.
     for (auto& key : removed) {
         // Tell everyone
-        for (sp<ConfigListener> listener:broadcastList) {
+        for (const sp<ConfigListener>& listener:broadcastList) {
             listener->OnConfigRemoved(key);
         }
     }
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
index c8c3920..d822959 100644
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
@@ -281,7 +281,7 @@
                              (long long)state.residencyInMsecSinceBoot,
                              (long long)state.totalTransitions,
                              state.supportedOnlyInSuspend ? 1 : 0);
-                        for (auto voter : state.voters) {
+                        for (const auto& voter : state.voters) {
                             auto voterPtr = make_shared<LogEvent>(
                                 android::util::SUBSYSTEM_SLEEP_STATE,
                                 wallClockTimestampNs, elapsedTimestampNs);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 14f2de0..13579d2 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -49,7 +49,7 @@
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
 
 // for CountMetricDataWrapper
 const int FIELD_ID_DATA = 1;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 7797bd9..0425671 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -48,7 +48,7 @@
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
 // for DurationMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for DurationMetricData
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 31a4361..ea125d0 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -44,7 +44,7 @@
 // for StatsLogReport
 const int FIELD_ID_ID = 1;
 const int FIELD_ID_EVENT_METRICS = 4;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
 // for EventMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for EventMetricData
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 03e42ce..98a33f5 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -49,7 +49,7 @@
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
 // for GaugeMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index ac34f47..dd969c0 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -412,7 +412,7 @@
 // Returns the total byte size of all metrics managed by a single config source.
 size_t MetricsManager::byteSize() {
     size_t totalSize = 0;
-    for (auto metricProducer : mAllMetricProducers) {
+    for (const auto& metricProducer : mAllMetricProducers) {
         totalSize += metricProducer->byteSize();
     }
     return totalSize;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 1f22a6a..7475b53 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -52,7 +52,7 @@
 const int FIELD_ID_BUCKET_SIZE = 10;
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
 // for ValueMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index b317361..180a1ae 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -598,7 +598,7 @@
         }
         noReportMetricIds.insert(no_report_metric);
     }
-    for (auto it : allMetricProducers) {
+    for (const auto& it : allMetricProducers) {
         uidMap.addListener(it);
     }
     return true;
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 59f3f04..e9c43cd 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -152,7 +152,7 @@
     // listener removes itself before we call it. It's then the listener's job to handle it (expect
     // the callback to be called after listener is removed, and the listener should properly
     // ignore it).
-    for (auto weakPtr : broadcastList) {
+    for (const auto& weakPtr : broadcastList) {
         auto strongPtr = weakPtr.promote();
         if (strongPtr != NULL) {
             strongPtr->onUidMapReceived(timestamp);
@@ -200,7 +200,7 @@
         StatsdStats::getInstance().setUidMapChanges(mChanges.size());
     }
 
-    for (auto weakPtr : broadcastList) {
+    for (const auto& weakPtr : broadcastList) {
         auto strongPtr = weakPtr.promote();
         if (strongPtr != NULL) {
             strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
@@ -269,7 +269,7 @@
         getListenerListCopyLocked(&broadcastList);
     }
 
-    for (auto weakPtr : broadcastList) {
+    for (const auto& weakPtr : broadcastList) {
         auto strongPtr = weakPtr.promote();
         if (strongPtr != NULL) {
             strongPtr->notifyAppRemoved(timestamp, app, uid);
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index a6f27c8..5a87e46 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -220,7 +220,9 @@
 
   optional DimensionsValue dimensions_path_in_condition = 12;
 
-  optional bool is_active = 13;
+  // DO NOT USE field 13.
+
+  optional bool is_active = 14;
 }
 
 message UidMapping {
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
index 79bed52..960fbda 100644
--- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -71,12 +71,12 @@
                          const std::shared_ptr<DimToValMap>& currentBucket,
                          const std::set<const MetricDimensionKey>& trueList,
                          const std::set<const MetricDimensionKey>& falseList) {
-    for (MetricDimensionKey key : trueList) {
+    for (const MetricDimensionKey& key : trueList) {
         if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
             return false;
         }
     }
-    for (MetricDimensionKey key : falseList) {
+    for (const MetricDimensionKey& key : falseList) {
         if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
             return false;
         }
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index 597377e..8464b8df 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -70,9 +70,16 @@
     /**
      * Dumps the report from the device and converts it to a ConfigMetricsReportList.
      * Erases the data if clearData is true.
+     * @param configId id of the config
+     * @param clearData whether to erase the report data from statsd after getting the report.
+     * @param useShellUid Pulls data for the {@link SHELL_UID} instead of the caller's uid.
+     * @param logger Logger to log error messages
+     * @return
+     * @throws IOException
+     * @throws InterruptedException
      */
     public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
-            Logger logger) throws IOException, InterruptedException {
+            boolean useShellUid, Logger logger) throws IOException, InterruptedException {
         try {
             File outputFile = File.createTempFile("statsdret", ".bin");
             outputFile.deleteOnExit();
@@ -82,7 +89,7 @@
                     "adb",
                     "shell",
                     CMD_DUMP_REPORT,
-                    SHELL_UID,
+                    useShellUid ? SHELL_UID : "",
                     String.valueOf(configId),
                     clearData ? "" : "--keep_data",
                     "--include_current_bucket",
@@ -93,8 +100,8 @@
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
             logger.severe("Failed to fetch and parse the statsd output report. "
                             + "Perhaps there is not a valid statsd config for the requested "
-                            + "uid=" + SHELL_UID
-                            + ", configId=" + configId
+                            + (useShellUid ? ("uid=" + SHELL_UID + ", ") : "")
+                            + "configId=" + configId
                             + ".");
             throw (e);
         }
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
index 08074ed..67fb906 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -165,7 +165,7 @@
             try {
                 Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
                         Utils.SHELL_UID, String.valueOf(configId));
-                Utils.getReportList(configId, true /* clearData */, sLogger);
+                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
             } catch (InterruptedException | IOException e) {
                 sLogger.severe("Failed to remove config: " + e.getMessage());
                 return false;
@@ -234,7 +234,7 @@
         // Even if the args request no modifications, we still parse it to make sure it's valid.
         ConfigMetricsReportList reportList;
         try {
-            reportList = Utils.getReportList(configId, clearData, sLogger);
+            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -278,7 +278,7 @@
         sLogger.fine(String.format("cmdClear with %d", configId));
 
         try {
-            Utils.getReportList(configId, true /* clearData */, sLogger);
+            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index f7bd44a..e3fe928 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -165,7 +165,7 @@
     }
 
     private void dumpMetrics() throws Exception {
-        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, logger);
+        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, logger);
         // We may get multiple reports. Take the last one.
         ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
         // Really should be only one metric.
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index 9b4d199..9e2230b 100644
--- a/config/dirty-image-objects
+++ b/config/dirty-image-objects
@@ -44,7 +44,6 @@
 sun.misc.FormattedFloatingDecimal
 java.util.stream.IntStream
 android.icu.util.TimeZone
-libcore.io.DropBox
 org.apache.harmony.luni.internal.util.TimezoneGetter
 dalvik.system.SocketTagger
 dalvik.system.CloseGuard
@@ -137,7 +136,6 @@
 android.icu.util.ULocale
 dalvik.system.BaseDexClassLoader
 android.icu.text.BreakIterator
-libcore.io.EventLogger
 libcore.net.NetworkSecurityPolicy
 android.icu.text.UnicodeSet
 com.android.org.conscrypt.TrustedCertificateStore$PreloadHolder
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 458c7de..5cd3d5d 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -2730,8 +2730,6 @@
 Lcom/android/internal/telephony/CommandsInterface;->changeBarringPassword(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/os/Message;)V
 Lcom/android/internal/telephony/CommandsInterface;->deleteSmsOnRuim(ILandroid/os/Message;)V
 Lcom/android/internal/telephony/CommandsInterface;->deleteSmsOnSim(ILandroid/os/Message;)V
-Lcom/android/internal/telephony/CommandsInterface;->dial(Ljava/lang/String;ILandroid/os/Message;)V
-Lcom/android/internal/telephony/CommandsInterface;->dial(Ljava/lang/String;ILcom/android/internal/telephony/UUSInfo;Landroid/os/Message;)V
 Lcom/android/internal/telephony/CommandsInterface;->exitEmergencyCallbackMode(Landroid/os/Message;)V
 Lcom/android/internal/telephony/CommandsInterface;->getBasebandVersion(Landroid/os/Message;)V
 Lcom/android/internal/telephony/CommandsInterface;->getCdmaBroadcastConfig(Landroid/os/Message;)V
@@ -2915,7 +2913,6 @@
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentDataStallAlarm(Landroid/content/Intent;)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentProvisioningApnAlarm(Landroid/content/Intent;)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubIdChanged()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z
 Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V
@@ -3744,8 +3741,6 @@
 Lcom/android/internal/telephony/TelephonyProperties;->PROPERTY_ICC_OPERATOR_NUMERIC:Ljava/lang/String;
 Lcom/android/internal/telephony/test/InterpreterEx;-><init>(Ljava/lang/String;)V
 Lcom/android/internal/telephony/test/SimulatedCommands;->acceptCall(Landroid/os/Message;)V
-Lcom/android/internal/telephony/test/SimulatedCommands;->dial(Ljava/lang/String;ILandroid/os/Message;)V
-Lcom/android/internal/telephony/test/SimulatedCommands;->dial(Ljava/lang/String;ILcom/android/internal/telephony/UUSInfo;Landroid/os/Message;)V
 Lcom/android/internal/telephony/test/SimulatedCommands;->mDcSuccess:Z
 Lcom/android/internal/telephony/test/SimulatedCommands;->resultFail(Landroid/os/Message;Ljava/lang/Object;Ljava/lang/Throwable;)V
 Lcom/android/internal/telephony/test/SimulatedCommands;->resultSuccess(Landroid/os/Message;Ljava/lang/Object;)V
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 3095925..c8a2a9c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6137,12 +6137,6 @@
 libcore.io.ClassPathURLStreamHandler
 libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection
 libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection$1
-libcore.io.DropBox
-libcore.io.DropBox$DefaultReporter
-libcore.io.DropBox$Reporter
-libcore.io.EventLogger
-libcore.io.EventLogger$DefaultReporter
-libcore.io.EventLogger$Reporter
 libcore.io.ForwardingOs
 libcore.io.IoBridge
 libcore.io.IoTracker
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5e445d14..48a767b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,7 +121,6 @@
 import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.ContentCaptureManager;
 import android.widget.AdapterView;
 import android.widget.Toast;
@@ -1027,28 +1026,39 @@
         return mContentCaptureManager;
     }
 
-    private void notifyContentCaptureManagerIfNeeded(@ContentCaptureEvent.EventType int event) {
+    /** @hide */ private static final int CONTENT_CAPTURE_START = 1;
+    /** @hide */ private static final int CONTENT_CAPTURE_FLUSH = 2;
+    /** @hide */ private static final int CONTENT_CAPTURE_STOP = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "CONTENT_CAPTURE_" }, value = {
+            CONTENT_CAPTURE_START,
+            CONTENT_CAPTURE_FLUSH,
+            CONTENT_CAPTURE_STOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContentCaptureNotificationType{}
+
+
+    private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
         final ContentCaptureManager cm = getContentCaptureManager();
         if (cm == null || !cm.isContentCaptureEnabled()) {
             return;
         }
-        switch (event) {
-            case ContentCaptureEvent.TYPE_ACTIVITY_CREATED:
+        switch (type) {
+            case CONTENT_CAPTURE_START:
                 //TODO(b/111276913): decide whether the InteractionSessionId should be
-                // saved / restored in the activity bundle.
-                cm.onActivityCreated(mToken, getComponentName());
+                // saved / restored in the activity bundle - probably not
+                cm.onActivityStarted(mToken, getComponentName());
                 break;
-            case ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED:
-                cm.onActivityDestroyed();
+            case CONTENT_CAPTURE_FLUSH:
+                cm.flush();
                 break;
-            case ContentCaptureEvent.TYPE_ACTIVITY_STARTED:
-            case ContentCaptureEvent.TYPE_ACTIVITY_RESUMED:
-            case ContentCaptureEvent.TYPE_ACTIVITY_PAUSED:
-            case ContentCaptureEvent.TYPE_ACTIVITY_STOPPED:
-                cm.onActivityLifecycleEvent(event);
+            case CONTENT_CAPTURE_STOP:
+                cm.onActivityStopped();
                 break;
             default:
-                Log.w(TAG, "notifyContentCaptureManagerIfNeeded(): invalid type " + event);
+                Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
         }
     }
 
@@ -1417,7 +1427,6 @@
         mRestoredFromBundle = savedInstanceState != null;
         mCalled = true;
 
-        notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_CREATED);
     }
 
     /**
@@ -1651,7 +1660,7 @@
         if (mAutoFillResetNeeded) {
             getAutofillManager().onVisibleForAutofill();
         }
-        notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STARTED);
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
     }
 
     /**
@@ -1734,8 +1743,8 @@
                 }
             }
         }
-        notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_RESUMED);
         mCalled = true;
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_FLUSH);
     }
 
     /**
@@ -2128,8 +2137,8 @@
                 mAutoFillIgnoreFirstResumePause = false;
             }
         }
-        notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_PAUSED);
         mCalled = true;
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_FLUSH);
     }
 
     /**
@@ -2317,7 +2326,7 @@
                 getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
                         mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
             }
-            notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STOPPED);
+            notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
         }
     }
 
@@ -2388,9 +2397,6 @@
         }
 
         dispatchActivityDestroyed();
-
-        notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED);
-
     }
 
     /**
@@ -2545,7 +2551,7 @@
      * picture-in-picture.
      *
      * @return true if the system successfully put this activity into picture-in-picture mode or was
-     * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device
+     * already in picture-in-picture mode (see {@link #isInPictureInPictureMode()}). If the device
      * does not support picture-in-picture, return false.
      */
     public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fe9b1ff..1b45d17 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -86,7 +86,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
-import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.GraphicsEnvironment;
@@ -166,8 +165,6 @@
 import dalvik.system.VMDebug;
 import dalvik.system.VMRuntime;
 
-import libcore.io.DropBox;
-import libcore.io.EventLogger;
 import libcore.io.ForwardingOs;
 import libcore.io.IoUtils;
 import libcore.io.Os;
@@ -6726,9 +6723,6 @@
             }
         }
 
-        // add dropbox logging to libcore
-        DropBox.setReporter(new DropBoxReporter());
-
         ViewRootImpl.ConfigChangedCallback configChangedCallback
                 = (Configuration globalConfig) -> {
             synchronized (mResourcesManager) {
@@ -6782,39 +6776,6 @@
         }
     }
 
-    private static class EventLoggingReporter implements EventLogger.Reporter {
-        @Override
-        public void report (int code, Object... list) {
-            EventLog.writeEvent(code, list);
-        }
-    }
-
-    private static class DropBoxReporter implements DropBox.Reporter {
-
-        private DropBoxManager dropBox;
-
-        public DropBoxReporter() {}
-
-        @Override
-        public void addData(String tag, byte[] data, int flags) {
-            ensureInitialized();
-            dropBox.addData(tag, data, flags);
-        }
-
-        @Override
-        public void addText(String tag, String data) {
-            ensureInitialized();
-            dropBox.addText(tag, data);
-        }
-
-        private synchronized void ensureInitialized() {
-            if (dropBox == null) {
-                dropBox = currentActivityThread().getApplication()
-                        .getSystemService(DropBoxManager.class);
-            }
-        }
-    }
-
     private static class AndroidOs extends ForwardingOs {
         /**
          * Install selective syscall interception. For example, this is used to
@@ -6904,9 +6865,6 @@
 
         Environment.initForCurrentUser();
 
-        // Set the reporter for event logging in libcore
-        EventLogger.setReporter(new EventLoggingReporter());
-
         // Make sure TrustedCertificateStore looks in the right place for CA certificates
         final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
         TrustedCertificateStore.setDefaultUserDirectory(configDir);
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 2c435a2..680fed8 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -361,7 +361,8 @@
                 DISPLAY_NAME + "@" + System.identityHashCode(this),
                 width, height, getBaseDisplayDensity(), mTmpSurface,
                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
-                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
         if (mVirtualDisplay == null) {
             Log.e(TAG, "Failed to initialize ActivityView");
             return;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2b81c86..17529a6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -794,12 +794,25 @@
 
     @Override
     public List<ModuleInfo> getInstalledModules(int flags) {
-        return null;
+        try {
+            return mPM.getInstalledModules(flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @Override
     public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {
-        return null;
+        try {
+            ModuleInfo mi = mPM.getModuleInfo(packageName, flags);
+            if (mi != null) {
+                return mi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException("No module info for package: " + packageName);
     }
 
     @SuppressWarnings("unchecked")
@@ -3002,7 +3015,6 @@
         }
     }
 
-    @Override
     public void sendDeviceCustomizationReadyBroadcast() {
         try {
             mPM.sendDeviceCustomizationReadyBroadcast();
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index e2f2075..fe23e21 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -20,10 +20,14 @@
 
 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 java.util.Objects;
 
@@ -40,6 +44,7 @@
     private @InterruptionFilter int interruptionFilter;
     private Uri conditionId;
     private ComponentName owner;
+    private ComponentName configurationActivity;
     private long creationTime;
     private ZenPolicy mZenPolicy;
     private boolean mModified = false;
@@ -49,39 +54,51 @@
      *
      * @param name The name of the rule.
      * @param owner The Condition Provider service that owns this rule.
-     * @param conditionId A representation of the state that should cause the Condition Provider
-     *                    service to apply the given interruption filter.
      * @param interruptionFilter The interruption filter defines which notifications are allowed to
      *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
      *                           is active.
      * @param enabled Whether the rule is enabled.
+     * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
+     * ZenPolicy, int, boolean)}.
      */
+    @Deprecated
     public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
             int interruptionFilter, boolean enabled) {
-        this.name = name;
-        this.owner = owner;
-        this.conditionId = conditionId;
-        this.interruptionFilter = interruptionFilter;
-        this.enabled = enabled;
+        this(name, owner, null, conditionId, null, interruptionFilter, enabled);
     }
 
     /**
      * Creates an automatic zen rule.
      *
      * @param name The name of the rule.
-     * @param owner The Condition Provider service that owns this rule.
-     * @param conditionId A representation of the state that should cause the Condition Provider
-     *                    service to apply the given interruption filter.
+     * @param owner The Condition Provider service that owns this rule. This can be null if you're
+     *              using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)}
+     *              instead of {@link android.service.notification.ConditionProviderService}.
+     * @param configurationActivity An activity that handles
+     *                              {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows
+     *                              the user
+     *                              more information about this rule and/or allows them to
+     *                              configure it. This is required if you are not using a
+     *                              {@link android.service.notification.ConditionProviderService}.
+     *                              If you are, it overrides the information specified in your
+     *                              manifest.
+     * @param conditionId A representation of the state that should cause your app to apply the
+     *                    given interruption filter.
+     * @param interruptionFilter The interruption filter defines which notifications are allowed to
+     *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
+     *                           is active.
      * @param policy The policy defines which notifications are allowed to interrupt the user
-     *               while this rule is active
+     *               while this rule is active. This overrides the global policy while this rule is
+     *               action ({@link Condition#STATE_TRUE}).
      * @param enabled Whether the rule is enabled.
      */
-    public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, ZenPolicy policy,
-            boolean enabled) {
+    public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
+            Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled) {
         this.name = name;
         this.owner = owner;
+        this.configurationActivity = configurationActivity;
         this.conditionId = conditionId;
-        this.interruptionFilter = INTERRUPTION_FILTER_PRIORITY;
+        this.interruptionFilter = interruptionFilter;
         this.enabled = enabled;
         this.mZenPolicy = policy;
     }
@@ -89,18 +106,10 @@
     /**
      * @hide
      */
-    public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
-            int interruptionFilter, boolean enabled, long creationTime) {
-        this(name, owner, conditionId, interruptionFilter, enabled);
-        this.creationTime = creationTime;
-    }
-
-    /**
-     * @hide
-     */
-    public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, ZenPolicy policy,
-            boolean enabled, long creationTime) {
-        this(name, owner, conditionId, policy, enabled);
+    public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
+            Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
+            long creationTime) {
+        this(name, owner, configurationActivity, conditionId, policy, interruptionFilter, enabled);
         this.creationTime = creationTime;
     }
 
@@ -112,6 +121,7 @@
         interruptionFilter = source.readInt();
         conditionId = source.readParcelable(null);
         owner = source.readParcelable(null);
+        configurationActivity = source.readParcelable(null);
         creationTime = source.readLong();
         mZenPolicy = source.readParcelable(null);
         mModified = source.readInt() == ENABLED;
@@ -125,6 +135,14 @@
     }
 
     /**
+     * Returns the {@link ComponentName} of the activity that shows configuration options
+     * for this rule.
+     */
+    public ComponentName getConfigurationActivity() {
+        return configurationActivity;
+    }
+
+    /**
      * Returns the representation of the state that causes this rule to become active.
      */
     public Uri getConditionId() {
@@ -218,6 +236,15 @@
         this.mZenPolicy = zenPolicy;
     }
 
+    /**
+     * Sets the configuration activity - an activity that handles
+     * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
+     * about this rule and/or allows them to configure it.
+     */
+    public void setConfigurationActivity(ComponentName componentName) {
+        this.configurationActivity = componentName;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -235,6 +262,7 @@
         dest.writeInt(interruptionFilter);
         dest.writeParcelable(conditionId, 0);
         dest.writeParcelable(owner, 0);
+        dest.writeParcelable(configurationActivity, 0);
         dest.writeLong(creationTime);
         dest.writeParcelable(mZenPolicy, 0);
         dest.writeInt(mModified ? ENABLED : DISABLED);
@@ -248,6 +276,7 @@
                 .append(",interruptionFilter=").append(interruptionFilter)
                 .append(",conditionId=").append(conditionId)
                 .append(",owner=").append(owner)
+                .append(",configActivity=").append(configurationActivity)
                 .append(",creationTime=").append(creationTime)
                 .append(",mZenPolicy=").append(mZenPolicy)
                 .append(']').toString();
@@ -264,14 +293,15 @@
                 && other.interruptionFilter == interruptionFilter
                 && Objects.equals(other.conditionId, conditionId)
                 && Objects.equals(other.owner, owner)
-                && other.creationTime == creationTime
-                && Objects.equals(other.mZenPolicy, mZenPolicy);
+                && Objects.equals(other.mZenPolicy, mZenPolicy)
+                && Objects.equals(other.configurationActivity, configurationActivity)
+                && other.creationTime == creationTime;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, creationTime,
-                mZenPolicy, mModified);
+        return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
+                configurationActivity, mZenPolicy, mModified, creationTime);
     }
 
     public static final Parcelable.Creator<AutomaticZenRule> CREATOR
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e508d42..00567523 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -164,6 +164,7 @@
     boolean removeAutomaticZenRule(String id);
     boolean removeAutomaticZenRules(String packageName);
     int getRuleInstanceCount(in ComponentName owner);
+    void setAutomaticZenRuleState(String id, in Condition condition);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index f8309bc..1ae0f52 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -270,14 +270,14 @@
     }
 
     /**
+     * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
+     * you to disable / reenable the keyguard.
+     *
      * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
      * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
      * instead; this allows you to seamlessly hide the keyguard as your application
      * moves in and out of the foreground and does not require that any special
      * permissions be requested.
-     *
-     * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
-     * you to disable / reenable the keyguard.
      */
     @Deprecated
     public class KeyguardLock {
@@ -303,7 +303,7 @@
         @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
         public void disableKeyguard() {
             try {
-                mWM.disableKeyguard(mToken, mTag);
+                mWM.disableKeyguard(mToken, mTag, mContext.getUserId());
             } catch (RemoteException ex) {
             }
         }
@@ -322,16 +322,17 @@
         @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
         public void reenableKeyguard() {
             try {
-                mWM.reenableKeyguard(mToken);
+                mWM.reenableKeyguard(mToken, mContext.getUserId());
             } catch (RemoteException ex) {
             }
         }
     }
 
     /**
-     * @deprecated Use {@link KeyguardDismissCallback}
      * Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify
      * caller of result.
+     *
+     * @deprecated Use {@link KeyguardDismissCallback}
      */
     @Deprecated
     public interface OnKeyguardExitResult {
@@ -380,12 +381,6 @@
     }
 
     /**
-     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
-     * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
-     * instead; this allows you to seamlessly hide the keyguard as your application
-     * moves in and out of the foreground and does not require that any special
-     * permissions be requested.
-     *
      * Enables you to lock or unlock the keyguard. Get an instance of this class by
      * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
      * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
@@ -394,6 +389,12 @@
      *
      * @return A {@link KeyguardLock} handle to use to disable and reenable the
      *   keyguard.
+     *
+     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
+     *   and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+     *   instead; this allows you to seamlessly hide the keyguard as your application
+     *   moves in and out of the foreground and does not require that any special
+     *   permissions be requested.
      */
     @Deprecated
     public KeyguardLock newKeyguardLock(String tag) {
@@ -430,13 +431,12 @@
     }
 
     /**
-     * @deprecated Use {@link #isKeyguardLocked()} instead.
-     *
      * If keyguard screen is showing or in restricted key input mode (i.e. in
      * keyguard password emergency screen). When in such mode, certain keys,
      * such as the Home key and the right soft keys, don't work.
      *
      * @return true if in keyguard restricted input mode.
+     * @deprecated Use {@link #isKeyguardLocked()} instead.
      */
     public boolean inKeyguardRestrictedInputMode() {
         return isKeyguardLocked();
@@ -588,12 +588,6 @@
     }
 
     /**
-     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
-     * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
-     * instead; this allows you to seamlessly hide the keyguard as your application
-     * moves in and out of the foreground and does not require that any special
-     * permissions be requested.
-     *
      * Exit the keyguard securely.  The use case for this api is that, after
      * disabling the keyguard, your app, which was granted permission to
      * disable the keyguard and show a limited amount of information deemed
@@ -606,6 +600,12 @@
      * @param callback Lets you know whether the operation was successful and
      *   it is safe to launch anything that would normally be considered safe
      *   once the user has gotten past the keyguard.
+
+     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
+     *   and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+     *   instead; this allows you to seamlessly hide the keyguard as your application
+     *   moves in and out of the foreground and does not require that any special
+     *   permissions be requested.
      */
     @Deprecated
     @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 25fa897..306c366 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -41,6 +41,7 @@
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.service.notification.Condition;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
@@ -262,6 +263,68 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Importance {}
 
+    /**
+     * Activity Action: Launch an Automatic Zen Rule configuration screen
+     * <p>
+     * Input: Optionally, {@link #EXTRA_AUTOMATIC_RULE_ID}, if the configuration screen for an
+     * existing rule should be displayed. If the rule id is missing or null, apps should display
+     * a configuration screen where users can create a new instance of the rule.
+     * <p>
+     * Output: Nothing
+     * <p>
+     *     You can have multiple activities handling this intent, if you support multiple
+     *     {@link AutomaticZenRule rules}. In order for the system to properly display all of your
+     *     rule types so that users can create new instances or configure existing ones, you need
+     *     to add some extra metadata ({@link #META_DATA_AUTOMATIC_RULE_TYPE})
+     *     to your activity tag in your manifest. If you'd like to limit the number of rules a user
+     *     can create from this flow, you can additionally optionally include
+     *     {@link #META_DATA_RULE_INSTANCE_LIMIT}.
+     *
+     *     For example,
+     *     &lt;meta-data
+     *         android:name="android.app.zen.automatic.ruleType"
+     *         android:value="@string/my_condition_rule">
+     *     &lt;/meta-data>
+     *     &lt;meta-data
+     *         android:name="android.app.zen.automatic.ruleInstanceLimit"
+     *         android:value="1">
+     *     &lt;/meta-data>
+     * </p>
+     * </p>
+     *
+     * @see {@link #addAutomaticZenRule(AutomaticZenRule)}
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_AUTOMATIC_ZEN_RULE =
+            "android.app.action.AUTOMATIC_ZEN_RULE";
+
+    /**
+     * Used as an optional string extra on {@link #ACTION_AUTOMATIC_ZEN_RULE} intents. If
+     * provided, contains the id of the {@link AutomaticZenRule} (as returned from
+     * {@link NotificationManager#addAutomaticZenRule(AutomaticZenRule)}) for which configuration
+     * settings should be displayed.
+     */
+    public static final String EXTRA_AUTOMATIC_RULE_ID = "android.app.extra.AUTOMATIC_RULE_ID";
+
+    /**
+     * A required {@code meta-data} tag for activities that handle
+     * {@link #ACTION_AUTOMATIC_ZEN_RULE}.
+     *
+     * This tag should contain a localized name of the type of the zen rule provided by the
+     * activity.
+     */
+    public static final String META_DATA_AUTOMATIC_RULE_TYPE = "android.app.automatic.ruleType";
+
+    /**
+     * An optional {@code meta-data} tag for activities that handle
+     * {@link #ACTION_AUTOMATIC_ZEN_RULE}.
+     *
+     * This tag should contain the maximum number of rule instances that
+     * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+     */
+    public static final String META_DATA_RULE_INSTANCE_LIMIT =
+            "android.app.zen.automatic.ruleInstanceLimit";
+
     /** Value signifying that the user has not expressed a per-app visibility override value.
      * @hide */
     public static final int VISIBILITY_NO_OVERRIDE = -1000;
@@ -859,14 +922,10 @@
             List<ZenModeConfig.ZenRule> rules = service.getZenRules();
             Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
             for (ZenModeConfig.ZenRule rule : rules) {
-                if (rule.zenPolicy == null) {
-                    ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
-                            rule.conditionId, zenModeToInterruptionFilter(rule.zenMode),
-                            rule.enabled, rule.creationTime));
-                } else {
-                    ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
-                            rule.conditionId, rule.zenPolicy, rule.enabled, rule.creationTime));
-                }
+                ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
+                        rule.configurationActivity, rule.conditionId, rule.zenPolicy,
+                        zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
+                        rule.creationTime));
             }
             return ruleMap;
         } catch (RemoteException e) {
@@ -936,6 +995,26 @@
     }
 
     /**
+     * Informs the notification manager that the state of an {@link AutomaticZenRule} has changed.
+     * Use this method to put the system into Do Not Disturb mode or request that it exits Do Not
+     * Disturb mode. The calling app must own the provided {@link android.app.AutomaticZenRule}.
+     * <p>
+     *     This method can be used in conjunction with or as a replacement to
+     *     {@link android.service.notification.ConditionProviderService#notifyCondition(Condition)}.
+     * </p>
+     * @param id The id of the rule whose state should change
+     * @param condition The new state of this rule
+     */
+    public void setAutomaticZenRuleState(String id, Condition condition) {
+        INotificationManager service = getService();
+        try {
+            service.setAutomaticZenRuleState(id, condition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Deletes the automatic zen rule with the given id.
      *
      * <p>
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
index ebbf317..392921e 100644
--- a/core/java/android/app/RemoteInput.java
+++ b/core/java/android/app/RemoteInput.java
@@ -173,7 +173,7 @@
 
     /**
      * Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput}
-     * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes is
+     * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes} is
      * non-null and not empty.
      */
     public boolean isDataOnly() {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f4fd5d1..45e87e0 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
 import android.hardware.ConsumerIrManager;
 import android.hardware.ISerialManager;
 import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.SerialManager;
 import android.hardware.SystemSensorManager;
 import android.hardware.biometrics.BiometricManager;
@@ -503,6 +504,13 @@
                   ctx.mMainThread.getHandler().getLooper());
             }});
 
+        registerService(Context.SENSOR_PRIVACY_SERVICE, SensorPrivacyManager.class,
+                new CachedServiceFetcher<SensorPrivacyManager>() {
+                    @Override
+                    public SensorPrivacyManager createService(ContextImpl ctx) {
+                        return SensorPrivacyManager.getInstance(ctx);
+                    }});
+
         registerService(Context.STATS_MANAGER, StatsManager.class,
                 new CachedServiceFetcher<StatsManager>() {
             @Override
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 6248e7c..5b87de4 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -5,7 +5,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -222,7 +221,6 @@
      * @param componentName ComponentName of a VR InputMethod that should be set as selected
      * input by InputMethodManagerService.
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     public void setVrInputMethod(ComponentName componentName) {
         try {
diff --git a/core/java/android/app/WaitResult.java b/core/java/android/app/WaitResult.java
index 5baf2e2..ad9f680 100644
--- a/core/java/android/app/WaitResult.java
+++ b/core/java/android/app/WaitResult.java
@@ -16,11 +16,14 @@
 
 package android.app;
 
+import android.annotation.IntDef;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Information returned after waiting for an activity start.
@@ -28,11 +31,43 @@
  * @hide
  */
 public class WaitResult implements Parcelable {
+
+    /**
+     * The state at which a launch sequence had started.
+     *
+     * @see <a href="https://developer.android.com/topic/performance/vitals/launch-time"App startup
+     * time</a>
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"LAUNCH_STATE_"}, value = {
+            LAUNCH_STATE_COLD,
+            LAUNCH_STATE_WARM,
+            LAUNCH_STATE_HOT
+    })
+    public @interface LaunchState {
+    }
+
+    /**
+     * Cold launch sequence: a new process has started.
+     */
+    public static final int LAUNCH_STATE_COLD = 1;
+
+    /**
+     * Warm launch sequence: process reused, but activity has to be created.
+     */
+    public static final int LAUNCH_STATE_WARM = 2;
+
+    /**
+     * Hot launch sequence: process reused, activity brought-to-top.
+     */
+    public static final int LAUNCH_STATE_HOT = 3;
+
     public static final int INVALID_DELAY = -1;
     public int result;
     public boolean timeout;
     public ComponentName who;
     public long totalTime;
+    public @LaunchState int launchState;
 
     public WaitResult() {
     }
@@ -48,6 +83,7 @@
         dest.writeInt(timeout ? 1 : 0);
         ComponentName.writeToParcel(who, dest);
         dest.writeLong(totalTime);
+        dest.writeInt(launchState);
     }
 
     public static final Parcelable.Creator<WaitResult> CREATOR
@@ -68,6 +104,7 @@
         timeout = source.readInt() != 0;
         who = ComponentName.readFromParcel(source);
         totalTime = source.readLong();
+        launchState = source.readInt();
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -76,5 +113,19 @@
         pw.println(prefix + "  timeout=" + timeout);
         pw.println(prefix + "  who=" + who);
         pw.println(prefix + "  totalTime=" + totalTime);
+        pw.println(prefix + "  launchState=" + launchState);
+    }
+
+    public static String launchStateToString(@LaunchState int type) {
+        switch (type) {
+            case LAUNCH_STATE_COLD:
+                return "COLD";
+            case LAUNCH_STATE_WARM:
+                return "WARM";
+            case LAUNCH_STATE_HOT:
+                return "HOT";
+            default:
+                return "UNKNOWN (" + type + ")";
+        }
     }
 }
\ No newline at end of file
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 0afb98f..f1e6b06 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -189,8 +189,11 @@
      * completed.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If the {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL permission.
      *
-     * @param fd The file descriptor to which a 'tar' file stream is to be written
+     * @param userId User id for which backup should be performed.
+     * @param fd The file descriptor to which a 'tar' file stream is to be written.
      * @param includeApks If <code>true</code>, the resulting tar stream will include the
      *     application .apk files themselves as well as their data.
      * @param includeObbs If <code>true</code>, the resulting tar stream will include any
@@ -209,7 +212,7 @@
      * @param packageNames The package names of the apps whose data (and optionally .apk files)
      *     are to be backed up.  The <code>allApps</code> parameter supersedes this.
      */
-    void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+    void adbBackup(int userId, in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
             boolean doCompress, boolean doKeyValue, in String[] packageNames);
 
@@ -227,8 +230,12 @@
      * Currently only used by the 'adb restore' command.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     * If the {@code userId} is different from the calling user id, then the caller must hold the
+     * android.permission.INTERACT_ACROSS_USERS_FULL.
+     *
+     * @param userId User id for which restore should be performed.
      */
-    void adbRestore(in ParcelFileDescriptor fd);
+    void adbRestore(int userId, in ParcelFileDescriptor fd);
 
     /**
      * Confirm that the requested full backup/restore operation can proceed.  The system will
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index e84517d..e7fe161 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -1545,13 +1545,6 @@
          * @return The job object to hand to the JobScheduler. This object is immutable.
          */
         public JobInfo build() {
-            // Allow jobs with no constraints - What am I, a database?
-            if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
-                    mNetworkRequest == null &&
-                    mTriggerContentUris == null) {
-                throw new IllegalArgumentException("You're trying to build a job with no " +
-                        "constraints, this is not allowed.");
-            }
             // Check that network estimates require network type
             if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 4ce0f31..0c9b41bf 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -48,4 +48,6 @@
     boolean addRoleHolderFromController(in String roleName, in String packageName);
 
     boolean removeRoleHolderFromController(in String roleName, in String packageName);
+
+    List<String> getHeldRolesFromController(in String packageName);
 }
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 5d101ab..2d630a6 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -542,6 +542,27 @@
         }
     }
 
+
+    /**
+     * Returns the list of all roles that the given package is currently holding
+     *
+     * @param packageName the package name
+     * @return the list of role names
+     *
+     * @hide
+     */
+    @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+    @SystemApi
+    @NonNull
+    public List<String> getHeldRolesFromController(@NonNull String packageName) {
+        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+        try {
+            return mService.getHeldRolesFromController(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
 
         @NonNull
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index ca60e14..0ccd49f 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -209,7 +209,7 @@
      *
      * @param sliceUri Uri to bind.
      * @param supportedSpecs List of supported specs.
-     * @see Slice.
+     * @see Slice
      * @see Slice#HINT_PARTIAL
      */
     public Slice onBindSlice(Uri sliceUri, Set<SliceSpec> supportedSpecs) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8e6a385..87b64797 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -643,6 +643,7 @@
     private final IBluetoothManager mManagerService;
     @UnsupportedAppUsage
     private IBluetooth mService;
+    private Context mContext;
     private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
 
     private final Object mLock = new Object();
@@ -1541,6 +1542,23 @@
     }
 
     /**
+     * Set the context for this BluetoothAdapter (only called from BluetoothManager)
+     * @hide
+     */
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    private String getOpPackageName() {
+        // Workaround for legacy API for getting a BluetoothAdapter not
+        // passing a context
+        if (mContext != null) {
+            return mContext.getOpPackageName();
+        }
+        return ActivityThread.currentOpPackageName();
+    }
+
+    /**
      * Start the remote device discovery process.
      * <p>The discovery process usually involves an inquiry scan of about 12
      * seconds, followed by a page scan of each new device to retrieve its
@@ -1577,7 +1595,7 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mService.startDiscovery();
+                return mService.startDiscovery(getOpPackageName());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index e3672a7..e08d405 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -67,6 +67,7 @@
         }
         // Legacy api - getDefaultAdapter does not take in the context
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mAdapter.setContext(context);
     }
 
     /**
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 54e6342..e6ffe8b4 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -192,6 +192,17 @@
     }
 
     /**
+     * Helper to get {@link #flattenToShortString()} in a {@link ComponentName} reference that can
+     * be {@code null}.
+     *
+     * @hide
+     */
+    @Nullable
+    public static String flattenToShortString(@Nullable ComponentName componentName) {
+        return componentName == null ? null : componentName.flattenToShortString();
+    }
+
+    /**
      * Return a String that unambiguously describes both the package and
      * class names contained in the ComponentName.  You can later recover
      * the ComponentName from this string through
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5a12e4e..f138d39 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -327,6 +327,7 @@
         public ContentProviderResult[] applyBatch(String callingPkg, String authority,
                 ArrayList<ContentProviderOperation> operations)
                 throws OperationApplicationException {
+            validateIncomingAuthority(authority);
             int numOperations = operations.size();
             final int[] userIds = new int[numOperations];
             for (int i = 0; i < numOperations; i++) {
@@ -447,6 +448,7 @@
         @Override
         public Bundle call(String callingPkg, String authority, String method, @Nullable String arg,
                 @Nullable Bundle extras) {
+            validateIncomingAuthority(authority);
             Bundle.setDefusable(extras, true);
             Trace.traceBegin(TRACE_TAG_DATABASE, "call");
             final String original = setCallingPackage(callingPkg);
@@ -1182,12 +1184,12 @@
      * Implement this to handle query requests where the arguments are packed into a {@link Bundle}.
      * Arguments may include traditional SQL style query arguments. When present these
      * should be handled  according to the contract established in
-     * {@link #query(Uri, String[], String, String[], String, CancellationSignal).
+     * {@link #query(Uri, String[], String, String[], String, CancellationSignal)}.
      *
      * <p>Traditional SQL arguments can be found in the bundle using the following keys:
-     * <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION}
-     * <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
-     * <li>{@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER}
+     * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SELECTION}
+     * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
+     * <li>{@link android.content.ContentResolver#QUERY_ARG_SQL_SORT_ORDER}
      *
      * <p>This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
@@ -1244,8 +1246,8 @@
 
         return cursor;</pre>
      * <p>
-     * @see #query(Uri, String[], String, String[], String, CancellationSignal) for
-     *     implementation details.
+     * See {@link #query(Uri, String[], String, String[], String, CancellationSignal)}
+     * for implementation details.
      *
      * @param uri The URI to query. This will be the full URI sent by the client.
      * @param projection The list of columns to put into the cursor.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b39010d..001e328 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3080,6 +3080,7 @@
             //@hide: COUNTRY_DETECTOR,
             SEARCH_SERVICE,
             SENSOR_SERVICE,
+            SENSOR_PRIVACY_SERVICE,
             STORAGE_SERVICE,
             STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
@@ -3544,6 +3545,18 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
+     * android.hardware.SensorPrivacyManager} for accessing sensor privacy
+     * functions.
+     *
+     * @see #getSystemService(String)
+     * @see android.hardware.SensorPrivacyManager
+     *
+     * @hide
+     */
+    public static final String SENSOR_PRIVACY_SERVICE = "sensor_privacy";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.os.storage.StorageManager} for accessing system storage
      * functions.
      *
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0a4f4eb..0edb36c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -236,6 +236,15 @@
     public float maxAspectRatio;
 
     /**
+     * Value indicating the minimum aspect ratio the activity supports.
+     * <p>
+     * 0 means unset.
+     * @See {@link android.R.attr#minAspectRatio}.
+     * @hide
+     */
+    public float minAspectRatio;
+
+    /**
      * Name of the VrListenerService component to run for this activity.
      * @see android.R.attr#enableVrMode
      * @hide
@@ -979,6 +988,7 @@
         rotationAnimation = orig.rotationAnimation;
         colorMode = orig.colorMode;
         maxAspectRatio = orig.maxAspectRatio;
+        minAspectRatio = orig.minAspectRatio;
     }
 
     /**
@@ -1142,6 +1152,9 @@
         if (maxAspectRatio != 0) {
             pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
         }
+        if (minAspectRatio != 0) {
+            pw.println(prefix + "minAspectRatio=" + minAspectRatio);
+        }
         super.dumpBack(pw, prefix, dumpFlags);
     }
 
@@ -1190,6 +1203,7 @@
         dest.writeInt(rotationAnimation);
         dest.writeInt(colorMode);
         dest.writeFloat(maxAspectRatio);
+        dest.writeFloat(minAspectRatio);
     }
 
     /**
@@ -1309,6 +1323,7 @@
         rotationAnimation = source.readInt();
         colorMode = source.readInt();
         maxAspectRatio = source.readFloat();
+        minAspectRatio = source.readFloat();
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index c361ac1..0c438af 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -712,6 +712,15 @@
      */
     public float maxAspectRatio;
 
+    /**
+     * Value indicating the minimum aspect ratio the application supports.
+     * <p>
+     * 0 means unset.
+     * @see {@link android.R.attr#minAspectRatio}.
+     * @hide
+     */
+    public float minAspectRatio;
+
     /** @removed */
     @Deprecated
     public String volumeUuid;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index eea2b88..a4ea513 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -36,6 +36,7 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
@@ -680,4 +681,8 @@
     boolean isPackageStateProtected(String packageName, int userId);
 
     void sendDeviceCustomizationReadyBroadcast();
+
+    List<ModuleInfo> getInstalledModules(int flags);
+
+    ModuleInfo getModuleInfo(String packageName, int flags);
 }
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/core/java/android/content/pm/ModuleInfo.aidl
similarity index 80%
copy from core/java/android/service/contentcapture/InteractionContext.aidl
copy to core/java/android/content/pm/ModuleInfo.aidl
index 982e095..cc13bf1 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/core/java/android/content/pm/ModuleInfo.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
+/*
+ * Copyright 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.contentcapture;
+package android.content.pm;
 
-parcelable InteractionContext;
+parcelable ModuleInfo;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 61a74de..e38b294 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -162,6 +163,8 @@
             SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
 
     private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+    private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO = 1.333f;
+    private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH = 1f;
 
     // TODO: switch outError users to PackageParserException
     // TODO: refactor "codePath" to "apkPath"
@@ -3673,6 +3676,7 @@
         }
 
         ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+        ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0);
 
         ai.networkSecurityConfigRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
@@ -3995,6 +3999,7 @@
         // Must be ran after the entire {@link ApplicationInfo} has been fully processed and after
         // every activity info has had a chance to set it from its attributes.
         setMaxAspectRatio(owner);
+        setMinAspectRatio(owner);
 
         PackageBackwardCompatibility.modifySharedLibraries(owner);
 
@@ -4492,6 +4497,13 @@
                         0 /*default*/));
             }
 
+            if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio)
+                    && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio)
+                    == TypedValue.TYPE_FLOAT) {
+                a.setMinAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio,
+                        0 /*default*/));
+            }
+
             a.info.lockTaskLaunchMode =
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
 
@@ -4751,6 +4763,34 @@
     }
 
     /**
+     * Sets every the max aspect ratio of every child activity that doesn't already have an aspect
+     * ratio set.
+     */
+    private void setMinAspectRatio(Package owner) {
+        final float minAspectRatio;
+        if (owner.applicationInfo.minAspectRatio != 0) {
+            // Use the application max aspect ration as default if set.
+            minAspectRatio = owner.applicationInfo.minAspectRatio;
+        } else {
+            // Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater.
+            // NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD,
+            // except for watches which always supported 1:1.
+            minAspectRatio = owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q
+                    ? 0
+                    : mCallback.hasFeature(FEATURE_WATCH)
+                            ? DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH
+                            : DEFAULT_PRE_Q_MIN_ASPECT_RATIO;
+        }
+
+        for (Activity activity : owner.activities) {
+            if (activity.hasMinAspectRatio()) {
+                continue;
+            }
+            activity.setMinAspectRatio(minAspectRatio);
+        }
+    }
+
+    /**
      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
      * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
      *                                AndroidManifest.xml.
@@ -4888,6 +4928,7 @@
         info.windowLayout = target.info.windowLayout;
         info.resizeMode = target.info.resizeMode;
         info.maxAspectRatio = target.info.maxAspectRatio;
+        info.minAspectRatio = target.info.minAspectRatio;
         info.requestedVrComponent = target.info.requestedVrComponent;
 
         info.encryptionAware = info.directBootAware = target.info.directBootAware;
@@ -7773,11 +7814,16 @@
         @UnsupportedAppUsage
         public final ActivityInfo info;
         private boolean mHasMaxAspectRatio;
+        private boolean mHasMinAspectRatio;
 
         private boolean hasMaxAspectRatio() {
             return mHasMaxAspectRatio;
         }
 
+        private boolean hasMinAspectRatio() {
+            return mHasMinAspectRatio;
+        }
+
         // To construct custom activity which does not exist in manifest
         Activity(final Package owner, final String className, final ActivityInfo info) {
             super(owner, new ArrayList<>(0), className);
@@ -7813,6 +7859,22 @@
             mHasMaxAspectRatio = true;
         }
 
+        private void setMinAspectRatio(float minAspectRatio) {
+            if (info.resizeMode == RESIZE_MODE_RESIZEABLE
+                    || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+                // Resizeable activities can be put in any aspect ratio.
+                return;
+            }
+
+            if (minAspectRatio < 1.0f && minAspectRatio != 0) {
+                // Ignore any value lesser than 1.0.
+                return;
+            }
+
+            info.minAspectRatio = minAspectRatio;
+            mHasMinAspectRatio = true;
+        }
+
         public String toString() {
             StringBuilder sb = new StringBuilder(128);
             sb.append("Activity{");
@@ -7833,12 +7895,14 @@
             super.writeToParcel(dest, flags);
             dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES);
             dest.writeBoolean(mHasMaxAspectRatio);
+            dest.writeBoolean(mHasMinAspectRatio);
         }
 
         private Activity(Parcel in) {
             super(in);
             info = in.readParcelable(Object.class.getClassLoader());
             mHasMaxAspectRatio = in.readBoolean();
+            mHasMinAspectRatio = in.readBoolean();
 
             for (ActivityIntentInfo aii : intents) {
                 aii.activity = this;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 740cdae..f1a4db2 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -203,11 +203,13 @@
             if (FEATURE_FLAG_IDMAP2) {
                 final String[] systemIdmapPaths =
                     nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
-                if (systemIdmapPaths == null) {
-                    throw new IOException("idmap2 scan failed");
-                }
-                for (String idmapPath : systemIdmapPaths) {
-                    apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                if (systemIdmapPaths != null) {
+                    for (String idmapPath : systemIdmapPaths) {
+                        apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                    }
+                } else {
+                    Log.w(TAG, "'idmap2 --scan' failed: no static=\"true\" overlays targeting "
+                            + "\"android\" will be loaded");
                 }
             } else {
                 nativeVerifySystemIdmaps();
diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl
new file mode 100644
index 0000000..5d40265
--- /dev/null
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -0,0 +1,29 @@
+/**
+ * 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.hardware;
+
+/**
+ * @hide
+ */
+oneway interface ISensorPrivacyListener {
+    // Since these transactions are also called from native code, these must be kept in sync with
+    // the ones in
+    //   frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
+    // =============== Beginning of transactions used on native side as well ======================
+    void onSensorPrivacyChanged(boolean enabled);
+    // =============== End of transactions used on native side as well ============================
+}
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
new file mode 100644
index 0000000..1ba7b98
--- /dev/null
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -0,0 +1,35 @@
+/**
+ * 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.hardware;
+
+import android.hardware.ISensorPrivacyListener;
+
+/** @hide */
+interface ISensorPrivacyManager {
+    // Since these transactions are also called from native code, these must be kept in sync with
+    // the ones in
+    //   frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+    // =============== Beginning of transactions used on native side as well ======================
+    void addSensorPrivacyListener(in ISensorPrivacyListener listener);
+
+    void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
+
+    boolean isSensorPrivacyEnabled();
+
+    void setSensorPrivacy(boolean enable);
+    // =============== End of transactions used on native side as well ============================
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
new file mode 100644
index 0000000..274202f
--- /dev/null
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -0,0 +1,171 @@
+/*
+ * 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.hardware;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * This class provides access to the sensor privacy services; sensor privacy allows the
+ * user to disable access to all sensors on the device. This class provides methods to query the
+ * current state of sensor privacy as well as to register / unregister for notification when
+ * the sensor privacy state changes.
+ *
+ * @hide
+ */
+@SystemService(Context.SENSOR_PRIVACY_SERVICE)
+public final class SensorPrivacyManager {
+
+    /**
+     * A class implementing this interface can register with the {@link
+     * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
+     * state changes.
+     */
+    public interface OnSensorPrivacyChangedListener {
+        /**
+         * Callback invoked when the sensor privacy state changes.
+         *
+         * @param enabled true if sensor privacy is enabled, false otherwise.
+         */
+        void onSensorPrivacyChanged(boolean enabled);
+    }
+
+    private static final Object sInstanceLock = new Object();
+
+    @GuardedBy("sInstanceLock")
+    private static SensorPrivacyManager sInstance;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final ISensorPrivacyManager mService;
+
+    @NonNull
+    private final ArrayMap<OnSensorPrivacyChangedListener, ISensorPrivacyListener> mListeners;
+
+    /**
+     * Private constructor to ensure only a single instance is created.
+     */
+    private SensorPrivacyManager(Context context, ISensorPrivacyManager service) {
+        mContext = context;
+        mService = service;
+        mListeners = new ArrayMap<>();
+    }
+
+    /**
+     * Returns the single instance of the SensorPrivacyManager.
+     */
+    public static SensorPrivacyManager getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                try {
+                    IBinder b = ServiceManager.getServiceOrThrow(Context.SENSOR_PRIVACY_SERVICE);
+                    ISensorPrivacyManager service = ISensorPrivacyManager.Stub.asInterface(b);
+                    sInstance = new SensorPrivacyManager(context, service);
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Sets sensor privacy to the specified state.
+     *
+     * @param enable the state to which sensor privacy should be set.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    public void setSensorPrivacy(boolean enable) {
+        try {
+            mService.setSensorPrivacy(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a new listener to receive notification when the state of sensor privacy
+     * changes.
+     *
+     * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
+     *                 privacy changes.
+     */
+    public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) {
+        synchronized (mListeners) {
+            ISensorPrivacyListener iListener = mListeners.get(listener);
+            if (iListener == null) {
+                iListener = new ISensorPrivacyListener.Stub() {
+                    @Override
+                    public void onSensorPrivacyChanged(boolean enabled) {
+                        listener.onSensorPrivacyChanged(enabled);
+                    }
+                };
+                mListeners.put(listener, iListener);
+            }
+
+            try {
+                mService.addSensorPrivacyListener(iListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the specified listener from receiving notifications when the state of sensor
+     * privacy changes.
+     *
+     * @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when
+     *                 sensor privacy changes.
+     */
+    public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) {
+        synchronized (mListeners) {
+            ISensorPrivacyListener iListener = mListeners.get(listener);
+            if (iListener != null) {
+                mListeners.remove(iListener);
+                try {
+                    mService.removeSensorPrivacyListener(iListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether sensor privacy is currently enabled.
+     *
+     * @return true if sensor privacy is currently enabled, false otherwise.
+     */
+    public boolean isSensorPrivacyEnabled() {
+        try {
+            return mService.isSensorPrivacyEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 5e402c7..105ae68 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -24,6 +24,8 @@
 import android.hardware.camera2.impl.SyntheticKey;
 import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
 import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.MandatoryStreamCombination;
+import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
 import android.hardware.camera2.utils.ArrayUtils;
 import android.hardware.camera2.utils.TypeReference;
 import android.util.Rational;
@@ -2609,6 +2611,39 @@
             new Key<android.hardware.camera2.params.ReprocessFormatsMap>("android.scaler.availableRecommendedInputOutputFormatsMap", android.hardware.camera2.params.ReprocessFormatsMap.class);
 
     /**
+     * <p>An array of mandatory stream combinations generated according to the camera device
+     * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL }
+     * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }.
+     * This is an app-readable conversion of the mandatory stream combination
+     * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
+     * <p>The array of
+     * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
+     * generated according to the documented
+     * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} based on
+     * specific device level and capabilities.
+     * Clients can use the array as a quick reference to find an appropriate camera stream
+     * combination.
+     * As per documentation, the stream combinations with given PREVIEW, RECORD and
+     * MAXIMUM resolutions and anything smaller from the list given by
+     * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } are
+     * guaranteed to work.
+     * The mandatory stream combination array will be {@code null} in case the device is a
+     * physical camera not independently exposed in
+     * {@link android.hardware.camera2.CameraManager#getCameraIdList } or is not backward
+     * compatible.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     */
+    @PublicKey
+    @SyntheticKey
+    public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS =
+            new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class);
+
+    /**
      * <p>The area of the image sensor which corresponds to active pixels after any geometric
      * distortion correction has been applied.</p>
      * <p>This is the rectangle representing the size of the active region of the sensor (i.e.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 448591f..9c213f2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -435,9 +435,13 @@
      * </table><br>
      * </p>
      *
+     * <p>Clients can access the above mandatory stream combination tables via
+     * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+     *
      * <p>Since the capabilities of camera devices vary greatly, a given camera device may support
      * target combinations with sizes outside of these guarantees, but this can only be tested for
-     * by attempting to create a session with such targets.</p>
+     * by calling {@link #isSessionConfigurationSupported} or attempting to create a session with
+     * such targets.</p>
      *
      * @param outputs The new set of Surfaces that should be made available as
      *                targets for captured image data.
@@ -619,6 +623,9 @@
      * </table><br>
      * </p>
      *
+     * <p>Clients can access the above mandatory stream combination tables via
+     * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+     *
      * @param inputConfig The configuration for the input {@link Surface}
      * @param outputs The new set of Surfaces that should be made available as
      *                targets for captured image data.
@@ -980,6 +987,12 @@
      * It must not impact normal camera behavior in any way and must complete significantly
      * faster than creating a regular or constrained capture session.</p>
      *
+     * <p>Although this method is faster than creating a new capture session, it is not intended
+     * to be used for exploring the entire space of supported stream combinations. The available
+     * mandatory stream combinations
+     * {@link android.hardware.camera2.params.MandatoryStreamCombination} are better suited for this
+     * purpose.</p>
+     *
      * <p>Note that session parameters will be ignored and calls to
      * {@link SessionConfiguration#setSessionParameters} are not required.</p>
      *
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 44d7364..536c2e1 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -40,6 +40,9 @@
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Size;
+import android.view.Display;
+import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -231,6 +234,30 @@
         CameraManagerGlobal.get().unregisterTorchCallback(callback);
     }
 
+    private Size getDisplaySize() {
+        Size ret = new Size(0, 0);
+
+        try {
+            WindowManager windowManager =
+                    (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+            Display display = windowManager.getDefaultDisplay();
+
+            int width = display.getWidth();
+            int height = display.getHeight();
+
+            if (height > width) {
+                height = width;
+                width = display.getHeight();
+            }
+
+            ret = new Size(width, height);
+        } catch (Exception e) {
+            Log.e(TAG, "getDisplaySize Failed. " + e.toString());
+        }
+
+        return ret;
+    }
+
     /**
      * <p>Query the capabilities of a camera device. These capabilities are
      * immutable for a given camera.</p>
@@ -269,6 +296,8 @@
                         "Camera service is currently unavailable");
             }
             try {
+                Size displaySize = getDisplaySize();
+
                 // First check isHiddenPhysicalCamera to avoid supportsCamera2ApiLocked throwing
                 // exception in case cameraId is a hidden physical camera.
                 if (!isHiddenPhysicalCamera(cameraId) && !supportsCamera2ApiLocked(cameraId)) {
@@ -280,10 +309,19 @@
 
                     CameraInfo info = cameraService.getCameraInfo(id);
 
-                    characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info);
+                    characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info,
+                            id, displaySize);
                 } else {
                     // Normal path: Get the camera characteristics directly from the camera service
                     CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
+                    if (!isHiddenPhysicalCamera(cameraId)) {
+                        try {
+                            info.setCameraId(Integer.parseInt(cameraId));
+                        } catch (NumberFormatException e) {
+                            Log.e(TAG, "Failed to parse camera Id " + cameraId + " to integer");
+                        }
+                    }
+                    info.setDisplaySize(displaySize);
 
                     characteristics = new CameraCharacteristics(info);
                 }
@@ -363,7 +401,8 @@
                     }
 
                     Log.i(TAG, "Using legacy camera HAL.");
-                    cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                    cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id,
+                            getDisplaySize());
                 }
             } catch (ServiceSpecificException e) {
                 if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index f81bd13..c527ab4 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -51,6 +51,8 @@
 import android.hardware.camera2.params.Face;
 import android.hardware.camera2.params.HighSpeedVideoConfiguration;
 import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MandatoryStreamCombination;
+import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
 import android.hardware.camera2.params.OisSample;
 import android.hardware.camera2.params.RecommendedStreamConfiguration;
 import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
@@ -75,6 +77,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Implementation of camera metadata marshal/unmarshal across Binder to
@@ -577,6 +580,15 @@
                     }
                 });
         sGetCommandMap.put(
+                CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getNativeKey(),
+                        new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getMandatoryStreamCombinations();
+                    }
+                });
+        sGetCommandMap.put(
                 CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() {
                     @Override
                     @SuppressWarnings("unchecked")
@@ -1161,6 +1173,26 @@
         return ret;
     }
 
+    private MandatoryStreamCombination[] getMandatoryStreamCombinations() {
+        int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+        ArrayList<Integer> caps = new ArrayList<Integer>();
+        caps.ensureCapacity(capabilities.length);
+        for (int c : capabilities) {
+            caps.add(new Integer(c));
+        }
+        int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+        MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder(
+                mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap());
+        List<MandatoryStreamCombination> combs = build.getAvailableMandatoryStreamCombinations();
+        if ((combs != null) && (!combs.isEmpty())) {
+            MandatoryStreamCombination[] combArray = new MandatoryStreamCombination[combs.size()];
+            combArray = combs.toArray(combArray);
+            return combArray;
+        }
+
+        return null;
+    }
+
     private StreamConfigurationMap getStreamConfigurationMap() {
         StreamConfiguration[] configurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
@@ -1433,6 +1465,31 @@
         return true;
     }
 
+    private int mCameraId = -1;
+    private Size mDisplaySize = new Size(0, 0);
+
+    /**
+     * Set the current camera Id.
+     *
+     * @param cameraId Current camera id.
+     *
+     * @hide
+     */
+    public void setCameraId(int cameraId) {
+        mCameraId = cameraId;
+    }
+
+    /**
+     * Set the current display size.
+     *
+     * @param displaySize The current display size.
+     *
+     * @hide
+     */
+    public void setDisplaySize(Size displaySize) {
+        mDisplaySize = displaySize;
+    }
+
     @UnsupportedAppUsage
     private long mMetadataPtr; // native CameraMetadata*
 
@@ -1476,6 +1533,8 @@
      */
     public void swap(CameraMetadataNative other) {
         nativeSwap(other);
+        mCameraId = other.mCameraId;
+        mDisplaySize = other.mDisplaySize;
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 123eb8e..3e130c5 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -39,6 +39,7 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
+import android.util.Size;
 import android.util.SparseArray;
 import android.view.Surface;
 
@@ -356,7 +357,7 @@
     }
 
     public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
-                                                         int cameraId) {
+                                                         int cameraId, Size displaySize) {
         if (DEBUG) {
             Log.d(TAG, "Opening shim Camera device");
         }
@@ -393,7 +394,8 @@
         }
 
         CameraCharacteristics characteristics =
-                LegacyMetadataMapper.createCharacteristics(legacyParameters, info);
+                LegacyMetadataMapper.createCharacteristics(legacyParameters, info, cameraId,
+                        displaySize);
         LegacyCameraDevice device = new LegacyCameraDevice(
                 cameraId, legacyCamera, characteristics, threadCallbacks);
         return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
@@ -482,8 +484,38 @@
 
     @Override
     public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) {
-        // TODO: Add support for this in legacy mode
-        throw new UnsupportedOperationException("Session configuration query not supported!");
+        if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) {
+            Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " +
+                    " regular. Legacy devices support only regular session types!");
+            return false;
+        }
+
+        if (sessionConfig.getInputConfiguration() != null) {
+            Log.e(TAG, "Input configuration present, legacy devices do not support this feature!");
+            return false;
+        }
+
+        List<OutputConfiguration> outputConfigs = sessionConfig.getOutputConfigurations();
+        if (outputConfigs.isEmpty()) {
+            Log.e(TAG, "Empty output configuration list!");
+            return false;
+        }
+
+        SparseArray<Surface> surfaces = new SparseArray<Surface>(outputConfigs.size());
+        int idx = 0;
+        for (OutputConfiguration outputConfig : outputConfigs) {
+            List<Surface> surfaceList = outputConfig.getSurfaces();
+            if (surfaceList.isEmpty() || (surfaceList.size() > 1)) {
+                Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!");
+                return false;
+            }
+
+            surfaces.put(idx++, outputConfig.getSurface());
+        }
+
+        int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true);
+
+        return ret == LegacyExceptionUtils.NO_ERROR;
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 71a361b..aff09f2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -346,6 +346,25 @@
      *          on success.
      */
     public int configureOutputs(SparseArray<Surface> outputs) {
+        return configureOutputs(outputs, /*validateSurfacesOnly*/false);
+    }
+
+    /**
+     * Configure the device with a set of output surfaces.
+     *
+     * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
+     *
+     * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
+     *
+     * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
+     *          list; it must not be modified by the caller once it's passed in.
+     * @param validateSurfacesOnly If set it will only check whether the outputs are supported
+     *                             and avoid any device configuration.
+     * @return an error code for this binder operation, or {@link NO_ERROR}
+     *          on success.
+     * @hide
+     */
+    public int configureOutputs(SparseArray<Surface> outputs, boolean validateSurfacesOnly) {
         List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>();
         if (outputs != null) {
             int count = outputs.size();
@@ -397,7 +416,9 @@
                         sizedSurfaces.add(new Pair<>(output, s));
                     }
                     // Lock down the size before configuration
-                    setSurfaceDimens(output, s.getWidth(), s.getHeight());
+                    if (!validateSurfacesOnly) {
+                        setSurfaceDimens(output, s.getWidth(), s.getHeight());
+                    }
                 } catch (BufferQueueAbandonedException e) {
                     Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
                     return BAD_VALUE;
@@ -406,6 +427,10 @@
             }
         }
 
+        if (validateSurfacesOnly) {
+            return LegacyExceptionUtils.NO_ERROR;
+        }
+
         boolean success = false;
         if (mDeviceState.setConfiguring()) {
             mRequestThreadManager.configure(sizedSurfaces);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 8822f71..6953a5b 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -111,13 +111,15 @@
      *
      * @param parameters A non-{@code null} parameters set
      * @param info Camera info with camera facing direction and angle of orientation
+     * @param cameraId Current camera Id
+     * @param displaySize Device display size
      *
      * @return static camera characteristics for a camera device
      *
      * @throws NullPointerException if any of the args were {@code null}
      */
     public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
-            CameraInfo info) {
+            CameraInfo info, int cameraId, Size displaySize) {
         checkNotNull(parameters, "parameters must not be null");
         checkNotNull(info, "info must not be null");
 
@@ -125,7 +127,7 @@
         android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
         outerInfo.info = info;
 
-        return createCharacteristics(paramStr, outerInfo);
+        return createCharacteristics(paramStr, outerInfo, cameraId, displaySize);
     }
 
     /**
@@ -134,12 +136,14 @@
      *
      * @param parameters A string parseable by {@link Camera.Parameters#unflatten}
      * @param info Camera info with camera facing direction and angle of orientation
+     * @param cameraId Current camera id
+     * @param displaySize Device display size
      * @return static camera characteristics for a camera device
      *
      * @throws NullPointerException if any of the args were {@code null}
      */
     public static CameraCharacteristics createCharacteristics(String parameters,
-            android.hardware.CameraInfo info) {
+            android.hardware.CameraInfo info, int cameraId, Size displaySize) {
         checkNotNull(parameters, "parameters must not be null");
         checkNotNull(info, "info must not be null");
         checkNotNull(info.info, "info.info must not be null");
@@ -159,6 +163,9 @@
             Log.v(TAG, "--------------------------------------------------- (end)");
         }
 
+        m.setCameraId(cameraId);
+        m.setDisplaySize(displaySize);
+
         return new CameraCharacteristics(m);
     }
 
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
new file mode 100644
index 0000000..b8e2d7a
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -0,0 +1,1219 @@
+/*
+ * 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.hardware.camera2.params;
+
+import static com.android.internal.util.Preconditions.*;
+import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.graphics.PixelFormat;
+import android.media.CamcorderProfile;
+import android.util.Size;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Immutable class to store the available mandatory stream combination.
+ *
+ * <p>The individual stream combinations are generated according to the guidelines
+ * at {@link CameraDevice#createCaptureSession}.</p>
+ *
+ * <p>The list of stream combinations is available by invoking
+ * {@link CameraCharacteristics#get} and passing key
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS}.</p>
+ */
+public final class MandatoryStreamCombination {
+    private static final String TAG = "MandatoryStreamCombination";
+    /**
+     * Immutable class to store available mandatory stream information.
+     */
+    public static final class MandatoryStreamInformation {
+        private final int mFormat;
+        private final ArrayList<Size> mAvailableSizes = new ArrayList<Size> ();
+        private final boolean mIsInput;
+
+        /**
+         * Create a new {@link MandatoryStreamInformation}.
+         *
+           @param availableSizes List of possible stream sizes.
+         * @param format Image format.
+         *
+         * @throws IllegalArgumentException
+         *              if sizes is empty or if the format was not user-defined in
+         *              ImageFormat/PixelFormat.
+         * @hide
+         */
+        public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format) {
+            this(availableSizes, format, /*isInput*/false);
+        }
+
+        /**
+         * Create a new {@link MandatoryStreamInformation}.
+         *
+           @param availableSizes List of possible stream sizes.
+         * @param format Image format.
+         * @param isInput Flag indicating whether this stream is input.
+         *
+         * @throws IllegalArgumentException
+         *              if sizes is empty or if the format was not user-defined in
+         *              ImageFormat/PixelFormat.
+         * @hide
+         */
+        public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format,
+                boolean isInput) {
+            if (availableSizes.isEmpty()) {
+                throw new IllegalArgumentException("No available sizes");
+            }
+            mAvailableSizes.addAll(availableSizes);
+            mFormat = checkArgumentFormat(format);
+            mIsInput = isInput;
+        }
+
+        /**
+         * Confirms whether or not this is an input stream.
+         * @return true in case the stream is input, false otherwise.
+         */
+        public boolean isInput() {
+            return mIsInput;
+        }
+
+        /**
+         * Return the list of available sizes for this mandatory stream.
+         *
+         * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest
+         * resolution in the result will be tested and guaranteed to work. If clients want to use
+         * smaller sizes, then the resulting
+         * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
+         * be tested either by calling {@link CameraDevice#createCaptureSession} or
+         * {@link CameraDevice#isSessionConfigurationSupported}.
+         *
+         * @return non-modifiable ascending list of available sizes.
+         */
+        public List<Size> getAvailableSizes() {
+            return Collections.unmodifiableList(mAvailableSizes);
+        }
+
+        /**
+         * Retrieve the mandatory stream {@code format}.
+         *
+         * @return integer format.
+         */
+        public int getFormat() {
+            return mFormat;
+        }
+
+        /**
+         * Check if this {@link MandatoryStreamInformation} is equal to another
+         * {@link MandatoryStreamInformation}.
+         *
+         * <p>Two vectors are only equal if and only if each of the respective elements is
+         * equal.</p>
+         *
+         * @return {@code true} if the objects were equal, {@code false} otherwise
+         */
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof MandatoryStreamInformation) {
+                final MandatoryStreamInformation other = (MandatoryStreamInformation) obj;
+                if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) ||
+                        (mAvailableSizes.size() != other.mAvailableSizes.size())) {
+                    return false;
+                }
+
+                return mAvailableSizes.equals(other.mAvailableSizes);
+            }
+
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int hashCode() {
+            return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput),
+                    mAvailableSizes.hashCode());
+        }
+    }
+
+    private final String mDescription;
+    private final boolean mIsReprocessable;
+    private final ArrayList<MandatoryStreamInformation> mStreamsInformation =
+            new ArrayList<MandatoryStreamInformation>();
+    /**
+     * Create a new {@link MandatoryStreamCombination}.
+     *
+     * @param streamsInformation list of available streams in the stream combination.
+     * @param description Summary of the stream combination use case.
+     * @param isReprocessable Flag whether the mandatory stream combination is reprocessable.
+     *
+     * @throws IllegalArgumentException
+     *              if stream information is empty
+     * @hide
+     */
+    public MandatoryStreamCombination(@NonNull List<MandatoryStreamInformation> streamsInformation,
+            String description, boolean isReprocessable) {
+        if (streamsInformation.isEmpty()) {
+            throw new IllegalArgumentException("Empty stream information");
+        }
+        mStreamsInformation.addAll(streamsInformation);
+        mDescription = description;
+        mIsReprocessable = isReprocessable;
+    }
+
+    /**
+     * Get the mandatory stream combination description.
+     *
+     * @return String with the mandatory combination description.
+     */
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Indicates whether the mandatory stream combination is reprocessable.
+     *
+     * @return {@code true} in case the mandatory stream combination contains an input,
+     *         {@code false} otherwise.
+     */
+    public boolean isReprocessable() {
+        return mIsReprocessable;
+    }
+
+    /**
+     * Get information about each stream in the mandatory combination.
+     *
+     * @return Non-modifiable list of stream information.
+     *
+     */
+    public List<MandatoryStreamInformation> getStreamsInformation() {
+        return Collections.unmodifiableList(mStreamsInformation);
+    }
+
+    /**
+     * Check if this {@link MandatoryStreamCombination} is equal to another
+     * {@link MandatoryStreamCombination}.
+     *
+     * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof MandatoryStreamCombination) {
+            final MandatoryStreamCombination other = (MandatoryStreamCombination) obj;
+            if ((mDescription != other.mDescription) ||
+                    (mIsReprocessable != other.mIsReprocessable) ||
+                    (mStreamsInformation.size() != other.mStreamsInformation.size())) {
+                return false;
+            }
+
+            return mStreamsInformation.equals(other.mStreamsInformation);
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(),
+                mStreamsInformation.hashCode());
+    }
+
+    private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM }
+    private static enum ReprocessType { NONE, PRIVATE, YUV }
+    private static final class StreamTemplate {
+        public int mFormat;
+        public SizeThreshold mSizeThreshold;
+        public boolean mIsInput;
+        public StreamTemplate(int format, SizeThreshold sizeThreshold) {
+            this(format, sizeThreshold, /*isInput*/false);
+        }
+
+        public StreamTemplate(int format, SizeThreshold sizeThreshold, boolean isInput) {
+            mFormat = format;
+            mSizeThreshold = sizeThreshold;
+            mIsInput = isInput;
+        }
+    }
+
+    private static final class StreamCombinationTemplate {
+        public StreamTemplate[] mStreamTemplates;
+        public String mDescription;
+        public ReprocessType mReprocessType;
+
+        public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description) {
+            this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE);
+        }
+
+        public StreamCombinationTemplate(StreamTemplate[] streamTemplates, String description,
+                ReprocessType reprocessType) {
+            mStreamTemplates = streamTemplates;
+            mReprocessType = reprocessType;
+            mDescription = description;
+        }
+    }
+
+    private static StreamCombinationTemplate sLegacyCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
+                "Simple preview, GPU video processing, or no-preview video recording"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "No-viewfinder still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "In-application video/image processing"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "Standard still imaging"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "In-app processing plus still capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
+                "Standard recording"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
+                "Preview plus in-app processing"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "Still capture plus in-app processing")
+    };
+
+    private static StreamCombinationTemplate sLimitedCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)},
+                "High-resolution video recording with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)},
+                "High-resolution in-app video processing with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
+                "Two-input in-app video processing"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
+                "High-resolution recording with video snapshot"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
+                "High-resolution in-app processing with video snapshot"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "Two-input in-app processing with still capture")
+    };
+
+    private static StreamCombinationTemplate sBurstCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution GPU processing with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution in-app processing with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution two-input in-app processsing")
+    };
+
+    private static StreamCombinationTemplate sFullCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution GPU processing with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution in-app processing with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution two-input in-app processsing"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "Video recording with maximum-size video snapshot"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Standard video recording plus maximum-resolution in-app processing"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Preview plus two-input maximum-resolution in-app processing")
+    };
+
+    private static StreamCombinationTemplate sRawCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.RAW_SENSOR,  SizeThreshold.MAXIMUM) },
+                "No-preview DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Standard DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "In-app processing plus DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Video recording with DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Preview with in-app processing and DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Two-input in-app processing plus DNG capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Still capture with simultaneous JPEG and DNG"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "In-app processing with simultaneous JPEG and DNG")
+    };
+
+    private static StreamCombinationTemplate sLevel3Combinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "In-app viewfinder analysis with dynamic selection of output format"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "In-app viewfinder analysis with dynamic selection of output format")
+    };
+
+    private static StreamCombinationTemplate sLimitedPrivateReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "No-viewfinder still image reprocessing",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL(Zero-Shutter-Lag) still imaging",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL still and in-app processing imaging",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL in-app processing with still capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+    };
+
+    private static StreamCombinationTemplate sLimitedYUVReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "No-viewfinder still image reprocessing",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL(Zero-Shutter-Lag) still imaging",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL still and in-app processing imaging",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL in-app processing with still capture",
+                /*reprocessType*/ ReprocessType.YUV),
+    };
+
+    private static StreamCombinationTemplate sFullPrivateReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
+                "High-resolution ZSL in-app video processing with regular preview",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution ZSL in-app processing with regular preview",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
+                "Maximum-resolution two-input ZSL in-app processing",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL still capture and in-app processing",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+    };
+
+    private static StreamCombinationTemplate sFullYUVReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
+                "Maximum-resolution multi-frame image fusion in-app processing with regular "
+                + "preview",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
+                "Maximum-resolution multi-frame image fusion two-input in-app processing",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
+                "High-resolution ZSL in-app video processing with regular preview",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "ZSL still capture and in-app processing",
+                /*reprocessType*/ ReprocessType.YUV),
+    };
+
+    private static StreamCombinationTemplate sRAWPrivateReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing and DNG capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing and preview with DNG capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL two-input in-app processing and DNG capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL still capture and preview with DNG capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+    };
+
+    private static StreamCombinationTemplate sRAWYUVReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing and DNG capture",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing and preview with DNG capture",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL two-input in-app processing and DNG capture",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL still capture and preview with DNG capture",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
+                /*reprocessType*/ ReprocessType.YUV),
+    };
+
+    private static StreamCombinationTemplate sLevel3PrivateReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
+                /*reprocessType*/ ReprocessType.PRIVATE),
+    };
+
+    private static StreamCombinationTemplate sLevel3YUVReprocCombinations[] = {
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
+                "In-app viewfinder analysis with ZSL and RAW",
+                /*reprocessType*/ ReprocessType.YUV),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
+                "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
+                /*reprocessType*/ ReprocessType.YUV),
+    };
+
+    /**
+     * Helper builder class to generate a list of available mandatory stream combinations.
+     * @hide
+     */
+    public static final class Builder {
+        private Size mDisplaySize;
+        private List<Integer> mCapabilities;
+        private int mHwLevel, mCameraId;
+        private StreamConfigurationMap mStreamConfigMap;
+
+        private final Size kPreviewSizeBound = new Size(1920, 1088);
+
+        /**
+         * Helper class to be used to generate the available mandatory stream combinations.
+         *
+         * @param cameraId Current camera id.
+         * @param hwLevel The camera HW level as reported by android.info.supportedHardwareLevel.
+         * @param displaySize The device display size.
+         * @param capabilities The camera device capabilities.
+         * @param sm The camera device stream configuration map.
+         */
+        public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
+                @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) {
+            mCameraId = cameraId;
+            mDisplaySize = displaySize;
+            mCapabilities = capabilities;
+            mStreamConfigMap = sm;
+            mHwLevel = hwLevel;
+        }
+
+        /**
+         * Retrieve a list of all available mandatory stream combinations.
+         *
+         * @return a non-modifiable list of supported mandatory stream combinations or
+         *         null in case device is not backward compatible or the method encounters
+         *         an error.
+         */
+        public List<MandatoryStreamCombination> getAvailableMandatoryStreamCombinations() {
+            if (!isColorOutputSupported()) {
+                Log.v(TAG, "Device is not backward compatible!");
+                return null;
+            }
+
+            if ((mCameraId < 0) && !isExternalCamera()) {
+                Log.i(TAG, "Invalid camera id");
+                return null;
+            }
+
+            ArrayList<StreamCombinationTemplate> availableTemplates =
+                    new ArrayList<StreamCombinationTemplate> ();
+            if (isHardwareLevelAtLeastLegacy()) {
+                availableTemplates.addAll(Arrays.asList(sLegacyCombinations));
+            }
+
+            // External devices are identical to limited devices w.r.t. stream combinations.
+            if (isHardwareLevelAtLeastLimited() || isExternalCamera()) {
+                availableTemplates.addAll(Arrays.asList(sLimitedCombinations));
+
+                if (isPrivateReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sLimitedPrivateReprocCombinations));
+                }
+
+                if (isYUVReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sLimitedYUVReprocCombinations));
+                }
+
+            }
+
+            if (isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+                availableTemplates.addAll(Arrays.asList(sBurstCombinations));
+            }
+
+            if (isHardwareLevelAtLeastFull()) {
+                availableTemplates.addAll(Arrays.asList(sFullCombinations));
+
+                if (isPrivateReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sFullPrivateReprocCombinations));
+                }
+
+                if (isYUVReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sFullYUVReprocCombinations));
+                }
+
+            }
+
+            if (isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+                availableTemplates.addAll(Arrays.asList(sRawCombinations));
+
+                if (isPrivateReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sRAWPrivateReprocCombinations));
+                }
+
+                if (isYUVReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sRAWYUVReprocCombinations));
+                }
+
+            }
+
+            if (isHardwareLevelAtLeastLevel3()) {
+                availableTemplates.addAll(Arrays.asList(sLevel3Combinations));
+
+                if (isPrivateReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sLevel3PrivateReprocCombinations));
+                }
+
+                if (isYUVReprocessingSupported()) {
+                    availableTemplates.addAll(Arrays.asList(sLevel3YUVReprocCombinations));
+                }
+
+            }
+
+            return generateAvailableCombinations(availableTemplates);
+        }
+
+        /**
+         * Helper method to generate the available stream combinations given the
+         * list of available combination templates.
+         *
+         * @param availableTemplates a list of templates supported by the camera device.
+         * @return a non-modifiable list of supported mandatory stream combinations or
+         *         null in case of errors.
+         */
+        private List<MandatoryStreamCombination> generateAvailableCombinations(
+                ArrayList<StreamCombinationTemplate> availableTemplates) {
+            if (availableTemplates.isEmpty()) {
+                Log.e(TAG, "No available stream templates!");
+                return null;
+            }
+
+            HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
+                enumerateAvailableSizes();
+            if (availableSizes == null) {
+                Log.e(TAG, "Available size enumeration failed!");
+                return null;
+            }
+
+            // RAW only uses MAXIMUM size threshold
+            Size[] rawSizes = mStreamConfigMap.getOutputSizes(ImageFormat.RAW_SENSOR);
+            ArrayList<Size> availableRawSizes = new ArrayList<Size>();
+            if (rawSizes != null) {
+                availableRawSizes.ensureCapacity(rawSizes.length);
+                availableRawSizes.addAll(Arrays.asList(rawSizes));
+            }
+
+            Size maxPrivateInputSize = new Size(0, 0);
+            if (isPrivateReprocessingSupported()) {
+                maxPrivateInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
+                            ImageFormat.PRIVATE));
+            }
+
+            Size maxYUVInputSize = new Size(0, 0);
+            if (isYUVReprocessingSupported()) {
+                maxYUVInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
+                            ImageFormat.YUV_420_888));
+            }
+
+            // Generate the available mandatory stream combinations given the supported templates
+            // and size ranges.
+            ArrayList<MandatoryStreamCombination> availableStreamCombinations =
+                    new ArrayList<MandatoryStreamCombination>();
+            availableStreamCombinations.ensureCapacity(availableTemplates.size());
+            for (StreamCombinationTemplate combTemplate : availableTemplates) {
+                ArrayList<MandatoryStreamInformation> streamsInfo =
+                        new ArrayList<MandatoryStreamInformation>();
+                streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
+                boolean isReprocessable = combTemplate.mReprocessType != ReprocessType.NONE;
+                if (isReprocessable) {
+                    // The first and second streams in a reprocessable combination have the
+                    // same size and format. The first is the input and the second is the output
+                    // used for generating the subsequent input buffers.
+                    ArrayList<Size> inputSize = new ArrayList<Size>();
+                    int format;
+                    if (combTemplate.mReprocessType == ReprocessType.PRIVATE) {
+                        inputSize.add(maxPrivateInputSize);
+                        format = ImageFormat.PRIVATE;
+                    } else {
+                        inputSize.add(maxYUVInputSize);
+                        format = ImageFormat.YUV_420_888;
+                    }
+
+                    streamsInfo.add(new MandatoryStreamInformation(inputSize, format,
+                                /*isInput*/true));
+                    streamsInfo.add(new MandatoryStreamInformation(inputSize, format));
+                }
+
+                for (StreamTemplate template : combTemplate.mStreamTemplates) {
+                    List<Size> sizes = null;
+                    if (template.mFormat == ImageFormat.RAW_SENSOR) {
+                        sizes = availableRawSizes;
+                    } else {
+                        Pair<SizeThreshold, Integer> pair;
+                        pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold,
+                                new Integer(template.mFormat));
+                        sizes = availableSizes.get(pair);
+                    }
+
+                    MandatoryStreamInformation streamInfo;
+                    try {
+                        streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "No available sizes found for format: " + template.mFormat +
+                                " size threshold: " + template.mSizeThreshold + " combination: " +
+                                combTemplate.mDescription);
+                        return null;
+                    }
+
+                    streamsInfo.add(streamInfo);
+                }
+
+                MandatoryStreamCombination streamCombination;
+                try {
+                    streamCombination = new MandatoryStreamCombination(streamsInfo,
+                            combTemplate.mDescription, isReprocessable);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "No stream information for mandatory combination: "
+                            + combTemplate.mDescription);
+                    return null;
+                }
+
+                availableStreamCombinations.add(streamCombination);
+            }
+
+            return Collections.unmodifiableList(availableStreamCombinations);
+        }
+
+        /**
+         * Helper method to enumerate all available sizes according to size threshold and format.
+         */
+        private HashMap<Pair<SizeThreshold, Integer>, List<Size>> enumerateAvailableSizes() {
+            final int[] formats = {
+                ImageFormat.PRIVATE,
+                ImageFormat.YUV_420_888,
+                ImageFormat.JPEG
+            };
+            Size recordingMaxSize = new Size(0, 0);
+            Size previewMaxSize = new Size(0, 0);
+            Size vgaSize = new Size(640, 480);
+            if (isExternalCamera()) {
+                recordingMaxSize = getMaxExternalRecordingSize();
+            } else {
+                recordingMaxSize = getMaxRecordingSize();
+            }
+            if (recordingMaxSize == null) {
+                Log.e(TAG, "Failed to find maximum recording size!");
+                return null;
+            }
+
+            HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>();
+            for (int format : formats) {
+                Integer intFormat = new Integer(format);
+                allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format));
+            }
+
+            List<Size> previewSizes = getSizesWithinBound(
+                    allSizes.get(new Integer(ImageFormat.PRIVATE)), kPreviewSizeBound);
+            if ((previewSizes == null) || (previewSizes.isEmpty())) {
+                Log.e(TAG, "No preview sizes within preview size bound!");
+                return null;
+            }
+            List<Size> orderedPreviewSizes = getAscendingOrderSizes(previewSizes,
+                    /*ascending*/false);
+            previewMaxSize = getMaxPreviewSize(orderedPreviewSizes);
+
+            HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
+                    new HashMap<Pair<SizeThreshold, Integer>, List<Size>>();
+
+            for (int format : formats) {
+                Integer intFormat = new Integer(format);
+                Size[] sizes = allSizes.get(intFormat);
+                Pair<SizeThreshold, Integer> pair = new Pair<SizeThreshold, Integer>(
+                        SizeThreshold.VGA, intFormat);
+                availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize));
+
+                pair = new Pair<SizeThreshold, Integer>(SizeThreshold.PREVIEW, intFormat);
+                availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize));
+
+                pair = new Pair<SizeThreshold, Integer>(SizeThreshold.RECORD, intFormat);
+                availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize));
+
+                pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat);
+                availableSizes.put(pair, Arrays.asList(sizes));
+            }
+
+            return availableSizes;
+        }
+
+        /**
+         * Compile a list of sizes smaller than or equal to given bound.
+         * Return an empty list if there is no size smaller than or equal to the bound.
+         */
+        private static List<Size> getSizesWithinBound(Size[] sizes, Size bound) {
+            if (sizes == null || sizes.length == 0) {
+                Log.e(TAG, "Empty or invalid size array!");
+                return null;
+            }
+
+            ArrayList<Size> ret = new ArrayList<Size>();
+            for (Size size : sizes) {
+                if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
+                    ret.add(size);
+                }
+            }
+
+            return ret;
+        }
+
+        /**
+         * Get the largest size by area.
+         *
+         * @param sizes an array of sizes, must have at least 1 element
+         *
+         * @return Largest Size
+         *
+         * @throws IllegalArgumentException if sizes was null or had 0 elements
+         */
+        public static Size getMaxSize(Size... sizes) {
+            if (sizes == null || sizes.length == 0) {
+                throw new IllegalArgumentException("sizes was empty");
+            }
+
+            Size sz = sizes[0];
+            for (Size size : sizes) {
+                if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
+                    sz = size;
+                }
+            }
+
+            return sz;
+        }
+
+        /**
+         * Whether or not the hardware level reported by android.info.supportedHardwareLevel is
+         * at least the desired one (but could be higher)
+         */
+        private boolean isHardwareLevelAtLeast(int level) {
+            final int[] sortedHwLevels = {
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
+            };
+            if (level == mHwLevel) {
+                return true;
+            }
+
+            for (int sortedlevel : sortedHwLevels) {
+                if (sortedlevel == level) {
+                    return true;
+                } else if (sortedlevel == mHwLevel) {
+                    return false;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Whether or not the camera is an external camera.
+         *
+         * @return {@code true} if the device is external, {@code false} otherwise.
+         */
+        private boolean isExternalCamera() {
+            return mHwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
+        }
+
+        /**
+         * Whether or not the hardware level is at least legacy.
+         *
+         * @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise.
+         */
+        private boolean isHardwareLevelAtLeastLegacy() {
+            return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
+        }
+
+        /**
+         * Whether or not the hardware level is at least limited.
+         *
+         * @return {@code true} if the device is {@code LIMITED} or {@code FULL},
+         *         {@code false} otherwise (i.e. LEGACY).
+         */
+        private boolean isHardwareLevelAtLeastLimited() {
+            return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
+        }
+
+        /**
+         * Whether or not the hardware level is at least full.
+         *
+         * @return {@code true} if the device is {@code FULL}, {@code false} otherwise.
+         */
+        private boolean isHardwareLevelAtLeastFull() {
+            return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
+        }
+
+        /**
+         * Whether or not the hardware level is at least Level 3.
+         *
+         * @return {@code true} if the device is {@code LEVEL3}, {@code false} otherwise.
+         */
+        private boolean isHardwareLevelAtLeastLevel3() {
+            return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3);
+        }
+
+        /**
+         * Determine whether the current device supports a capability or not.
+         *
+         * @param capability (non-negative)
+         *
+         * @return {@code true} if the capability is supported, {@code false} otherwise.
+         *
+         */
+        private boolean isCapabilitySupported(int capability) {
+            return mCapabilities.contains(capability);
+        }
+
+        /**
+         * Check whether the current device is backward compatible.
+         */
+        private boolean isColorOutputSupported() {
+            return isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
+        }
+
+        /**
+         * Check whether the current device supports private reprocessing.
+         */
+        private boolean isPrivateReprocessingSupported() {
+            return isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
+        }
+
+        /**
+         * Check whether the current device supports YUV reprocessing.
+         */
+        private boolean isYUVReprocessingSupported() {
+            return isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
+        }
+
+        /**
+         * Return the maximum supported video size using the camcorder profile information.
+         *
+         * @return Maximum supported video size.
+         */
+        private Size getMaxRecordingSize() {
+            int quality =
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P) ?
+                        CamcorderProfile.QUALITY_2160P :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P) ?
+                        CamcorderProfile.QUALITY_1080P :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P) ?
+                        CamcorderProfile.QUALITY_720P :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P) ?
+                        CamcorderProfile.QUALITY_480P :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA) ?
+                        CamcorderProfile.QUALITY_QVGA :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_CIF) ?
+                        CamcorderProfile.QUALITY_CIF :
+                    CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QCIF) ?
+                        CamcorderProfile.QUALITY_QCIF :
+                        -1;
+
+            if (quality < 0) {
+                return null;
+            }
+
+            CamcorderProfile maxProfile = CamcorderProfile.get(mCameraId, quality);
+            return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
+        }
+
+        /**
+         * Return the maximum supported video size for external cameras using data from
+         * the stream configuration map.
+         *
+         * @return Maximum supported video size.
+         */
+        private Size getMaxExternalRecordingSize() {
+            final Size FULLHD = new Size(1920, 1080);
+
+            Size[] videoSizeArr = mStreamConfigMap.getOutputSizes(
+                    android.media.MediaRecorder.class);
+            List<Size> sizes = new ArrayList<Size>();
+            for (Size sz: videoSizeArr) {
+                if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
+                    sizes.add(sz);
+                }
+            }
+            List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false);
+            for (Size sz : videoSizes) {
+                long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration(
+                        android.media.MediaRecorder.class, sz);
+                // Give some margin for rounding error
+                if (minFrameDuration > (1e9 / 30.1)) {
+                    Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz);
+                    return sz;
+                }
+            }
+            Log.w(TAG, "Camera " + mCameraId + " does not support any 30fps video output");
+            return FULLHD; // doesn't matter what size is returned here
+        }
+
+        private Size getMaxPreviewSize(List<Size> orderedPreviewSizes) {
+            if (orderedPreviewSizes != null) {
+                for (Size size : orderedPreviewSizes) {
+                    if ((mDisplaySize.getWidth() >= size.getWidth()) &&
+                            (mDisplaySize.getWidth() >= size.getHeight())) {
+                        return size;
+                    }
+                }
+            }
+
+            Log.w(TAG,"Camera " + mCameraId + " maximum preview size search failed with "
+                    + "display size " + mDisplaySize);
+            return kPreviewSizeBound;
+        }
+
+        /**
+         * Size comparison method used by size comparators.
+         */
+        private static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
+            long left = widthA * (long) heightA;
+            long right = widthB * (long) heightB;
+            if (left == right) {
+                left = widthA;
+                right = widthB;
+            }
+            return (left < right) ? -1 : (left > right ? 1 : 0);
+        }
+
+        /**
+         * Size comparator that compares the number of pixels it covers.
+         *
+         * <p>If two the areas of two sizes are same, compare the widths.</p>
+         */
+        public static class SizeComparator implements Comparator<Size> {
+            @Override
+            public int compare(Size lhs, Size rhs) {
+                return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(),
+                        rhs.getHeight());
+            }
+        }
+
+        /**
+         * Get a sorted list of sizes from a given size list.
+         *
+         * <p>
+         * The size is compare by area it covers, if the areas are same, then
+         * compare the widths.
+         * </p>
+         *
+         * @param sizeList The input size list to be sorted
+         * @param ascending True if the order is ascending, otherwise descending order
+         * @return The ordered list of sizes
+         */
+        private static List<Size> getAscendingOrderSizes(final List<Size> sizeList,
+                boolean ascending) {
+            if (sizeList == null) {
+                return null;
+            }
+
+            Comparator<Size> comparator = new SizeComparator();
+            List<Size> sortedSizes = new ArrayList<Size>();
+            sortedSizes.addAll(sizeList);
+            Collections.sort(sortedSizes, comparator);
+            if (!ascending) {
+                Collections.reverse(sortedSizes);
+            }
+
+            return sortedSizes;
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 51d33b2..3f25113 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -16,8 +16,6 @@
 
 package android.inputmethodservice;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.annotation.UnsupportedAppUsage;
 import android.annotation.XmlRes;
 import android.content.Context;
@@ -27,10 +25,12 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
 import android.util.Xml;
-import android.util.DisplayMetrics;
+
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -59,7 +59,12 @@
  * @attr ref android.R.styleable#Keyboard_keyHeight
  * @attr ref android.R.styleable#Keyboard_horizontalGap
  * @attr ref android.R.styleable#Keyboard_verticalGap
+ * @deprecated This class is deprecated because this is just a convenient UI widget class that
+ *             application developers can re-implement on top of existing public APIs.  If you have
+ *             already depended on this class, consider copying the implementation from AOSP into
+ *             your project or re-implementing a similar widget by yourselves
  */
+@Deprecated
 public class Keyboard {
 
     static final String TAG = "Keyboard";
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 9ca8049..45f067b 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -65,7 +65,13 @@
  * @attr ref android.R.styleable#KeyboardView_keyTextColor
  * @attr ref android.R.styleable#KeyboardView_verticalCorrection
  * @attr ref android.R.styleable#KeyboardView_popupLayout
+ *
+ * @deprecated This class is deprecated because this is just a convenient UI widget class that
+ *             application developers can re-implement on top of existing public APIs.  If you have
+ *             already depended on this class, consider copying the implementation from AOSP into
+ *             your project or re-implementing a similar widget by yourselves
  */
+@Deprecated
 public class KeyboardView extends View implements View.OnClickListener {
 
     /**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c437dde..eae1aa5 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2283,7 +2283,8 @@
     static final String[] DATA_CONNECTION_NAMES = {
         "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
         "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte",
-        "ehrpd", "hspap", "gsm", "td_scdma", "iwlan", "lte_ca", "other"
+        "ehrpd", "hspap", "gsm", "td_scdma", "iwlan", "lte_ca", "nr",
+        "other"
     };
 
     public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1;
@@ -4730,7 +4731,7 @@
             sb.append("\n       ");
             sb.append(prefix);
             didOne = true;
-            sb.append(DATA_CONNECTION_NAMES[i]);
+            sb.append(i < DATA_CONNECTION_NAMES.length ? DATA_CONNECTION_NAMES[i] : "ERROR");
             sb.append(" ");
             formatTimeMs(sb, time/1000);
             sb.append("(");
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 567efa7..e84a518 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -23,6 +23,7 @@
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
 import static android.system.OsConstants.F_OK;
+import static android.system.OsConstants.O_ACCMODE;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
@@ -49,6 +50,7 @@
 import android.webkit.MimeTypeMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 
 import libcore.io.IoUtils;
@@ -110,8 +112,6 @@
         public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
     }
 
-    private static final File[] EMPTY = new File[0];
-
     // non-final so it can be toggled by Robolectric's ShadowFileUtils
     private static boolean sEnableCopyOptimizations = true;
 
@@ -1164,35 +1164,20 @@
 
     /** {@hide} */
     public static @NonNull String[] listOrEmpty(@Nullable File dir) {
-        if (dir == null) return EmptyArray.STRING;
-        final String[] res = dir.list();
-        if (res != null) {
-            return res;
-        } else {
-            return EmptyArray.STRING;
-        }
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
+                : EmptyArray.STRING;
     }
 
     /** {@hide} */
     public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
-        if (dir == null) return EMPTY;
-        final File[] res = dir.listFiles();
-        if (res != null) {
-            return res;
-        } else {
-            return EMPTY;
-        }
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+                : ArrayUtils.EMPTY_FILE;
     }
 
     /** {@hide} */
     public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
-        if (dir == null) return EMPTY;
-        final File[] res = dir.listFiles(filter);
-        if (res != null) {
-            return res;
-        } else {
-            return EMPTY;
-        }
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
+                : ArrayUtils.EMPTY_FILE;
     }
 
     /** {@hide} */
@@ -1275,11 +1260,11 @@
 
         int res = 0;
         if (mode.startsWith("rw")) {
-            res |= O_RDWR | O_CREAT;
+            res = O_RDWR | O_CREAT;
         } else if (mode.startsWith("w")) {
-            res |= O_WRONLY | O_CREAT;
+            res = O_WRONLY | O_CREAT;
         } else if (mode.startsWith("r")) {
-            res |= O_RDONLY;
+            res = O_RDONLY;
         } else {
             throw new IllegalArgumentException("Bad mode: " + mode);
         }
@@ -1295,12 +1280,12 @@
     /** {@hide} */
     public static String translateModePosixToString(int mode) {
         String res = "";
-        if ((mode & O_RDWR) == O_RDWR) {
-            res += "rw";
-        } else if ((mode & O_WRONLY) == O_WRONLY) {
-            res += "w";
-        } else if ((mode & O_RDONLY) == O_RDONLY) {
-            res += "r";
+        if ((mode & O_ACCMODE) == O_RDWR) {
+            res = "rw";
+        } else if ((mode & O_ACCMODE) == O_WRONLY) {
+            res = "w";
+        } else if ((mode & O_ACCMODE) == O_RDONLY) {
+            res = "r";
         } else {
             throw new IllegalArgumentException("Bad mode: " + mode);
         }
@@ -1316,12 +1301,12 @@
     /** {@hide} */
     public static int translateModePosixToPfd(int mode) {
         int res = 0;
-        if ((mode & O_RDWR) == O_RDWR) {
-            res |= MODE_READ_WRITE;
-        } else if ((mode & O_WRONLY) == O_WRONLY) {
-            res |= MODE_WRITE_ONLY;
-        } else if ((mode & O_RDONLY) == O_RDONLY) {
-            res |= MODE_READ_ONLY;
+        if ((mode & O_ACCMODE) == O_RDWR) {
+            res = MODE_READ_WRITE;
+        } else if ((mode & O_ACCMODE) == O_WRONLY) {
+            res = MODE_WRITE_ONLY;
+        } else if ((mode & O_ACCMODE) == O_RDONLY) {
+            res = MODE_READ_ONLY;
         } else {
             throw new IllegalArgumentException("Bad mode: " + mode);
         }
@@ -1341,11 +1326,11 @@
     public static int translateModePfdToPosix(int mode) {
         int res = 0;
         if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
-            res |= O_RDWR;
+            res = O_RDWR;
         } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
-            res |= O_WRONLY;
+            res = O_WRONLY;
         } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
-            res |= O_RDONLY;
+            res = O_RDONLY;
         } else {
             throw new IllegalArgumentException("Bad mode: " + mode);
         }
@@ -1444,4 +1429,3 @@
         }
     }
 }
-
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index bdc776d..cbcf600 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -25,9 +25,14 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.ArrayList;
-import java.util.HashMap;
+import libcore.util.HexEncoding;
 
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 
 /**
  * Gives access to the system properties store.  The system properties
@@ -232,6 +237,27 @@
         native_report_sysprop_change();
     }
 
+    /**
+     * Return a {@code SHA-1} digest of the given keys and their values as a
+     * hex-encoded string. The ordering of the incoming keys doesn't change the
+     * digest result.
+     *
+     * @hide
+     */
+    public static @NonNull String digestOf(@NonNull String... keys) {
+        Arrays.sort(keys);
+        try {
+            final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            for (String key : keys) {
+                final String item = key + "=" + get(key) + "\n";
+                digest.update(item.getBytes(StandardCharsets.UTF_8));
+            }
+            return HexEncoding.encodeToString(digest.digest()).toLowerCase();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private SystemProperties() {
     }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 17ce79b..0a60764 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1515,11 +1515,11 @@
      * background user; the result here does not distinguish between the two.
      *
      * <p>Note prior to Android Nougat MR1 (SDK version <= 24;
-     * {@link android.os.Build.VERSION_CODES#N), this API required a system permission
+     * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
      * in order to check other profile's status.
      * Since Android Nougat MR1 (SDK version >= 25;
-     * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now
-     * it'll accept any {@link UserHandle} within the same profile group as the caller.
+     * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+     * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
      *
      * @param user The user to retrieve the running state for.
      */
@@ -1544,11 +1544,11 @@
      * (but is not yet fully stopped, and still running some code).
      *
      * <p>Note prior to Android Nougat MR1 (SDK version <= 24;
-     * {@link android.os.Build.VERSION_CODES#N), this API required a system permission
+     * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
      * in order to check other profile's status.
      * Since Android Nougat MR1 (SDK version >= 25;
-     * {@link android.os.Build.VERSION_CODES#N_MR1)), the restriction has been relaxed, and now
-     * it'll accept any {@link UserHandle} within the same profile group as the caller.
+     * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+     * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
      *
      * @param user The user to retrieve the running state for.
      */
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d315383..8b1d3c6 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -226,7 +226,9 @@
     /** {@hide} */
     public static final int DEBUG_VIRTUAL_DISK = 1 << 5;
     /** {@hide} */
-    public static final int DEBUG_ISOLATED_STORAGE = 1 << 6;
+    public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6;
+    /** {@hide} */
+    public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7;
 
     /** {@hide} */
     public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 3d93afd..de54a8aa 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -28,12 +28,14 @@
 import android.location.Country;
 import android.location.CountryDetector;
 import android.net.Uri;
+import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.ContactsContract.CommonDataKinds.Callable;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DataUsageFeedback;
+import android.telecom.CallIdentification;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -603,6 +605,69 @@
         public static final String BLOCK_REASON = "block_reason";
 
         /**
+         * The package name of the {@link android.telecom.CallScreeningService} which provided
+         * {@link android.telecom.CallIdentification} for this call.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name";
+
+        /**
+         * The app name of the {@link android.telecom.CallScreeningService} which provided
+         * {@link android.telecom.CallIdentification} for this call.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CALL_ID_APP_NAME = "call_id_app_name";
+
+        /**
+         * The {@link CallIdentification#getName() name} of a call, as provided by the
+         * {@link android.telecom.CallScreeningService}.
+         * <p>
+         * The name is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
+         * {@link #CALL_ID_APP_NAME}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CALL_ID_NAME = "call_id_name";
+
+        /**
+         * The {@link CallIdentification#getDescription() description} of a call, as provided by the
+         * {@link android.telecom.CallScreeningService}.
+         * <p>
+         * The description is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
+         * {@link #CALL_ID_APP_NAME}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CALL_ID_DESCRIPTION = "call_id_description";
+
+        /**
+         * The {@link CallIdentification#getDetails() details} of a call, as provided by the
+         * {@link android.telecom.CallScreeningService}.
+         * <p>
+         * The details field is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
+         * {@link #CALL_ID_APP_NAME}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CALL_ID_DETAILS = "call_id_details";
+
+        /**
+         * The {@link CallIdentification#getNuisanceConfidence() nuisance confidence} of a call, as
+         * provided by the {@link android.telecom.CallScreeningService}.
+         * <p>
+         * Valid values are defined in {@link CallIdentification}, and include:
+         * <ul>
+         *     <li>{@link CallIdentification#CONFIDENCE_NOT_NUISANCE}</li>
+         *     <li>{@link CallIdentification#CONFIDENCE_LIKELY_NOT_NUISANCE}</li>
+         *     <li>{@link CallIdentification#CONFIDENCE_UNKNOWN}</li>
+         *     <li>{@link CallIdentification#CONFIDENCE_LIKELY_NUISANCE}</li>
+         *     <li>{@link CallIdentification#CONFIDENCE_NUISANCE}</li>
+         * </ul>
+         * <p>
+         * The nuisance confidence is provided by the app identified by
+         * {@link #CALL_ID_PACKAGE_NAME} and {@link #CALL_ID_APP_NAME}.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence";
+
+        /**
          * Adds a call to the call log.
          *
          * @param ci the CallerInfo object to get the target contact from.  Can be null
@@ -631,7 +696,8 @@
                 presentation, callType, features, accountHandle, start, duration,
                 dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */,
                 false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */,
-                null /* callScreeningAppName */, null /* callScreeningComponentName */);
+                null /* callScreeningAppName */, null /* callScreeningComponentName */,
+                null /* callIdentification */);
         }
 
 
@@ -671,7 +737,8 @@
                 features, accountHandle, start, duration, dataUsage, addForAllUsers,
                 userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED
                 /* callBlockReason */, null /* callScreeningAppName */,
-                null /* callScreeningComponentName */);
+                null /* callScreeningComponentName */,
+                null /* callIdentification */);
         }
 
         /**
@@ -705,19 +772,32 @@
          * @param callBlockReason The reason why the call is blocked.
          * @param callScreeningAppName The call screening application name which block the call.
          * @param callScreeningComponentName The call screening component name which block the call.
+         * @param callIdPackageName The package name of the
+         *      {@link android.telecom.CallScreeningService} which provided
+         *      {@link CallIdentification}.
+         * @param callIdAppName The app name of the {@link android.telecom.CallScreeningService}
+         *                      which provided {@link CallIdentification}.
+         * @param callIdName The caller name provided by the
+         *      {@link android.telecom.CallScreeningService}.
+         * @param callIdDescription The caller description provided by the
+         *      {@link android.telecom.CallScreeningService}.
+         * @param callIdDetails The caller details provided by the
+         *      {@link android.telecom.CallScreeningService}.
+         * @param callIdCallType The caller type provided by the
+         *      {@link android.telecom.CallScreeningService}.
          *
          * @result The URI of the call log entry belonging to the user that made or received this
          *        call.  This could be of the shadow provider.  Do not return it to non-system apps,
          *        as they don't have permissions.
          * {@hide}
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public static Uri addCall(CallerInfo ci, Context context, String number,
                 String postDialDigits, String viaNumber, int presentation, int callType,
                 int features, PhoneAccountHandle accountHandle, long start, int duration,
                 Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
                 boolean isRead, int callBlockReason, String callScreeningAppName,
-                String callScreeningComponentName) {
+                String callScreeningComponentName, CallIdentification callIdentification) {
             if (VERBOSE_LOG) {
                 Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
                         number, userToBeInsertedTo, addForAllUsers));
@@ -799,6 +879,22 @@
             values.put(CALL_SCREENING_APP_NAME, callScreeningAppName);
             values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
 
+            if (callIdentification != null) {
+                values.put(CALL_ID_PACKAGE_NAME, callIdentification.getCallScreeningPackageName());
+                values.put(CALL_ID_APP_NAME, callIdentification.getCallScreeningAppName());
+                values.put(CALL_ID_NAME, callIdentification.getName());
+                values.put(CALL_ID_DESCRIPTION, callIdentification.getDescription());
+                values.put(CALL_ID_DETAILS, callIdentification.getDetails());
+                values.put(CALL_ID_NUISANCE_CONFIDENCE, callIdentification.getNuisanceConfidence());
+            } else {
+                values.putNull(CALL_ID_PACKAGE_NAME);
+                values.putNull(CALL_ID_APP_NAME);
+                values.putNull(CALL_ID_NAME);
+                values.putNull(CALL_ID_DESCRIPTION);
+                values.putNull(CALL_ID_DETAILS);
+                values.putNull(CALL_ID_NUISANCE_CONFIDENCE);
+            }
+
             if ((ci != null) && (ci.contactIdOrZero > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
new file mode 100644
index 0000000..4e207ed
--- /dev/null
+++ b/core/java/android/provider/DeviceConfig.java
@@ -0,0 +1,275 @@
+/*
+ * 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.provider;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings.ResetMode;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Device level configuration parameters which can be tuned by a separate configuration service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceConfig {
+    /**
+     * The content:// style URL for the config table.
+     *
+     * @hide
+     */
+    public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
+
+    private static final Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
+            new HashMap<>();
+    @GuardedBy("sLock")
+    private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
+
+    // Should never be invoked
+    private DeviceConfig() {
+    }
+
+    /**
+     * Look up the value of a property for a particular namespace.
+     *
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @return the corresponding value, or null if not present.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static String getProperty(String namespace, String name) {
+        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+        String compositeName = createCompositeName(namespace, name);
+        return Settings.Config.getString(contentResolver, compositeName);
+    }
+
+    /**
+     * Create a new property with the the provided name and value in the provided namespace, or
+     * update the value of such a property if it already exists. The same name can exist in multiple
+     * namespaces and might have different values in any or all namespaces.
+     * <p>
+     * The method takes an argument indicating whether to make the value the default for this
+     * property.
+     * <p>
+     * All properties stored for a particular scope can be reverted to their default values
+     * by passing the namespace to {@link #resetToDefaults(int, String)}.
+     *
+     * @param namespace The namespace containing the property to create or update.
+     * @param name The name of the property to create or update.
+     * @param value The value to store for the property.
+     * @param makeDefault Whether to make the new value the default one.
+     * @return True if the value was set, false if the storage implementation throws errors.
+     * @see #resetToDefaults(int, String).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static boolean setProperty(
+            String namespace, String name, String value, boolean makeDefault) {
+        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+        String compositeName = createCompositeName(namespace, name);
+        return Settings.Config.putString(contentResolver, compositeName, value, makeDefault);
+    }
+
+    /**
+     * Reset properties to their default values.
+     * <p>
+     * The method accepts an optional namespace parameter. If provided, only properties set within
+     * that namespace will be reset. Otherwise, all properties will be reset.
+     *
+     * @param resetMode The reset mode to use.
+     * @param namespace Optionally, the specific namespace which resets will be limited to.
+     * @see #setProperty(String, String, String, boolean)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
+        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+        Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+    }
+
+    /**
+     * Add a listener for property changes.
+     * <p>
+     * This listener will be called whenever properties in the specified namespace change. Callbacks
+     * will be made on the specified executor. Future calls to this method with the same listener
+     * will replace the old namespace and executor. Remove the listener entirely by calling
+     * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
+     *
+     * @param namespace The namespace containing properties to monitor.
+     * @param executor The executor which will be used to run callbacks.
+     * @param onPropertyChangedListener The listener to add.
+     * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void addOnPropertyChangedListener(
+            @NonNull String namespace,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnPropertyChangedListener onPropertyChangedListener) {
+        synchronized (sLock) {
+            Pair<String, Executor> oldNamespace = sListeners.get(onPropertyChangedListener);
+            if (oldNamespace == null) {
+                // Brand new listener, add it to the list.
+                sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+                incrementNamespace(namespace);
+            } else if (namespace.equals(oldNamespace.first)) {
+                // Listener is already registered for this namespace, update executor just in case.
+                sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+            } else {
+                // Update this listener from an old namespace to the new one.
+                decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+                sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+                incrementNamespace(namespace);
+            }
+        }
+    }
+
+    /**
+     * Remove a listener for property changes. The listener will receive no further notification of
+     * property changes.
+     *
+     * @param onPropertyChangedListener The listener to remove.
+     * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static void removeOnPropertyChangedListener(
+            OnPropertyChangedListener onPropertyChangedListener) {
+        synchronized (sLock) {
+            if (sListeners.containsKey(onPropertyChangedListener)) {
+                decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+                sListeners.remove(onPropertyChangedListener);
+            }
+        }
+    }
+
+    private static String createCompositeName(String namespace, String name) {
+        return namespace + "/" + name;
+    }
+
+    private static Uri createNamespaceUri(String namespace) {
+        return CONTENT_URI.buildUpon().appendPath(namespace).build();
+    }
+
+    /**
+     * Increment the count used to represent the number of listeners subscribed to the given
+     * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
+     * ContentObserver is registered.
+     *
+     * @param namespace The namespace to increment the count for.
+     */
+    @GuardedBy("sLock")
+    private static void incrementNamespace(String namespace) {
+        Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
+        if (namespaceCount != null) {
+            sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second + 1));
+        } else {
+            // This is a new namespace, register a ContentObserver for it.
+            ContentObserver contentObserver = new ContentObserver(null) {
+                @Override
+                public void onChange(boolean selfChange, Uri uri) {
+                    handleChange(uri);
+                }
+            };
+            ActivityThread.currentApplication().getContentResolver()
+                    .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
+            sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
+        }
+    }
+
+    /**
+     * Decrement the count used to represent th enumber of listeners subscribed to the given
+     * namespace. If this is the final decrement call (i.e. decrementing from 1 to 0) for the given
+     * namespace, the ContentObserver that had been tracking it will be removed.
+     *
+     * @param namespace The namespace to decrement the count for.
+     */
+    @GuardedBy("sLock")
+    private static void decrementNamespace(String namespace) {
+        Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
+        if (namespaceCount == null) {
+            // This namespace is not registered and does not need to be decremented
+            return;
+        } else if (namespaceCount.second > 1) {
+            sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
+        } else {
+            // Decrementing a namespace to zero means we no longer need its ContentObserver.
+            ActivityThread.currentApplication().getContentResolver()
+                    .unregisterContentObserver(namespaceCount.first);
+            sNamespaces.remove(namespace);
+        }
+    }
+
+    private static void handleChange(Uri uri) {
+        List<String> pathSegments = uri.getPathSegments();
+        // pathSegments(0) is "config"
+        String namespace = pathSegments.get(1);
+        String name = pathSegments.get(2);
+        String value = getProperty(namespace, name);
+        synchronized (sLock) {
+            for (OnPropertyChangedListener listener : sListeners.keySet()) {
+                if (namespace.equals(sListeners.get(listener).first)) {
+                    sListeners.get(listener).second.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            listener.onPropertyChanged(namespace, name, value);
+                        }
+
+                    });
+                }
+            }
+        }
+    }
+
+    /**
+     * Interface for monitoring to properties.
+     * <p>
+     * Override {@link #onPropertyChanged(String, String, String)} to handle callbacks for changes.
+     */
+    public interface OnPropertyChangedListener {
+        /**
+         * Called when a property has changed.
+         *
+         * @param namespace The namespace containing the property which has changed.
+         * @param name The name of the property which has changed.
+         * @param value The new value of the property which has changed.
+         */
+        void onPropertyChanged(String namespace, String name, String value);
+    }
+}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a8726e9..cd991cc 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1275,7 +1275,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Bitmap getDocumentThumbnail(ContentResolver content, Uri documentUri, Point size,
             CancellationSignal signal) throws FileNotFoundException {
         return getDocumentThumbnail((ContentInterface) content, documentUri, size, signal);
@@ -1307,7 +1307,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri createDocument(ContentResolver content, Uri parentDocumentUri,
             String mimeType, String displayName) throws FileNotFoundException {
         return createDocument((ContentInterface) content, parentDocumentUri, mimeType, displayName);
@@ -1345,7 +1345,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean isChildDocument(ContentResolver content, Uri parentDocumentUri,
             Uri childDocumentUri) throws FileNotFoundException {
         return isChildDocument((ContentInterface) content, parentDocumentUri, childDocumentUri);
@@ -1382,7 +1382,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri renameDocument(ContentResolver content, Uri documentUri,
             String displayName) throws FileNotFoundException {
         return renameDocument((ContentInterface) content, documentUri, displayName);
@@ -1410,7 +1410,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean deleteDocument(ContentResolver content, Uri documentUri)
             throws FileNotFoundException {
         return deleteDocument((ContentInterface) content, documentUri);
@@ -1441,7 +1441,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri copyDocument(ContentResolver content, Uri sourceDocumentUri,
             Uri targetParentDocumentUri) throws FileNotFoundException {
         return copyDocument((ContentInterface) content, sourceDocumentUri, targetParentDocumentUri);
@@ -1474,7 +1474,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri moveDocument(ContentResolver content, Uri sourceDocumentUri,
             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
         return moveDocument((ContentInterface) content, sourceDocumentUri, sourceParentDocumentUri,
@@ -1508,7 +1508,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean removeDocument(ContentResolver content, Uri documentUri,
             Uri parentDocumentUri) throws FileNotFoundException {
         return removeDocument((ContentInterface) content, documentUri, parentDocumentUri);
@@ -1531,7 +1531,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static void ejectRoot(ContentResolver content, Uri rootUri) {
         ejectRoot((ContentInterface) content, rootUri);
     }
@@ -1581,7 +1581,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Bundle getDocumentMetadata(ContentResolver content, Uri documentUri)
             throws FileNotFoundException {
         return getDocumentMetadata((ContentInterface) content, documentUri);
@@ -1618,7 +1618,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Path findDocumentPath(ContentResolver content, Uri treeUri)
             throws FileNotFoundException {
         return findDocumentPath((ContentInterface) content, treeUri);
@@ -1697,7 +1697,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static IntentSender createWebLinkIntent(ContentResolver content, Uri uri,
             Bundle options) throws FileNotFoundException {
         return createWebLinkIntent((ContentInterface) content, uri, options);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 93a5950..299db73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -150,6 +150,23 @@
             "android.settings.LOCATION_SOURCE_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of location controller extra package.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS =
+            "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
+
+    /**
      * Activity Action: Show scanning settings to allow configuration of Wi-Fi
      * and Bluetooth scanning settings.
      * <p>
@@ -7867,6 +7884,24 @@
         public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete";
 
         /**
+         * Control whether Trust Agents are in active unlock or extend unlock mode.
+         * @hide
+         */
+        public static final String TRUST_AGENTS_EXTEND_UNLOCK = "trust_agents_extend_unlock";
+
+        private static final Validator TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR =
+                BOOLEAN_VALIDATOR;
+
+        /**
+         * Control whether the screen locks when trust is lost.
+         * @hide
+         */
+        public static final String LOCK_SCREEN_WHEN_TRUST_LOST = "lock_screen_when_trust_lost";
+
+        private static final Validator LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR =
+                BOOLEAN_VALIDATOR;
+
+        /**
          * Control whether Night display is currently activated.
          * @hide
          */
@@ -8244,6 +8279,38 @@
                 "packages_to_clear_data_before_full_restore";
 
         /**
+         * Indicates the location state should be maintained after sensor privacy is disabled.
+         * @hide
+         */
+        public static final String MAINTAIN_LOCATION_AFTER_SP_DISABLED = "0";
+
+        /**
+         * Indicates location should be reenabled after sensor privacy is disabled.
+         * @hide
+         */
+        public static final String REENABLE_LOCATION_AFTER_SP_DISABLED = "1";
+
+        /**
+         * Indicates the state of airplane mode should be maintained after sensor privacy is
+         * disabled.
+         * @hide
+         */
+        public static final String MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED = "0";
+
+        /**
+         * Indicates airplane mode should be disabled after sensor privacy is disabled.
+         * @hide
+         */
+        public static final String DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED = "1";
+
+        /**
+         * The state of all sensors managed by SensorPrivacyService when sensor privacy is enabled.
+         * @hide
+         */
+        public static final String SENSOR_PRIVACY_SENSOR_STATE =
+                "sensor_privacy_sensor_state";
+
+        /**
          * Setting to determine whether to use the new notification priority handling features.
          * @hide
          */
@@ -8382,6 +8449,8 @@
             ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
             ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
             NOTIFICATION_NEW_INTERRUPTION_MODEL,
+            TRUST_AGENTS_EXTEND_UNLOCK,
+            LOCK_SCREEN_WHEN_TRUST_LOST,
         };
 
         /**
@@ -8543,6 +8612,8 @@
             VALIDATORS.put(USER_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
             VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
+            VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
+            VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
         }
 
         /**
@@ -12893,6 +12964,11 @@
         public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED =
                 "content_capture_service_explicitly_enabled";
 
+        /** {@hide} */
+        public static final String ISOLATED_STORAGE_LOCAL = "isolated_storage_local";
+        /** {@hide} */
+        public static final String ISOLATED_STORAGE_REMOTE = "isolated_storage_remote";
+
         /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
@@ -13879,7 +13955,6 @@
          */
         public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id";
 
-
         /**
          * Whether we've enabled native flags health check on this device. Takes effect on
          * reboot. The value "1" enables native flags health check; otherwise it's disabled.
@@ -13887,7 +13962,6 @@
          */
         public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
                 "native_flags_health_check_enabled";
-
     }
 
     /**
@@ -13898,21 +13972,12 @@
      * @hide
      */
     public static final class Config extends NameValueTable {
-        /**
-         * The content:// style URL for the config table.
-         *
-         * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a
-         *     System API.
-         */
-        private static final Uri CONTENT_URI =
-                Uri.parse("content://" + AUTHORITY + "/config");
-
         private static final ContentProviderHolder sProviderHolder =
-                new ContentProviderHolder(CONTENT_URI);
+                new ContentProviderHolder(DeviceConfig.CONTENT_URI);
 
         // Populated lazily, guarded by class object:
         private static final NameValueCache sNameValueCache = new NameValueCache(
-                CONTENT_URI,
+                DeviceConfig.CONTENT_URI,
                 CALL_METHOD_GET_CONFIG,
                 CALL_METHOD_PUT_CONFIG,
                 sProviderHolder);
@@ -13986,7 +14051,7 @@
                 cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
                         CALL_METHOD_RESET_CONFIG, null, arg);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
+                Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
             }
         }
     }
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 9aff281..7683b8a 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -254,6 +254,15 @@
         @GuardedBy("mLock")
         private AutofillValue mFocusedValue;
 
+        /**
+         * Id of the last field that cause the Autofill UI to be shown.
+         *
+         * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
+         */
+        // TODO(b/111330312): might not be needed when using IME
+        @GuardedBy("mLock")
+        private AutofillId mLastShownId;
+
         // Objects used to log metrics
         private final long mRequestTime;
         private long mOnSuccessTime;
@@ -284,7 +293,7 @@
         @NonNull
         public SystemPopupPresentationParams getSmartSuggestionParams() {
             synchronized (mLock) {
-                if (mSmartSuggestion != null) {
+                if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) {
                     return mSmartSuggestion;
                 }
                 Rect rect;
@@ -299,6 +308,7 @@
                     return null;
                 }
                 mSmartSuggestion = new SystemPopupPresentationParams(this, rect);
+                mLastShownId = mFocusedId;
                 return mSmartSuggestion;
             }
         }
@@ -401,6 +411,9 @@
             if (mFocusedValue != null) {
                 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
             }
+            if (mLastShownId != null) {
+                pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId);
+            }
             pw.print(prefix); pw.print("client: "); pw.println(mClient);
             final String prefix2 = prefix + "  ";
             if (mFillWindow != null) {
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 3dfeede..58848fc 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -29,7 +29,9 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
 
 import java.util.List;
 import java.util.Set;
@@ -45,7 +47,7 @@
 
     private static final String TAG = ContentCaptureService.class.getSimpleName();
 
-    // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+    // TODO(b/121044306): STOPSHIP use dynamic value, or change to false
     static final boolean DEBUG = true;
     static final boolean VERBOSE = false;
 
@@ -64,15 +66,15 @@
     private final IContentCaptureService mInterface = new IContentCaptureService.Stub() {
 
         @Override
-        public void onSessionLifecycle(InteractionContext context, String sessionId)
+        public void onSessionLifecycle(ContentCaptureContext context, String sessionId)
                 throws RemoteException {
             if (context != null) {
                 mHandler.sendMessage(
-                        obtainMessage(ContentCaptureService::handleOnCreateInteractionSession,
+                        obtainMessage(ContentCaptureService::handleOnCreateSession,
                                 ContentCaptureService.this, context, sessionId));
             } else {
                 mHandler.sendMessage(
-                        obtainMessage(ContentCaptureService::handleOnDestroyInteractionSession,
+                        obtainMessage(ContentCaptureService::handleOnDestroySession,
                                 ContentCaptureService.this, sessionId));
             }
         }
@@ -175,15 +177,15 @@
     }
 
     /**
-     * Creates a new interaction session.
+     * Creates a new content capture session.
      *
-     * @param context interaction context
+     * @param context content capture context
      * @param sessionId the session's Id
      */
-    public void onCreateInteractionSession(@NonNull InteractionContext context,
-            @NonNull InteractionSessionId sessionId) {
+    public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
+            @NonNull ContentCaptureSessionId sessionId) {
         if (VERBOSE) {
-            Log.v(TAG, "onCreateInteractionSession(id=" + sessionId + ", ctx=" + context + ")");
+            Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
         }
     }
 
@@ -194,7 +196,7 @@
      * @param sessionId the session's Id
      * @param request the events
      */
-    public abstract void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId,
+    public abstract void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
             @NonNull ContentCaptureEventsRequest request);
 
     /**
@@ -203,39 +205,39 @@
      * @param sessionId the session's Id
      * @param snapshotData the data
      */
-    public void onActivitySnapshot(@NonNull InteractionSessionId sessionId,
+    public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
             @NonNull SnapshotData snapshotData) {}
 
     /**
-     * Destroys the interaction session.
+     * Destroys the content capture session.
      *
      * @param sessionId the id of the session to destroy
      */
-    public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) {
+    public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
         if (VERBOSE) {
-            Log.v(TAG, "onDestroyInteractionSession(id=" + sessionId + ")");
+            Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
         }
     }
 
     //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
     // so we don't need to create a temporary InteractionSessionId for each event.
 
-    private void handleOnCreateInteractionSession(@NonNull InteractionContext context,
+    private void handleOnCreateSession(@NonNull ContentCaptureContext context,
             @NonNull String sessionId) {
-        onCreateInteractionSession(context, new InteractionSessionId(sessionId));
+        onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
     }
 
     private void handleOnContentCaptureEventsRequest(@NonNull String sessionId,
             @NonNull ContentCaptureEventsRequest request) {
-        onContentCaptureEventsRequest(new InteractionSessionId(sessionId), request);
+        onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), request);
     }
 
     private void handleOnActivitySnapshot(@NonNull String sessionId,
             @NonNull SnapshotData snapshotData) {
-        onActivitySnapshot(new InteractionSessionId(sessionId), snapshotData);
+        onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
     }
 
-    private void handleOnDestroyInteractionSession(@NonNull String sessionId) {
-        onDestroyInteractionSession(new InteractionSessionId(sessionId));
+    private void handleOnDestroySession(@NonNull String sessionId) {
+        onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
     }
 }
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index 29e9abb..8167be9 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -17,8 +17,8 @@
 package android.service.contentcapture;
 
 import android.service.contentcapture.ContentCaptureEventsRequest;
-import android.service.contentcapture.InteractionContext;
 import android.service.contentcapture.SnapshotData;
+import android.view.contentcapture.ContentCaptureContext;
 
 import java.util.List;
 
@@ -30,7 +30,7 @@
 oneway interface IContentCaptureService {
 
     // Called when session is created (context not null) or destroyed (context null)
-    void onSessionLifecycle(in InteractionContext context, String sessionId);
+    void onSessionLifecycle(in ContentCaptureContext context, String sessionId);
 
     void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request);
 
diff --git a/core/java/android/service/contentcapture/InteractionContext.java b/core/java/android/service/contentcapture/InteractionContext.java
deleted file mode 100644
index f1281ff..0000000
--- a/core/java/android/service/contentcapture/InteractionContext.java
+++ /dev/null
@@ -1,157 +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.contentcapture;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.app.TaskInfo;
-import android.content.ComponentName;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-// TODO(b/111276913): add javadocs / implement Parcelable / implement equals/hashcode/toString
-/** @hide */
-@SystemApi
-public final class InteractionContext implements Parcelable {
-
-    /**
-     * Flag used to indicate that the app explicitly disabled content capture for the activity
-     * (using
-     * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}),
-     * in which case the service will just receive activity-level events.
-     */
-    public static final int FLAG_DISABLED_BY_APP = 0x1;
-
-    /**
-     * Flag used to indicate that the activity's window is tagged with
-     * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
-     * activity-level events.
-     */
-    public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
-            FLAG_DISABLED_BY_APP,
-            FLAG_DISABLED_BY_FLAG_SECURE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface ContextCreationFlags{}
-
-    // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
-    private final @NonNull ComponentName mComponentName;
-    private final int mTaskId;
-    private final int mDisplayId;
-    private final int mFlags;
-
-
-    /** @hide */
-    public InteractionContext(@NonNull ComponentName componentName, int taskId, int displayId,
-            int flags) {
-        mComponentName = Preconditions.checkNotNull(componentName);
-        mTaskId = taskId;
-        mDisplayId = displayId;
-        mFlags = flags;
-    }
-
-    /**
-     * Gets the id of the {@link TaskInfo task} associated with this context.
-     */
-    public int getTaskId() {
-        return mTaskId;
-    }
-
-    /**
-     * Gets the activity associated with this context.
-     */
-    public @NonNull ComponentName getActivityComponent() {
-        return mComponentName;
-    }
-
-    /**
-     * Gets the ID of the display associated with this context, as defined by
-     * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
-     */
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    /**
-     * Gets the flags associated with this context.
-     *
-     * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE} and
-     * {@link #FLAG_DISABLED_BY_APP}.
-     */
-    public @ContextCreationFlags int getFlags() {
-        return mFlags;
-    }
-
-    /**
-     * @hide
-     */
-    // TODO(b/111276913): dump to proto as well
-    public void dump(PrintWriter pw) {
-        pw.print("comp="); pw.print(mComponentName.flattenToShortString());
-        pw.print(", taskId="); pw.print(mTaskId);
-        pw.print(", displayId="); pw.print(mDisplayId);
-        if (mFlags > 0) {
-            pw.print(", flags="); pw.print(mFlags);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Context[act=" + mComponentName.flattenToShortString() + ", taskId=" + mTaskId
-                + ", displayId=" + mDisplayId + ", flags=" + mFlags + "]";
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeParcelable(mComponentName, flags);
-        parcel.writeInt(mTaskId);
-        parcel.writeInt(mDisplayId);
-        parcel.writeInt(mFlags);
-    }
-
-    public static final Parcelable.Creator<InteractionContext> CREATOR =
-            new Parcelable.Creator<InteractionContext>() {
-
-        @Override
-        public InteractionContext createFromParcel(Parcel parcel) {
-            final ComponentName componentName = parcel.readParcelable(null);
-            final int taskId = parcel.readInt();
-            final int displayId = parcel.readInt();
-            final int flags = parcel.readInt();
-            return new InteractionContext(componentName, taskId, displayId, flags);
-        }
-
-        @Override
-        public InteractionContext[] newArray(int size) {
-            return new InteractionContext[size];
-        }
-    };
-}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index af7e93e..30d9804 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -29,7 +29,7 @@
 
 /**
  * The current condition of an {@link android.app.AutomaticZenRule}, provided by the
- * {@link ConditionProviderService} that owns the rule. Used to tell the system to enter Do Not
+ * app that owns the rule. Used to tell the system to enter Do Not
  * Disturb mode and request that the system exit Do Not Disturb mode.
  */
 public final class Condition implements Parcelable {
@@ -48,8 +48,8 @@
 
     /**
      * Indicates that Do Not Disturb should be turned off. Note that all Conditions from all
-     * {@link ConditionProviderService} providers must be off for Do Not Disturb to be turned off on
-     * the device.
+     * {@link android.app.AutomaticZenRule} providers must be off for Do Not Disturb to be turned
+     * off on the device.
      */
     public static final int STATE_FALSE = 0;
     /**
@@ -154,7 +154,7 @@
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
 
-        // id is guarantreed not to be null.
+        // id is guaranteed not to be null.
         proto.write(ConditionProto.ID, id.toString());
         proto.write(ConditionProto.SUMMARY, summary);
         proto.write(ConditionProto.LINE_1, line1);
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 5203c8f..45480cb 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -59,7 +59,16 @@
  *
  *  <p> Condition providers cannot be bound by the system on
  * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
+ *
+ * @deprecated Instead of using an automatically bound service, use
+ * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)} to tell the
+ * system about the state of your rule. In order to maintain a link from
+ * Settings to your rule configuration screens, provide a configuration activity that handles
+ * {@link android.app.NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} on your
+ * {@link android.app.AutomaticZenRule} via
+ * {@link android.app.AutomaticZenRule#setConfigurationActivity(ComponentName)}.
  */
+@Deprecated
 public abstract class ConditionProviderService extends Service {
     private final String TAG = ConditionProviderService.class.getSimpleName()
             + "[" + getClass().getSimpleName() + "]";
@@ -79,26 +88,38 @@
     /**
      * The name of the {@code meta-data} tag containing a localized name of the type of zen rules
      * provided by this service.
+     *
+     * @deprecated see {@link android.app.NotificationManager#META_DATA_AUTOMATIC_RULE_TYPE}.
      */
+    @Deprecated
     public static final String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
 
     /**
      * The name of the {@code meta-data} tag containing the {@link ComponentName} of an activity
      * that allows users to configure the conditions provided by this service.
+     *
+     * @deprecated see {@link android.app.NotificationManager#ACTION_AUTOMATIC_ZEN_RULE}.
      */
+    @Deprecated
     public static final String META_DATA_CONFIGURATION_ACTIVITY =
             "android.service.zen.automatic.configurationActivity";
 
     /**
      * The name of the {@code meta-data} tag containing the maximum number of rule instances that
      * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+     *
+     * @deprecated see {@link android.app.NotificationManager#META_DATA_RULE_INSTANCE_LIMIT}.
      */
+    @Deprecated
     public static final String META_DATA_RULE_INSTANCE_LIMIT =
             "android.service.zen.automatic.ruleInstanceLimit";
 
     /**
      * A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
+     *
+     * @deprecated see {@link android.app.NotificationManager#EXTRA_AUTOMATIC_RULE_ID}.
      */
+    @Deprecated
     public static final String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
 
     /**
@@ -171,7 +192,11 @@
      * service that has an {@link android.app.AutomaticZenRule#getConditionId()} equal to this
      * {@link Condition#id}.
      * @param condition the condition that has changed.
+     *
+     * @deprecated see
+     * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)}.
      */
+    @Deprecated
     public final void notifyCondition(Condition condition) {
         if (condition == null) return;
         notifyConditions(new Condition[]{ condition });
@@ -181,7 +206,11 @@
      * Informs the notification manager that the state of one or more Conditions has changed. See
      * {@link #notifyCondition(Condition)} for restrictions.
      * @param conditions the changed conditions.
+     *
+     * @deprecated see
+     *       {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)}.
      */
+    @Deprecated
     public final void notifyConditions(Condition... conditions) {
         if (!isBound() || conditions == null) return;
         try {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1fe97b7..6d2f856 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1482,7 +1482,7 @@
         private boolean mShowBadge;
         private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
         private boolean mHidden;
-        private boolean mAudiblyAlerted;
+        private long mLastAudiblyAlertedMs;
         private boolean mNoisy;
         private ArrayList<Notification.Action> mSmartActions;
         private ArrayList<CharSequence> mSmartReplies;
@@ -1650,12 +1650,12 @@
         }
 
         /**
-         * Returns whether this notification alerted the user via sound or vibration.
+         * Returns the last time this notification alerted the user via sound or vibration.
          *
-         * @return true if the notification alerted the user, false otherwise.
+         * @return the time of the last alerting behavior, in milliseconds.
          */
-        public boolean audiblyAlerted() {
-            return mAudiblyAlerted;
+        public long getLastAudiblyAlertedMillis() {
+            return mLastAudiblyAlertedMs;
         }
 
         /** @hide */
@@ -1672,7 +1672,7 @@
                 CharSequence explanation, String overrideGroupKey,
                 NotificationChannel channel, ArrayList<String> overridePeople,
                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
-                int userSentiment, boolean hidden, boolean audiblyAlerted,
+                int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
                 ArrayList<CharSequence> smartReplies) {
             mKey = key;
@@ -1690,7 +1690,7 @@
             mShowBadge = showBadge;
             mUserSentiment = userSentiment;
             mHidden = hidden;
-            mAudiblyAlerted = audiblyAlerted;
+            mLastAudiblyAlertedMs = lastAudiblyAlertedMs;
             mNoisy = noisy;
             mSmartActions = smartActions;
             mSmartReplies = smartReplies;
@@ -1743,7 +1743,7 @@
         private ArrayMap<String, Boolean> mShowBadge;
         private ArrayMap<String, Integer> mUserSentiment;
         private ArrayMap<String, Boolean> mHidden;
-        private ArrayMap<String, Boolean> mAudiblyAlerted;
+        private ArrayMap<String, Long> mLastAudiblyAlerted;
         private ArrayMap<String, Boolean> mNoisy;
         private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
         private ArrayMap<String, ArrayList<CharSequence>> mSmartReplies;
@@ -1776,7 +1776,7 @@
                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
                     getShowBadge(key), getUserSentiment(key), getHidden(key),
-                    getAudiblyAlerted(key), getNoisy(key), getSmartActions(key),
+                    getLastAudiblyAlerted(key), getNoisy(key), getSmartActions(key),
                     getSmartReplies(key));
             return rank >= 0;
         }
@@ -1915,14 +1915,14 @@
             return hidden == null ? false : hidden.booleanValue();
         }
 
-        private boolean getAudiblyAlerted(String key) {
+        private long getLastAudiblyAlerted(String key) {
             synchronized (this) {
-                if (mAudiblyAlerted == null) {
-                    buildAudiblyAlertedLocked();
+                if (mLastAudiblyAlerted == null) {
+                    buildLastAudiblyAlertedLocked();
                 }
             }
-            Boolean audiblyAlerted = mAudiblyAlerted.get(key);
-            return audiblyAlerted == null ? false : audiblyAlerted.booleanValue();
+            Long lastAudibleAlerted = mLastAudiblyAlerted.get(key);
+            return lastAudibleAlerted == null ? -1 : lastAudibleAlerted.longValue();
         }
 
         private boolean getNoisy(String key) {
@@ -1994,6 +1994,14 @@
             return newMap;
         }
 
+        private ArrayMap<String, Long> buildLongMapFromBundle(Bundle bundle) {
+            ArrayMap<String, Long> newMap = new ArrayMap<>(bundle.size());
+            for (String key : bundle.keySet()) {
+                newMap.put(key, bundle.getLong(key));
+            }
+            return newMap;
+        }
+
         // Locked by 'this'
         private void buildVisibilityOverridesLocked() {
             mVisibilityOverrides = buildIntMapFromBundle(mRankingUpdate.getVisibilityOverrides());
@@ -2070,8 +2078,8 @@
         }
 
         // Locked by 'this'
-        private void buildAudiblyAlertedLocked() {
-            mAudiblyAlerted = buildBooleanMapFromBundle(mRankingUpdate.getAudiblyAlerted());
+        private void buildLastAudiblyAlertedLocked() {
+            mLastAudiblyAlerted = buildLongMapFromBundle(mRankingUpdate.getLastAudiblyAlerted());
         }
 
         // Locked by 'this'
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index f80df93..ebaeff8 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -39,7 +39,7 @@
     private final Bundle mHidden;
     private final Bundle mSmartActions;
     private final Bundle mSmartReplies;
-    private final Bundle mAudiblyAlerted;
+    private final Bundle mLastAudiblyAlerted;
     private final Bundle mNoisy;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
@@ -47,7 +47,7 @@
             int[] importance, Bundle explanation, Bundle overrideGroupKeys,
             Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
             Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions,
-            Bundle smartReplies, Bundle audiblyAlerted, Bundle noisy) {
+            Bundle smartReplies, Bundle lastAudiblyAlerted, Bundle noisy) {
         mKeys = keys;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
@@ -63,7 +63,7 @@
         mHidden = hidden;
         mSmartActions = smartActions;
         mSmartReplies = smartReplies;
-        mAudiblyAlerted = audiblyAlerted;
+        mLastAudiblyAlerted = lastAudiblyAlerted;
         mNoisy = noisy;
     }
 
@@ -84,7 +84,7 @@
         mHidden = in.readBundle();
         mSmartActions = in.readBundle();
         mSmartReplies = in.readBundle();
-        mAudiblyAlerted = in.readBundle();
+        mLastAudiblyAlerted = in.readBundle();
         mNoisy = in.readBundle();
     }
 
@@ -110,7 +110,7 @@
         out.writeBundle(mHidden);
         out.writeBundle(mSmartActions);
         out.writeBundle(mSmartReplies);
-        out.writeBundle(mAudiblyAlerted);
+        out.writeBundle(mLastAudiblyAlerted);
         out.writeBundle(mNoisy);
     }
 
@@ -185,8 +185,8 @@
         return mSmartReplies;
     }
 
-    public Bundle getAudiblyAlerted() {
-        return mAudiblyAlerted;
+    public Bundle getLastAudiblyAlerted() {
+        return mLastAudiblyAlerted;
     }
 
     public Bundle getNoisy() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 0e2ae83..6792c69 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -142,6 +142,7 @@
     private static final String RULE_ATT_SNOOZING = "snoozing";
     private static final String RULE_ATT_NAME = "name";
     private static final String RULE_ATT_COMPONENT = "component";
+    private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity";
     private static final String RULE_ATT_ZEN = "zen";
     private static final String RULE_ATT_CONDITION_ID = "conditionId";
     private static final String RULE_ATT_CREATION_TIME = "creationTime";
@@ -269,7 +270,7 @@
         return buffer.toString();
     }
 
-    private Diff diff(ZenModeConfig to) {
+    public Diff diff(ZenModeConfig to) {
         final Diff d = new Diff();
         if (to == null) {
             return d.addLine("config", "delete");
@@ -623,7 +624,6 @@
     public static ZenRule readRuleXml(XmlPullParser parser) {
         final ZenRule rt = new ZenRule();
         rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
-        rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
         rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
         final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
         rt.zenMode = tryParseZenMode(zen, -1);
@@ -633,6 +633,12 @@
         }
         rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
         rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+        rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY);
+        rt.pkg = (rt.component != null)
+                ? rt.component.getPackageName()
+                : (rt.configurationActivity != null)
+                        ? rt.configurationActivity.getPackageName()
+                        : null;
         rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
         rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
         rt.condition = readConditionXml(parser);
@@ -649,7 +655,6 @@
 
     public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
         out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
-        out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
         if (rule.name != null) {
             out.attribute(null, RULE_ATT_NAME, rule.name);
         }
@@ -657,6 +662,10 @@
         if (rule.component != null) {
             out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
         }
+        if (rule.configurationActivity != null) {
+            out.attribute(null, RULE_ATT_CONFIG_ACTIVITY,
+                    rule.configurationActivity.flattenToString());
+        }
         if (rule.conditionId != null) {
             out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
         }
@@ -1452,12 +1461,15 @@
         public Uri conditionId;          // required for automatic
         public Condition condition;      // optional
         public ComponentName component;  // optional
+        public ComponentName configurationActivity; // optional
         public String id;                // required for automatic (unique)
         @UnsupportedAppUsage
         public long creationTime;        // required for automatic
-        public String enabler;          // package name, only used for manual rules.
+        // package name, only used for manual rules when they have turned DND on.
+        public String enabler;
         public ZenPolicy zenPolicy;
         public boolean modified;    // rule has been modified from initial creation
+        public String pkg;
 
         public ZenRule() { }
 
@@ -1471,6 +1483,7 @@
             conditionId = source.readParcelable(null);
             condition = source.readParcelable(null);
             component = source.readParcelable(null);
+            configurationActivity = source.readParcelable(null);
             if (source.readInt() == 1) {
                 id = source.readString();
             }
@@ -1480,6 +1493,7 @@
             }
             zenPolicy = source.readParcelable(null);
             modified = source.readInt() == 1;
+            pkg = source.readString();
         }
 
         @Override
@@ -1501,6 +1515,7 @@
             dest.writeParcelable(conditionId, 0);
             dest.writeParcelable(condition, 0);
             dest.writeParcelable(component, 0);
+            dest.writeParcelable(configurationActivity, 0);
             if (id != null) {
                 dest.writeInt(1);
                 dest.writeString(id);
@@ -1516,6 +1531,7 @@
             }
             dest.writeParcelable(zenPolicy, 0);
             dest.writeInt(modified ? 1 : 0);
+            dest.writeString(pkg);
         }
 
         @Override
@@ -1528,7 +1544,9 @@
                     .append(",zenMode=").append(Global.zenModeToString(zenMode))
                     .append(",conditionId=").append(conditionId)
                     .append(",condition=").append(condition)
+                    .append(",pkg=").append(pkg)
                     .append(",component=").append(component)
+                    .append(",configActivity=").append(configurationActivity)
                     .append(",creationTime=").append(creationTime)
                     .append(",enabler=").append(enabler)
                     .append(",zenPolicy=").append(zenPolicy)
@@ -1537,6 +1555,7 @@
         }
 
         /** @hide */
+        // TODO: add configuration activity
         public void writeToProto(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
 
@@ -1600,6 +1619,9 @@
             if (!Objects.equals(component, to.component)) {
                 d.addLine(item, "component", component, to.component);
             }
+            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
+                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
+            }
             if (!Objects.equals(id, to.id)) {
                 d.addLine(item, "id", id, to.id);
             }
@@ -1615,6 +1637,9 @@
             if (modified != to.modified) {
                 d.addLine(item, "modified", modified, to.modified);
             }
+            if (pkg != to.pkg) {
+                d.addLine(item, "pkg", pkg, to.pkg);
+            }
         }
 
         @Override
@@ -1629,20 +1654,22 @@
                     && Objects.equals(other.conditionId, conditionId)
                     && Objects.equals(other.condition, condition)
                     && Objects.equals(other.component, component)
+                    && Objects.equals(other.configurationActivity, configurationActivity)
                     && Objects.equals(other.id, id)
                     && Objects.equals(other.enabler, enabler)
                     && Objects.equals(other.zenPolicy, zenPolicy)
+                    && Objects.equals(other.pkg, pkg)
                     && other.modified == modified;
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
-                    component, id, enabler, zenPolicy, modified);
+                    component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
         }
 
         public boolean isAutomaticActive() {
-            return enabled && !snoozing && component != null && isTrueOrUnknown();
+            return enabled && !snoozing && pkg != null && isTrueOrUnknown();
         }
 
         public boolean isTrueOrUnknown() {
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 254a710..7941794 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -26,6 +26,7 @@
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextSelection;
@@ -52,10 +53,15 @@
             in TextLinks.Request request,
             in ITextLinksCallback callback);
 
+    // TODO: Remove
     void onSelectionEvent(
             in TextClassificationSessionId sessionId,
             in SelectionEvent event);
 
+    void onTextClassifierEvent(
+            in TextClassificationSessionId sessionId,
+            in TextClassifierEvent event);
+
     void onCreateTextClassificationSession(
             in TextClassificationContext context,
             in TextClassificationSessionId sessionId);
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 3b813c7..2221d6e 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -39,6 +39,7 @@
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassificationSessionId;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
@@ -193,6 +194,15 @@
 
         /** {@inheritDoc} */
         @Override
+        public void onTextClassifierEvent(
+                TextClassificationSessionId sessionId,
+                TextClassifierEvent event) {
+            Preconditions.checkNotNull(event);
+            TextClassifierService.this.onTextClassifierEvent(sessionId, event);
+        }
+
+        /** {@inheritDoc} */
+        @Override
         public void onDetectLanguage(
                 TextClassificationSessionId sessionId,
                 TextLanguage.Request request,
@@ -368,11 +378,28 @@
      *
      * @param sessionId the session id
      * @param event the selection event
+     * @deprecated
+     *      Use {@link #onTextClassifierEvent(TextClassificationSessionId, TextClassifierEvent)}
+     *      instead
      */
+    @Deprecated
     public void onSelectionEvent(
             @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
 
     /**
+     * Writes the TextClassifier event.
+     * This is called when a TextClassifier event occurs. e.g. user changed selection,
+     * smart selection happened, or a link was clicked.
+     *
+     * <p>The default implementation ignores the event.
+     *
+     * @param sessionId the session id
+     * @param event the TextClassifier event
+     */
+    public void onTextClassifierEvent(
+            @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}
+
+    /**
      * Creates a new text classification session for the specified context.
      *
      * @param context the text classification context
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index d4edde9..13ac9ff 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -259,7 +259,8 @@
      * Returns the source string that was saved during construction.
      *
      * @return the source string that was saved during construction
-     * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)}
+     * @see #ImageSpan(Drawable, String)
+     * @see #ImageSpan(Context, Uri)
      */
     @Nullable
     public String getSource() {
diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java
index 5a55fd7..e43fd83 100644
--- a/core/java/android/text/style/LineBackgroundSpan.java
+++ b/core/java/android/text/style/LineBackgroundSpan.java
@@ -118,7 +118,7 @@
                 int lineNumber) {
             final int originColor = paint.getColor();
             paint.setColor(mColor);
-            canvas.drawRect(left, right, top, bottom, paint);
+            canvas.drawRect(left, top, right, bottom, paint);
             paint.setColor(originColor);
         }
     }
diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java
index b1fc17a..8d4db54 100644
--- a/core/java/android/transition/Scene.java
+++ b/core/java/android/transition/Scene.java
@@ -96,7 +96,7 @@
      * the hierarchy specified by the layoutId resource file.
      *
      * <p>This method is hidden because layoutId-based scenes should be
-     * created by the caching factory method {@link Scene#getCurrentScene(View)}.</p>
+     * created by the caching factory method {@link Scene#getCurrentScene(ViewGroup)}.</p>
      *
      * @param sceneRoot The root of the hierarchy in which scene changes
      * and transitions will take place.
@@ -194,28 +194,28 @@
     }
 
     /**
-     * Set the scene that the given view is in. The current scene is set only
-     * on the root view of a scene, not for every view in that hierarchy. This
+     * Set the scene that the given ViewGroup is in. The current scene is set only
+     * on the root ViewGroup of a scene, not for every view in that hierarchy. This
      * information is used by Scene to determine whether there is a previous
      * scene which should be exited before the new scene is entered.
      *
-     * @param sceneRoot The view on which the current scene is being set
+     * @param sceneRoot The ViewGroup on which the current scene is being set
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    static void setCurrentScene(@NonNull View sceneRoot, @Nullable Scene scene) {
+    static void setCurrentScene(@NonNull ViewGroup sceneRoot, @Nullable Scene scene) {
         sceneRoot.setTagInternal(com.android.internal.R.id.current_scene, scene);
     }
 
     /**
-     * Gets the current {@link Scene} set on the given view. A scene is set on a view
-     * only if that view is the scene root.
+     * Gets the current {@link Scene} set on the given ViewGroup. A scene is set on a ViewGroup
+     * only if that ViewGroup is the scene root.
      *
-     * @param sceneRoot The view on which the current scene will be returned
-     * @return The current Scene set on this view. A value of null indicates that
+     * @param sceneRoot The ViewGroup on which the current scene will be returned
+     * @return The current Scene set on this ViewGroup. A value of null indicates that
      * no Scene is currently set.
      */
     @Nullable
-    public static Scene getCurrentScene(@NonNull View sceneRoot) {
+    public static Scene getCurrentScene(@NonNull ViewGroup sceneRoot) {
         return (Scene) sceneRoot.getTag(com.android.internal.R.id.current_scene);
     }
 
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 5e6d3d1..c06a1fe 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -71,41 +71,18 @@
     void dispatchGetNewSurface();
 
     /**
-     * Tell the window that it is either gaining or losing focus.
-     *
-     * @param hasFocus       {@code true} if window has focus, {@code false} otherwise.
-     * @param inTouchMode    {@code true} if screen is in touch mode, {@code false} otherwise.
-     * @param reportToClient {@code true} when need to report to child view with
-     *                       {@link View#onWindowFocusChanged(boolean)}, {@code false} otherwise.
-     * <p>
-     * Note: In the previous design, there is only one window focus state tracked by
-     * WindowManagerService.
-     * For multi-display, the window focus state is tracked by each display independently.
-     * <p>
-     * It will introduce a problem if the window was already focused on one display and then
-     * switched to another display, since the window focus state on each display is independent,
-     * there is no global window focus state in WindowManagerService, so the window focus state of
-     * the former display remains unchanged.
-     * <p>
-     * When switched back to former display, some flows that rely on the global window focus state
-     * in view root will be missed due to the window focus state remaining unchanged.
-     * (i.e: Showing single IME window when switching between displays.)
-     * <p>
-     * To solve the problem, WindowManagerService tracks the top focused display change and then
-     * callbacks to the client via this method to make sure that the client side will request the
-     * IME on the top focused display, and then set {@param reportToClient} as {@code false} to
-     * ignore reporting to the application, since its focus remains unchanged on its display.
-     *
+     * Tell the window that it is either gaining or losing focus.  Keep it up
+     * to date on the current state showing navigational focus (touch mode) too.
      */
-    void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient);
-    
+    void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
+
     void closeSystemDialogs(String reason);
-    
+
     /**
      * Called for wallpaper windows when their offsets change.
      */
     void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync);
-    
+
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras, boolean sync);
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c4be0e5..9dfd43c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -117,8 +117,10 @@
     void stopFreezingScreen();
 
     // these require DISABLE_KEYGUARD permission
-    void disableKeyguard(IBinder token, String tag);
-    void reenableKeyguard(IBinder token);
+    /** @deprecated use Activity.setShowWhenLocked instead. */
+    void disableKeyguard(IBinder token, String tag, int userId);
+    /** @deprecated use Activity.setShowWhenLocked instead. */
+    void reenableKeyguard(IBinder token, int userId);
     void exitKeyguardSecurely(IOnKeyguardExitResult callback);
     boolean isKeyguardLocked();
     boolean isKeyguardSecure();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b59d8c7..a86abe5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2588,6 +2588,38 @@
     }
 
     /**
+     * Returns the original raw X coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #getX(int)
+     * @see #AXIS_X
+     */
+    public float getRawX(int pointerIndex) {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
+     * Returns the original raw Y coordinate of this event.  For touch
+     * events on the screen, this is the original location of the event
+     * on the screen, before it had been adjusted for the containing window
+     * and views.
+     *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
+     *
+     * @see #getY(int)
+     * @see #AXIS_Y
+     */
+    public float getRawY(int pointerIndex) {
+        return nativeGetRawAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
+    }
+
+    /**
      * Return the precision of the X coordinates being reported.  You can
      * multiply this number with {@link #getX} to find the actual hardware
      * value of the X coordinate.
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 4a5ccdf..ada7853 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -340,7 +340,7 @@
     }
 
     /** Updates icon visibility based on the noisiness of the notification. */
-    public void setAudiblyAlerted(boolean audiblyAlerted) {
+    public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
         mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4297efb7..468d922 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,7 +109,9 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
@@ -121,6 +123,7 @@
 import android.widget.ScrollBarDrawable;
 
 import com.android.internal.R;
+import com.android.internal.util.Preconditions;
 import com.android.internal.view.TooltipPopup;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.widget.ScrollBarUtils;
@@ -5018,6 +5021,13 @@
     private Handler mVisibilityChangeForAutofillHandler;
 
     /**
+     * Used when app developers explicitly set the {@link ContentCaptureSession} associated with the
+     * view (through {@link #setContentCaptureSession(ContentCaptureSession)}.
+     */
+    @Nullable
+    private WeakReference<ContentCaptureSession> mContentCaptureSession;
+
+    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -8161,7 +8171,7 @@
      * is visible.
      *
      * <p>The populated structure is then passed to the service through
-     * {@link ContentCaptureManager#notifyViewAppeared(ViewStructure)}.
+     * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
      *
      * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
      * <ul>
@@ -8977,13 +8987,16 @@
         if (!mContext.isContentCaptureSupported()) return;
 
         // Then check if it's enabled in the context...
-        final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
-        if (cm == null || !cm.isContentCaptureEnabled()) return;
+        final ContentCaptureManager ccm = mContext.getSystemService(ContentCaptureManager.class);
+        if (ccm == null || !ccm.isContentCaptureEnabled()) return;
 
         // ... and finally at the view level
         // NOTE: isImportantForContentCapture() is more expensive than cm.isContentCaptureEnabled()
         if (!isImportantForContentCapture()) return;
 
+        final ContentCaptureSession session = getContentCaptureSession(ccm);
+        if (session == null) return;
+
         if (appeared) {
             if (!isLaidOut() || !isVisibleToUser()
                     || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) {
@@ -8995,10 +9008,10 @@
                 }
                 return;
             }
-            // All good: notify the manager...
-            final ViewStructure structure = cm.newViewStructure(this);
+            // All good: notify it...
+            final ViewStructure structure = session.newViewStructure(this);
             onProvideContentCaptureStructure(structure, /* flags= */ 0);
-            cm.notifyViewAppeared(structure);
+            session.notifyViewAppeared(structure);
             // ...and set the flags
             mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
             mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -9014,14 +9027,85 @@
                 }
                 return;
             }
-            // All good: notify the manager...
-            cm.notifyViewDisappeared(getAutofillId());
+            // All good: notify it...
+            session.notifyViewDisappeared(getAutofillId());
             // ...and set the flags
             mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
             mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
         }
     }
 
+    /**
+     * Sets the (optional) {@link ContentCaptureSession} associated with this view.
+     *
+     * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to
+     * the Content Capture events associated with this view or its view hierarchy (if it's a
+     * {@link ViewGroup}).
+     *
+     * <p>For example, if your activity is associated with a web domain, you could create a session
+     * {@code onCreate()} and associate it with the root view of the activity:
+     *
+     * <pre>
+     *  ContentCaptureManager mgr = getSystemService(ContentCaptureManager.class);
+     *  if (mgr != null && mgr.isContentCaptureEnabled()) {
+     *    View rootView = findViewById(R.my_root_view);
+     *    ContentCaptureSession session = mgr.createContentCaptureSession(new
+     *        ContentCaptureContext.Builder().setUri(myUrl).build());
+     *    rootView.setContentCaptureSession(session);
+     *  }
+     * </pre>
+     *
+     * @param contentCaptureSession a session created by
+     * {@link ContentCaptureManager#createContentCaptureSession(
+     *        android.view.contentcapture.ContentCaptureContext)}.
+     */
+    public void setContentCaptureSession(@NonNull ContentCaptureSession contentCaptureSession) {
+        mContentCaptureSession = new WeakReference<>(
+                Preconditions.checkNotNull(contentCaptureSession));
+    }
+
+    /**
+     * Gets the session used to notify Content Capture events.
+     *
+     * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)},
+     * inherited by ancestore, default session or {@code null} if content capture is disabled for
+     * this view.
+     */
+    @Nullable
+    public final ContentCaptureSession getContentCaptureSession() {
+        // First try the session explicitly set by setContentCaptureSession()
+        if (mContentCaptureSession != null) return mContentCaptureSession.get();
+
+        // Then the session explicitly set in an ancestor
+        ContentCaptureSession session = null;
+        if (mParent instanceof View) {
+            session = ((View) mParent).getContentCaptureSession();
+        }
+
+        // Finally, if no session was explicitly set, use the context's default session.
+        if (session == null) {
+            final ContentCaptureManager ccm = mContext
+                    .getSystemService(ContentCaptureManager.class);
+            return ccm == null ? null : ccm.getMainContentCaptureSession();
+        }
+        return session;
+    }
+
+    /**
+     * Optimized version of {@link #getContentCaptureSession()} that avoids a service lookup.
+     */
+    @Nullable
+    private ContentCaptureSession getContentCaptureSession(@NonNull ContentCaptureManager ccm) {
+        if (mContentCaptureSession != null) return mContentCaptureSession.get();
+
+        ContentCaptureSession session = null;
+        if (mParent instanceof View) {
+            session = ((View) mParent).getContentCaptureSession();
+        }
+
+        return session != null ? session : ccm.getMainContentCaptureSession();
+    }
+
     @Nullable
     private AutofillManager getAutofillManager() {
         return mContext.getSystemService(AutofillManager.class);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9fe0ddc..3f7a512 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1876,6 +1876,7 @@
     }
 
     void dispatchApplyInsets(View host) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
         WindowInsets insets = getWindowInsets(true /* forceConstruct */);
         final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode
                 == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
@@ -1885,6 +1886,7 @@
             insets = insets.consumeDisplayCutout();
         }
         host.dispatchApplyWindowInsets(insets);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
     InsetsController getInsetsController() {
@@ -2763,7 +2765,7 @@
         }
     }
 
-    private void handleWindowFocusChanged(boolean reportToClient) {
+    private void handleWindowFocusChanged() {
         final boolean hasWindowFocus;
         final boolean inTouchMode;
         synchronized (this) {
@@ -2798,9 +2800,8 @@
                         } catch (RemoteException ex) {
                         }
                         // Retry in a bit.
-                        final Message msg = mHandler.obtainMessage(MSG_WINDOW_FOCUS_CHANGED);
-                        msg.arg1 = reportToClient ? 1 : 0;
-                        mHandler.sendMessageDelayed(msg, 500);
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_WINDOW_FOCUS_CHANGED), 500);
                         return;
                     }
                 }
@@ -2817,15 +2818,8 @@
             }
             if (mView != null) {
                 mAttachInfo.mKeyDispatchState.reset();
-                // We dispatch onWindowFocusChanged to child view only when window is gaining /
-                // losing focus.
-                // If the focus is updated from top display change but window focus on the display
-                // remains unchanged, will not callback onWindowFocusChanged again since it may
-                // be redundant & can affect the state when it callbacks.
-                if (reportToClient) {
-                    mView.dispatchWindowFocusChanged(hasWindowFocus);
-                    mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-                }
+                mView.dispatchWindowFocusChanged(hasWindowFocus);
+                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
                 if (mAttachInfo.mTooltipHost != null) {
                     mAttachInfo.mTooltipHost.hideTooltip();
                 }
@@ -4426,7 +4420,7 @@
                     }
                     break;
                 case MSG_WINDOW_FOCUS_CHANGED: {
-                    handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */);
+                    handleWindowFocusChanged();
                 } break;
                 case MSG_DIE:
                     doDie();
@@ -7370,7 +7364,7 @@
         }
 
         if (stage != null) {
-            handleWindowFocusChanged(true /* reportToClient */);
+            handleWindowFocusChanged();
             stage.deliver(q);
         } else {
             finishInputEvent(q);
@@ -7710,11 +7704,6 @@
     }
 
     public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
-        windowFocusChanged(hasFocus, inTouchMode, true /* reportToClient */);
-    }
-
-    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
-            boolean reportToClient) {
         synchronized (this) {
             mWindowFocusChanged = true;
             mUpcomingWindowFocus = hasFocus;
@@ -7722,7 +7711,6 @@
         }
         Message msg = Message.obtain();
         msg.what = MSG_WINDOW_FOCUS_CHANGED;
-        msg.arg1 = reportToClient ? 1 : 0;
         mHandler.sendMessage(msg);
     }
 
@@ -8284,11 +8272,10 @@
         }
 
         @Override
-        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
-                boolean reportToClient) {
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
-                viewAncestor.windowFocusChanged(hasFocus, inTouchMode, reportToClient);
+                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
             }
         }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9227249..699b34a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1471,8 +1471,8 @@
     // Note: don't need to use locked suffix because mContext is final.
     private AutofillClient getClient() {
         final AutofillClient client = mContext.getAutofillClient();
-        if (client == null && sDebug) {
-            Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+        if (client == null && sVerbose) {
+            Log.v(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
                     + mContext);
         }
         return client;
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/core/java/android/view/contentcapture/ContentCaptureContext.aidl
similarity index 89%
rename from core/java/android/service/contentcapture/InteractionContext.aidl
rename to core/java/android/view/contentcapture/ContentCaptureContext.aidl
index 982e095..da492f5 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.contentcapture;
+package android.view.contentcapture;
 
-parcelable InteractionContext;
+parcelable ContentCaptureContext;
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
new file mode 100644
index 0000000..9c11743
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -0,0 +1,333 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Context associated with a {@link ContentCaptureSession}.
+ */
+public final class ContentCaptureContext implements Parcelable {
+
+    /*
+     * IMPLEMENTATION NOTICE:
+     *
+     * This object contains both the info that's explicitly added by apps (hence it's public), but
+     * it also contains info injected by the server (and are accessible through @SystemApi methods).
+     */
+
+    /**
+     * Flag used to indicate that the app explicitly disabled content capture for the activity
+     * (using
+     * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}),
+     * in which case the service will just receive activity-level events.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_DISABLED_BY_APP = 0x1;
+
+    /**
+     * Flag used to indicate that the activity's window is tagged with
+     * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
+     * activity-level events.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_DISABLED_BY_APP,
+            FLAG_DISABLED_BY_FLAG_SECURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContextCreationFlags{}
+
+    /**
+     * Flag indicating if this object has the app-provided context (which is set on
+     * {@link ContentCaptureManager#createContentCaptureSession(ContentCaptureContext)}).
+     */
+    private final boolean mHasClientContext;
+
+    // Fields below are set by app on Builder
+    private final @Nullable Bundle mExtras;
+    private final @Nullable Uri mUri;
+
+    // Fields below are set by server when the session starts
+    // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
+    private final @Nullable ComponentName mComponentName;
+    private final int mTaskId;
+    private final int mDisplayId;
+    private final int mFlags;
+
+    /** @hide */
+    public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
+            @NonNull ComponentName componentName, int taskId, int displayId, int flags) {
+        if (clientContext != null) {
+            mHasClientContext = true;
+            mExtras = clientContext.mExtras;
+            mUri = clientContext.mUri;
+        } else {
+            mHasClientContext = false;
+            mExtras = null;
+            mUri = null;
+        }
+        mComponentName = Preconditions.checkNotNull(componentName);
+        mTaskId = taskId;
+        mDisplayId = displayId;
+        mFlags = flags;
+    }
+
+    private ContentCaptureContext(@NonNull Builder builder) {
+        mHasClientContext = true;
+        mExtras = builder.mExtras;
+        mUri = builder.mUri;
+
+        mComponentName  = null;
+        mTaskId = mFlags = mDisplayId = 0;
+    }
+
+    /**
+     * Gets the (optional) extras set by the app.
+     *
+     * <p>It can be used to provide vendor-specific data that can be modified and examined.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Gets the (optional) URI set by the app.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
+     * Gets the id of the {@link TaskInfo task} associated with this context.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    /**
+     * Gets the activity associated with this context.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull ComponentName getActivityComponent() {
+        return mComponentName;
+    }
+
+    /**
+     * Gets the ID of the display associated with this context, as defined by
+     * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Gets the flags associated with this context.
+     *
+     * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE} and
+     * {@link #FLAG_DISABLED_BY_APP}.
+     *
+     * @hide
+     */
+    @SystemApi
+     public @ContextCreationFlags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Builder for {@link ContentCaptureContext} objects.
+     */
+    public static final class Builder {
+        private Bundle mExtras;
+        private Uri mUri;
+
+        /**
+         * Sets extra options associated with this context.
+         *
+         * <p>It can be used to provide vendor-specific data that can be modified and examined.
+         *
+         * @param extras extra options.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            // TODO(b/111276913): check build just once / throw exception / test / document
+            mExtras = Preconditions.checkNotNull(extras);
+            return this;
+        }
+
+        /**
+         * Sets the {@link Uri} associated with this context.
+         *
+         * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for an example.
+         *
+         * @param uri URI associated with this context.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setUri(@NonNull Uri uri) {
+            // TODO(b/111276913): check build just once / throw exception / test / document
+            mUri = Preconditions.checkNotNull(uri);
+            return this;
+        }
+
+        /**
+         * Builds the {@link ContentCaptureContext}.
+         */
+        public ContentCaptureContext build() {
+            // TODO(b/111276913): check build just once / throw exception / test / document
+            // TODO(b/111276913): make sure it at least one property (uri / extras) / test /
+            // throw exception / documment
+            return new ContentCaptureContext(this);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    // TODO(b/111276913): dump to proto as well
+    public void dump(PrintWriter pw) {
+        pw.print("comp="); pw.print(ComponentName.flattenToShortString(mComponentName));
+        pw.print(", taskId="); pw.print(mTaskId);
+        pw.print(", displayId="); pw.print(mDisplayId);
+        if (mFlags > 0) {
+            pw.print(", flags="); pw.print(mFlags);
+        }
+        if (mExtras != null) {
+            // NOTE: cannot dump because it could contain PII
+            pw.print(", hasExtras");
+        }
+        if (mUri != null) {
+            // NOTE: cannot dump because it could contain PII
+            pw.print(", hasUri");
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder("Context[act=")
+                .append(ComponentName.flattenToShortString(mComponentName))
+                .append(", taskId=").append(mTaskId)
+                .append(", displayId=").append(mDisplayId)
+                .append(", flags=").append(mFlags);
+        if (mExtras != null) {
+            // NOTE: cannot print because it could contain PII
+            builder.append(", hasExtras");
+        }
+        if (mUri != null) {
+            // NOTE: cannot print because it could contain PII
+            builder.append(", hasUri");
+        }
+        return builder.append(']').toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mHasClientContext ? 1 : 0);
+        if (mHasClientContext) {
+            parcel.writeParcelable(mUri, flags);
+            parcel.writeBundle(mExtras);
+        }
+        parcel.writeParcelable(mComponentName, flags);
+        if (mComponentName != null) {
+            parcel.writeInt(mTaskId);
+            parcel.writeInt(mDisplayId);
+            parcel.writeInt(mFlags);
+        }
+    }
+
+    public static final Parcelable.Creator<ContentCaptureContext> CREATOR =
+            new Parcelable.Creator<ContentCaptureContext>() {
+
+        @Override
+        public ContentCaptureContext createFromParcel(Parcel parcel) {
+            final boolean hasClientContext = parcel.readInt() == 1;
+
+            final ContentCaptureContext clientContext;
+            if (hasClientContext) {
+                final Builder builder = new Builder();
+                final Uri uri = parcel.readParcelable(null);
+                final Bundle extras = parcel.readBundle();
+                if (uri != null) builder.setUri(uri);
+                if (extras != null) builder.setExtras(extras);
+                // Must reconstruct the client context using the Builder API
+                clientContext = new ContentCaptureContext(builder);
+            } else {
+                clientContext = null;
+            }
+            final ComponentName componentName = parcel.readParcelable(null);
+            if (componentName == null) {
+                // Client-state only
+                return clientContext;
+            } else {
+                final int taskId = parcel.readInt();
+                final int displayId = parcel.readInt();
+                final int flags = parcel.readInt();
+                        return new ContentCaptureContext(clientContext, componentName, taskId,
+                                displayId, flags);
+                    }
+        }
+
+        @Override
+        public ContentCaptureContext[] newArray(int size) {
+            return new ContentCaptureContext[size];
+        }
+    };
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 66fa530..5d8fe5f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -21,7 +21,6 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemClock;
 import android.view.autofill.AutofillId;
 
 import com.android.internal.util.Preconditions;
@@ -41,54 +40,18 @@
     public static final int TYPE_ACTIVITY_CREATED = -1;
 
     /**
-     * Called when the activity is started.
-     *
-     * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
-     * something related to a session and/or domain.
-     */
-    @Deprecated
-    public static final int TYPE_ACTIVITY_STARTED  = 1;
-
-    /**
-     * Called when the activity is resumed.
-     *
-     * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
-     * something related to a session and/or domain.
-     */
-    @Deprecated
-    public static final int TYPE_ACTIVITY_RESUMED = 2;
-
-    /**
-     * Called when the activity is paused.
-     *
-     * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
-     * something related to a session and/or domain.
-     */
-    @Deprecated
-    public static final int TYPE_ACTIVITY_PAUSED = 3;
-
-    /**
-     * Called when the activity is stopped.
-     *
-     * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
-     * something related to a session and/or domain.
-     */
-    @Deprecated
-    public static final int TYPE_ACTIVITY_STOPPED  = 4;
-
-    /**
      * Called when a node has been added to the screen and is visible to the user.
      *
      * <p>The metadata of the node is available through {@link #getViewNode()}.
      */
-    public static final int TYPE_VIEW_APPEARED = 5;
+    public static final int TYPE_VIEW_APPEARED = 1;
 
     /**
      * Called when a node has been removed from the screen and is not visible to the user anymore.
      *
      * <p>The id of the node is available through {@link #getId()}.
      */
-    public static final int TYPE_VIEW_DISAPPEARED = 6;
+    public static final int TYPE_VIEW_DISAPPEARED = 2;
 
     /**
      * Called when the text of a node has been changed.
@@ -96,16 +59,12 @@
      * <p>The id of the node is available through {@link #getId()}, and the new text is
      * available through {@link #getText()}.
      */
-    public static final int TYPE_VIEW_TEXT_CHANGED = 7;
+    public static final int TYPE_VIEW_TEXT_CHANGED = 3;
 
     // TODO(b/111276913): add event to indicate when FLAG_SECURE was changed?
 
     /** @hide */
     @IntDef(prefix = { "TYPE_" }, value = {
-            TYPE_ACTIVITY_STARTED,
-            TYPE_ACTIVITY_PAUSED,
-            TYPE_ACTIVITY_RESUMED,
-            TYPE_ACTIVITY_STOPPED,
             TYPE_VIEW_APPEARED,
             TYPE_VIEW_DISAPPEARED,
             TYPE_VIEW_TEXT_CHANGED
@@ -130,7 +89,7 @@
 
     /** @hide */
     public ContentCaptureEvent(int type, int flags) {
-        this(type, SystemClock.uptimeMillis(), flags);
+        this(type, System.currentTimeMillis(), flags);
     }
 
     /** @hide */
@@ -159,9 +118,7 @@
     /**
      * Gets the type of the event.
      *
-     * @return one of {@link #TYPE_ACTIVITY_STARTED}, {@link #TYPE_ACTIVITY_RESUMED},
-     * {@link #TYPE_ACTIVITY_PAUSED}, {@link #TYPE_ACTIVITY_STOPPED},
-     * {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
+     * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
      * or {@link #TYPE_VIEW_TEXT_CHANGED}.
      */
     public @EventType int getType() {
@@ -169,7 +126,7 @@
     }
 
     /**
-     * Gets when the event was generated, in ms.
+     * Gets when the event was generated, in millis since epoch.
      */
     public long getEventTime() {
         return mEventTime;
@@ -179,7 +136,7 @@
      * Gets optional flags associated with the event.
      *
      * @return either {@code 0} or
-     * {@link android.view.contentcapture.ContentCaptureManager#FLAG_USER_INPUT}.
+     * {@link android.view.contentcapture.ContentCaptureSession#FLAG_USER_INPUT}.
      */
     public int getFlags() {
         return mFlags;
@@ -295,14 +252,6 @@
     /** @hide */
     public static String getTypeAsString(@EventType int type) {
         switch (type) {
-            case TYPE_ACTIVITY_STARTED:
-                return "ACTIVITY_STARTED";
-            case TYPE_ACTIVITY_RESUMED:
-                return "ACTIVITY_RESUMED";
-            case TYPE_ACTIVITY_PAUSED:
-                return "ACTIVITY_PAUSED";
-            case TYPE_ACTIVITY_STOPPED:
-                return "ACTIVITY_STOPPED";
             case TYPE_VIEW_APPEARED:
                 return "VIEW_APPEARED";
             case TYPE_VIEW_DISAPPEARED:
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 1889692..7fbbfb7 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,34 +15,20 @@
  */
 package android.view.contentcapture;
 
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.ComponentName;
 import android.content.Context;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewStructure;
-import android.view.autofill.AutofillId;
-import android.view.contentcapture.ContentCaptureEvent.EventType;
 
-import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /*
@@ -60,51 +46,11 @@
 
     private static final String TAG = ContentCaptureManager.class.getSimpleName();
 
-    // TODO(b/111276913): define a way to dynamically set them(for example, using settings?)
-    private static final boolean VERBOSE = false;
-    private static final boolean DEBUG = true; // STOPSHIP if not set to false
-
-    /**
-     * Used to indicate that a text change was caused by user input (for example, through IME).
-     */
-    //TODO(b/111276913): link to notifyTextChanged() method once available
-    public static final int FLAG_USER_INPUT = 0x1;
-
-    /**
-     * Initial state, when there is no session.
-     *
-     * @hide
-     */
-    public static final int STATE_UNKNOWN = 0;
-
-    /**
-     * Service's startSession() was called, but server didn't confirm it was created yet.
-     *
-     * @hide
-     */
-    public static final int STATE_WAITING_FOR_SERVER = 1;
-
-    /**
-     * Session is active.
-     *
-     * @hide
-     */
-    public static final int STATE_ACTIVE = 2;
-
-    /**
-     * Session is disabled.
-     *
-     * @hide
-     */
-    public static final int STATE_DISABLED = 3;
-
     private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
 
-    /**
-     * Maximum number of events that are buffered before sent to the app.
-     */
-    // TODO(b/111276913): use settings
-    private static final int MAX_BUFFER_SIZE = 100;
+    // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
+    static final boolean VERBOSE = false;
+    static final boolean DEBUG = true; // STOPSHIP if not set to false
 
     @NonNull
     private final AtomicBoolean mDisabled = new AtomicBoolean();
@@ -115,27 +61,13 @@
     @Nullable
     private final IContentCaptureManager mService;
 
-    @Nullable
-    private String mId;
-
-    private int mState = STATE_UNKNOWN;
-
-    @Nullable
-    private IBinder mApplicationToken;
-
-    @Nullable
-    private ComponentName mComponentName;
-
-    /**
-     * List of events held to be sent as a batch.
-     */
-    @Nullable
-    private ArrayList<ContentCaptureEvent> mEvents;
-
-    // TODO(b/111276913): use UI Thread directly (as calls are one-way) or a shared thread / handler
+    // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
     // held at the Application level
+    @NonNull
     private final Handler mHandler;
 
+    private ContentCaptureSession mMainSession;
+
     /** @hide */
     public ContentCaptureManager(@NonNull Context context,
             @Nullable IContentCaptureManager service) {
@@ -144,253 +76,93 @@
             Log.v(TAG, "Constructor for " + context.getPackageName());
         }
         mService = service;
-        // TODO(b/111276913): use an existing bg thread instead...
+        // TODO(b/119220549): use an existing bg thread instead...
         final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
         bgThread.start();
         mHandler = Handler.createAsync(bgThread.getLooper());
     }
 
-    /** @hide */
-    public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
-        if (!isContentCaptureEnabled()) return;
-
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleStartSession, this,
-                token, componentName));
-    }
-
-    private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
-        if (mState != STATE_UNKNOWN) {
-            // TODO(b/111276913): revisit this scenario
-            Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
-                    + getStateAsString(mState));
-            return;
-        }
-        mState = STATE_WAITING_FOR_SERVER;
-        mId = UUID.randomUUID().toString();
-        mApplicationToken = token;
-        mComponentName = componentName;
-
-        if (VERBOSE) {
-            Log.v(TAG, "handleStartSession(): token=" + token + ", act="
-                    + getActivityDebugName() + ", id=" + mId);
-        }
-        final int flags = 0; // TODO(b/111276913): get proper flags
-
-        try {
-            mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
-                    mId, flags, new IResultReceiver.Stub() {
-                        @Override
-                        public void send(int resultCode, Bundle resultData) {
-                            handleSessionStarted(resultCode);
-                        }
-                    });
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
-                    + e);
-        }
-    }
-
-    private void handleSessionStarted(int resultCode) {
-        mState = resultCode;
-        mDisabled.set(mState == STATE_DISABLED);
-        if (VERBOSE) {
-            Log.v(TAG, "onActivityStarted() result: code=" + resultCode + ", id=" + mId
-                    + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
-        }
-    }
-
-    private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
-        if (mEvents == null) {
-            if (VERBOSE) {
-                Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
-            }
-            mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
-        }
-        mEvents.add(event);
-        final int numberEvents = mEvents.size();
-        if (numberEvents < MAX_BUFFER_SIZE && !forceFlush) {
-            // Buffering events, return right away...
-            return;
-        }
-
-        if (mState != STATE_ACTIVE) {
-            // Callback from startSession hasn't been called yet - typically happens on system
-            // apps that are started before the system service
-            // TODO(b/111276913): try to ignore session while system is not ready / boot
-            // not complete instead. Similarly, the manager service should return right away
-            // when the user does not have a service set
-            if (VERBOSE) {
-                Log.v(TAG, "Closing session for " + getActivityDebugName()
-                        + " after " + numberEvents + " delayed events and state "
-                        + getStateAsString(mState));
-            }
-            handleResetState();
-            // TODO(b/111276913): blacklist activity / use special flag to indicate that
-            // when it's launched again
-            return;
-        }
-
-        if (mId == null) {
-            // Sanity check - should not happen
-            Log.wtf(TAG, "null session id for " + getActivityDebugName());
-            return;
-        }
-
-        try {
-            if (DEBUG) {
-                Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
-            }
-            mService.sendEvents(mContext.getUserId(), mId, mEvents);
-            // TODO(b/111276913): decide whether we should clear or set it to null, as each has
-            // its own advantages: clearing will save extra allocations while the session is
-            // active, while setting to null would save memory if there's no more event coming.
-            mEvents.clear();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
-                    + ": " + e);
-        }
+    @NonNull
+    private static Handler newHandler() {
+        // TODO(b/119220549): use an existing bg thread instead...
+        // TODO(b/119220549): use UI Thread directly (as calls are one-way) or an existing bgThread
+        // or a shared thread / handler held at the Application level
+        final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
+        bgThread.start();
+        return Handler.createAsync(bgThread.getLooper());
     }
 
     /**
-     * Used for intermediate events (i.e, other than created and destroyed).
+     * Creates a new {@link ContentCaptureSession}.
      *
-     * @hide
+     * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info.
      */
-    public void onActivityLifecycleEvent(@EventType int type) {
-        if (!isContentCaptureEnabled()) return;
-        if (VERBOSE) {
-            Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugName()
-                    + ": " + ContentCaptureEvent.getTypeAsString(type));
+    @NonNull
+    public ContentCaptureSession createContentCaptureSession(
+            @NonNull ContentCaptureContext context) {
+        if (DEBUG) Log.d(TAG, "createContentCaptureSession(): " + context);
+        // TODO(b/121033016): for now we're updating the main session, but we need instead:
+        // 1.Keep a list of sessions
+        // 2.Making sure the applicationToken and componentName passed by
+        // the activity is used on all of these sessions
+        // 3.We might also need to delay the start of all of these sessions until
+        // onActivityStarted() is called (and the main session is created).
+        // 4.Close (and delete) these sessions when onActivityStopped() is called.
+        // 5.Figure out whether each session will have its own mDisabled AtomicBoolean.
+        if (mMainSession == null) {
+            mMainSession = new ContentCaptureSession(mContext, mHandler, mService,
+                    mDisabled, Preconditions.checkNotNull(context));
+        } else {
+            throw new IllegalStateException("Manager already has a session: " + mMainSession);
         }
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
-                new ContentCaptureEvent(type), /* forceFlush= */ true));
-    }
-
-    /** @hide */
-    public void onActivityDestroyed() {
-        if (!isContentCaptureEnabled()) return;
-
-        //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
-        // id) and send it to the cache of batched commands
-        if (VERBOSE) {
-            Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsString(mState)
-                    + ", mId=" + mId);
-        }
-
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleFinishSession, this));
-    }
-
-    private void handleFinishSession() {
-        //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
-        // to system_server, so it's ok to call both in sequence here. But once we split
-        // them so the events are sent directly to the service, we need to make sure they're
-        // sent in order.
-        try {
-            if (DEBUG) {
-                Log.d(TAG, "Finishing session " + mId + " with "
-                        + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
-                        + getActivityDebugName());
-            }
-
-            mService.finishSession(mContext.getUserId(), mId, mEvents);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error finishing session " + mId + " for " + getActivityDebugName()
-                    + ": " + e);
-        } finally {
-            handleResetState();
-        }
-    }
-
-    private void handleResetState() {
-        mState = STATE_UNKNOWN;
-        mId = null;
-        mApplicationToken = null;
-        mComponentName = null;
-        mEvents = null;
+        return mMainSession;
     }
 
     /**
-     * Notifies the Intelligence Service that a node has been added to the view structure.
+     * Gets the main session associated with the context.
      *
-     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
-     * automatically by the Android System for views that return {@code true} on
-     * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
-     *
-     * @param node node that has been added.
-     */
-    public void notifyViewAppeared(@NonNull ViewStructure node) {
-        Preconditions.checkNotNull(node);
-        if (!isContentCaptureEnabled()) return;
-
-        if (!(node instanceof ViewNode.ViewStructureImpl)) {
-            throw new IllegalArgumentException("Invalid node class: " + node.getClass());
-        }
-
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
-                new ContentCaptureEvent(TYPE_VIEW_APPEARED)
-                        .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
-                        /* forceFlush= */ false));
-    }
-
-    /**
-     * Notifies the Intelligence Service that a node has been removed from the view structure.
-     *
-     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
-     * automatically by the Android System for standard views.
-     *
-     * @param id id of the node that has been removed.
-     */
-    public void notifyViewDisappeared(@NonNull AutofillId id) {
-        Preconditions.checkNotNull(id);
-        if (!isContentCaptureEnabled()) return;
-
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
-                new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
-                        /* forceFlush= */ false));
-    }
-
-    /**
-     * Notifies the Intelligence Service that the value of a text node has been changed.
-     *
-     * @param id of the node.
-     * @param text new text.
-     * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
-     * changed by the user (for example, through the keyboard).
-     */
-    public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
-            int flags) {
-        Preconditions.checkNotNull(id);
-
-        if (!isContentCaptureEnabled()) return;
-
-        mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
-                new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
-                        .setText(text), /* forceFlush= */ false));
-    }
-
-    /**
-     * Creates a {@link ViewStructure} for a "standard" view.
+     * <p>By default there's just one (associated with the activity lifecycle), but apps could
+     * explicitly add more using {@link #createContentCaptureSession(ContentCaptureContext)}.
      *
      * @hide
      */
     @NonNull
-    public ViewStructure newViewStructure(@NonNull View view) {
-        return new ViewNode.ViewStructureImpl(view);
+    public ContentCaptureSession getMainContentCaptureSession() {
+        // TODO(b/121033016): figure out how to manage the "default" session when it support
+        // multiple sessions (can't just be the first one, as it could be closed).
+        if (mMainSession == null) {
+            mMainSession = new ContentCaptureSession(mContext, mHandler, mService, mDisabled,
+                    /* contentCaptureContext=  */ null);
+            if (VERBOSE) {
+                Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+            }
+        }
+        return mMainSession;
+    }
+
+    /** @hide */
+    public void onActivityStarted(@NonNull IBinder applicationToken,
+            @NonNull ComponentName activityComponent) {
+        // TODO(b/121033016): must start all sessions
+        getMainContentCaptureSession().start(applicationToken, activityComponent);
+    }
+
+    /** @hide */
+    public void onActivityStopped() {
+        // TODO(b/121033016): must finish all sessions
+        getMainContentCaptureSession().destroy();
     }
 
     /**
-     * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
-     * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
+     * Flushes the content of all sessions.
      *
-     * @param parentId id of the virtual view parent (it can be obtained by calling
-     * {@link ViewStructure#getAutofillId()} on the parent).
-     * @param virtualId id of the virtual child, relative to the parent.
+     * <p>Typically called by {@code Activity} when it's paused / resumed.
      *
-     * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
+     * @hide
      */
-    @NonNull
-    public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
-        return new ViewNode.ViewStructureImpl(parentId, virtualId);
+    public void flush() {
+        // TODO(b/121033016): must flush all sessions
+        getMainContentCaptureSession().flush();
     }
 
     /**
@@ -399,7 +171,7 @@
      */
     @Nullable
     public ComponentName getServiceComponentName() {
-        //TODO(b/111276913): implement
+        //TODO(b/121047489): implement
         return null;
     }
 
@@ -417,68 +189,35 @@
      * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
      */
     public void setContentCaptureEnabled(boolean enabled) {
+        //TODO(b/111276913): implement (need to finish / disable all sessions)
+    }
+
+    /**
+     * Called by the ap to request the Content Capture service to remove user-data associated with
+     * some context.
+     *
+     * @param request object specifying what user data should be removed.
+     */
+    public void removeUserData(@NonNull UserDataRemovalRequest request) {
         //TODO(b/111276913): implement
     }
 
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.println("ContentCaptureManager");
-        final String prefix2 = prefix + "  ";
-        pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
-        pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
+
+        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled.get());
+        pw.print(prefix); pw.print("Context: "); pw.println(mContext);
+        pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
         if (mService != null) {
-            pw.print(prefix2); pw.print("mService: "); pw.println(mService);
+            pw.print(prefix); pw.print("Service: "); pw.println(mService);
         }
-        pw.print(prefix2); pw.print("mDisabled: "); pw.println(mDisabled.get());
-        pw.print(prefix2); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
-        if (mId != null) {
-            pw.print(prefix2); pw.print("id: "); pw.println(mId);
-        }
-        pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
-        pw.print(getStateAsString(mState)); pw.println(")");
-        if (mApplicationToken != null) {
-            pw.print(prefix2); pw.print("app token: "); pw.println(mApplicationToken);
-        }
-        if (mComponentName != null) {
-            pw.print(prefix2); pw.print("component name: ");
-            pw.println(mComponentName.flattenToShortString());
-        }
-        if (mEvents != null) {
-            final int numberEvents = mEvents.size();
-            pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
-            pw.print('/'); pw.println(MAX_BUFFER_SIZE);
-            if (VERBOSE && numberEvents > 0) {
-                final String prefix3 = prefix2 + "  ";
-                for (int i = 0; i < numberEvents; i++) {
-                    final ContentCaptureEvent event = mEvents.get(i);
-                    pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
-                    pw.println();
-                }
-            }
-        }
-    }
-
-    /**
-     * Gets a string that can be used to identify the activity on logging statements.
-     */
-    private String getActivityDebugName() {
-        return mComponentName == null ? mContext.getPackageName()
-                : mComponentName.flattenToShortString();
-    }
-
-    @NonNull
-    private static String getStateAsString(int state) {
-        switch (state) {
-            case STATE_UNKNOWN:
-                return "UNKNOWN";
-            case STATE_WAITING_FOR_SERVER:
-                return "WAITING_FOR_SERVER";
-            case STATE_ACTIVE:
-                return "ACTIVE";
-            case STATE_DISABLED:
-                return "DISABLED";
-            default:
-                return "INVALID:" + state;
+        if (mMainSession != null) {
+            final String prefix2 = prefix + "  ";
+            pw.print(prefix); pw.println("Main session:");
+            mMainSession.dump(prefix2, pw);
+        } else {
+            pw.print(prefix); pw.println("No sessions");
         }
     }
 }
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
new file mode 100644
index 0000000..632955d
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -0,0 +1,570 @@
+/*
+ * 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.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+import static android.view.contentcapture.ContentCaptureManager.DEBUG;
+import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.TimeUtils;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Session used to notify a system-provided Content Capture service about events associated with
+ * views.
+ */
+public final class ContentCaptureSession implements AutoCloseable {
+
+    /*
+     * IMPLEMENTATION NOTICE:
+     *
+     * All methods in this class should return right away, or do the real work in a handler thread.
+     *
+     * Hence, the only field that must be thread-safe is mEnabled, which is called at the
+     * beginning of every method.
+     */
+
+    private static final String TAG = ContentCaptureSession.class.getSimpleName();
+
+    /**
+     * Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
+     * thext change was caused by user input (for example, through IME).
+     */
+    public static final int FLAG_USER_INPUT = 0x1;
+
+    /**
+     * Initial state, when there is no session.
+     *
+     * @hide
+     */
+    public static final int STATE_UNKNOWN = 0;
+
+    /**
+     * Service's startSession() was called, but server didn't confirm it was created yet.
+     *
+     * @hide
+     */
+    public static final int STATE_WAITING_FOR_SERVER = 1;
+
+    /**
+     * Session is active.
+     *
+     * @hide
+     */
+    public static final int STATE_ACTIVE = 2;
+
+    /**
+     * Session is disabled.
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED = 3;
+
+    /**
+     * Handler message used to flush the buffer.
+     */
+    private static final int MSG_FLUSH = 1;
+
+    /**
+     * Maximum number of events that are buffered before sent to the app.
+     */
+    // TODO(b/121044064): use settings
+    private static final int MAX_BUFFER_SIZE = 100;
+
+    /**
+     * Frequency the buffer is flushed if stale.
+     */
+    // TODO(b/121044064): use settings
+    private static final int FLUSHING_FREQUENCY_MS = 5_000;
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    @NonNull
+    private final AtomicBoolean mDisabled;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final Handler mHandler;
+
+    @Nullable
+    private final IContentCaptureManager mService;
+
+    @Nullable
+    private final String mId = UUID.randomUUID().toString();
+
+    private int mState = STATE_UNKNOWN;
+
+    @Nullable
+    private IBinder mApplicationToken;
+
+    @Nullable
+    private ComponentName mComponentName;
+
+    /**
+     * List of events held to be sent as a batch.
+     */
+    // TODO(b/111276913): once we support multiple sessions, we need to move the buffer of events
+    // to its own class so it's shared by all sessions
+    @Nullable
+    private ArrayList<ContentCaptureEvent> mEvents;
+
+    // Used just for debugging purposes (on dump)
+    private long mNextFlush;
+
+    // Lazily created on demand.
+    private ContentCaptureSessionId mContentCaptureSessionId;
+
+    /**
+     * {@link ContentCaptureContext} set by client, or {@code null} when it's the
+     * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
+     * context.
+     */
+    @Nullable
+    private final ContentCaptureContext mClientContext;
+
+    /** @hide */
+    protected ContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
+            @Nullable IContentCaptureManager service, @NonNull AtomicBoolean disabled,
+            @Nullable ContentCaptureContext clientContext) {
+        mContext = context;
+        mHandler = handler;
+        mService = service;
+        mDisabled = disabled;
+        mClientContext = clientContext;
+        mCloseGuard.open("destroy");
+    }
+
+    /**
+     * Gets the id used to identify this session.
+     */
+    public ContentCaptureSessionId getContentCaptureSessionId() {
+        if (mContentCaptureSessionId == null) {
+            mContentCaptureSessionId = new ContentCaptureSessionId(mId);
+        }
+        return mContentCaptureSessionId;
+    }
+
+    /**
+     * Starts this session.
+     *
+     * @hide
+     */
+    void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
+        if (!isContentCaptureEnabled()) return;
+
+        if (VERBOSE) {
+            Log.v(TAG, "start(): token=" + applicationToken + ", comp="
+                    + ComponentName.flattenToShortString(activityComponent));
+        }
+
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleStartSession, this,
+                applicationToken, activityComponent));
+    }
+
+    /**
+     * Flushes the buffered events to the service.
+     *
+     * @hide
+     */
+    void flush() {
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleForceFlush, this));
+    }
+
+    /**
+     * Destroys this session, flushing out all pending notifications to the service.
+     *
+     * <p>Once destroyed, any new notification will be dropped.
+     */
+    public void destroy() {
+        //TODO(b/111276913): mark it as destroyed so other methods are ignored (and test on CTS)
+
+        if (!isContentCaptureEnabled()) return;
+
+        //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+        // id) and send it to the cache of batched commands
+        if (VERBOSE) {
+            Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
+        }
+
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleDestroySession, this));
+        mCloseGuard.close();
+    }
+
+    /** @hide */
+    @Override
+    public void close() {
+        destroy();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
+        if (mState != STATE_UNKNOWN) {
+            // TODO(b/111276913): revisit this scenario
+            Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
+                    + getStateAsString(mState));
+            return;
+        }
+        mState = STATE_WAITING_FOR_SERVER;
+        mApplicationToken = token;
+        mComponentName = componentName;
+
+        if (VERBOSE) {
+            Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+                    + getActivityDebugName() + ", id=" + mId);
+        }
+        final int flags = 0; // TODO(b/111276913): get proper flags
+
+        try {
+            mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
+                    mId, mClientContext, flags, new IResultReceiver.Stub() {
+                        @Override
+                        public void send(int resultCode, Bundle resultData) {
+                            handleSessionStarted(resultCode);
+                        }
+                    });
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
+                    + e);
+        }
+    }
+
+    private void handleSessionStarted(int resultCode) {
+        mState = resultCode;
+        mDisabled.set(mState == STATE_DISABLED);
+        if (VERBOSE) {
+            Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+                    + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
+        }
+    }
+
+    private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        if (mEvents == null) {
+            if (VERBOSE) {
+                Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
+            }
+            mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
+        }
+        mEvents.add(event);
+
+        final int numberEvents = mEvents.size();
+
+        // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are
+        // buffered (either total or per autofillid). For
+        // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer
+        // "a" and "b" then send "abc".
+        final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
+
+        if (bufferEvent && !forceFlush) {
+            handleScheduleFlush();
+            return;
+        }
+
+        if (mState != STATE_ACTIVE) {
+            // Callback from startSession hasn't been called yet - typically happens on system
+            // apps that are started before the system service
+            // TODO(b/111276913): try to ignore session while system is not ready / boot
+            // not complete instead. Similarly, the manager service should return right away
+            // when the user does not have a service set
+            if (VERBOSE) {
+                Log.v(TAG, "Closing session for " + getActivityDebugName()
+                        + " after " + numberEvents + " delayed events and state "
+                        + getStateAsString(mState));
+            }
+            handleResetState();
+            // TODO(b/111276913): blacklist activity / use special flag to indicate that
+            // when it's launched again
+            return;
+        }
+
+        handleForceFlush();
+    }
+
+    private void handleScheduleFlush() {
+        if (mHandler.hasMessages(MSG_FLUSH)) {
+            // "Renew" the flush message by removing the previous one
+            mHandler.removeMessages(MSG_FLUSH);
+        }
+        mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
+        if (VERBOSE) {
+            Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
+        }
+        mHandler.sendMessageDelayed(
+                obtainMessage(ContentCaptureSession::handleFlushIfNeeded, this).setWhat(MSG_FLUSH),
+                FLUSHING_FREQUENCY_MS);
+    }
+
+    private void handleFlushIfNeeded() {
+        if (mEvents.isEmpty()) {
+            if (VERBOSE) Log.v(TAG, "Nothing to flush");
+            return;
+        }
+        handleForceFlush();
+    }
+
+    private void handleForceFlush() {
+        if (mEvents == null) return;
+
+        final int numberEvents = mEvents.size();
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
+            }
+            mHandler.removeMessages(MSG_FLUSH);
+            mService.sendEvents(mContext.getUserId(), mId, mEvents);
+            // TODO(b/111276913): decide whether we should clear or set it to null, as each has
+            // its own advantages: clearing will save extra allocations while the session is
+            // active, while setting to null would save memory if there's no more event coming.
+            mEvents.clear();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
+                    + ": " + e);
+        }
+    }
+
+    private void handleDestroySession() {
+        //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
+        // to system_server, so it's ok to call both in sequence here. But once we split
+        // them so the events are sent directly to the service, we need to make sure they're
+        // sent in order.
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+                        + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
+                        + getActivityDebugName());
+            }
+
+            mService.finishSession(mContext.getUserId(), mId, mEvents);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error destroying session " + mId + " for " + getActivityDebugName()
+                    + ": " + e);
+        } finally {
+            handleResetState();
+        }
+    }
+
+    // TODO(b/111276913): once we support multiple sessions, we might need to move some of these
+    // clearings out.
+    private void handleResetState() {
+        mState = STATE_UNKNOWN;
+        mContentCaptureSessionId = null;
+        mApplicationToken = null;
+        mComponentName = null;
+        mEvents = null;
+        mHandler.removeMessages(MSG_FLUSH);
+    }
+
+    /**
+     * Notifies the Content Capture Service that a node has been added to the view structure.
+     *
+     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+     * automatically by the Android System for views that return {@code true} on
+     * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
+     *
+     * @param node node that has been added.
+     */
+    public void notifyViewAppeared(@NonNull ViewStructure node) {
+        Preconditions.checkNotNull(node);
+        if (!isContentCaptureEnabled()) return;
+
+        if (!(node instanceof ViewNode.ViewStructureImpl)) {
+            throw new IllegalArgumentException("Invalid node class: " + node.getClass());
+        }
+
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+                new ContentCaptureEvent(TYPE_VIEW_APPEARED)
+                        .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
+                        /* forceFlush= */ false));
+    }
+
+    /**
+     * Notifies the Content Capture Service that a node has been removed from the view structure.
+     *
+     * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+     * automatically by the Android System for standard views.
+     *
+     * @param id id of the node that has been removed.
+     */
+    public void notifyViewDisappeared(@NonNull AutofillId id) {
+        Preconditions.checkNotNull(id);
+        if (!isContentCaptureEnabled()) return;
+
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+                new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
+                        /* forceFlush= */ false));
+    }
+
+    /**
+     * Notifies the Intelligence Service that the value of a text node has been changed.
+     *
+     * @param id of the node.
+     * @param text new text.
+     * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
+     * changed by the user (for example, through the keyboard).
+     */
+    public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
+            int flags) {
+        Preconditions.checkNotNull(id);
+
+        if (!isContentCaptureEnabled()) return;
+
+        mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+                new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
+                        .setText(text), /* forceFlush= */ false));
+    }
+
+    /**
+     * Creates a {@link ViewStructure} for a "standard" view.
+     *
+     * @hide
+     */
+    @NonNull
+    public ViewStructure newViewStructure(@NonNull View view) {
+        return new ViewNode.ViewStructureImpl(view);
+    }
+
+    /**
+     * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
+     * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
+     *
+     * @param parentId id of the virtual view parent (it can be obtained by calling
+     * {@link ViewStructure#getAutofillId()} on the parent).
+     * @param virtualId id of the virtual child, relative to the parent.
+     *
+     * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
+     *
+     * @hide
+     */
+    @NonNull
+    public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
+        return new ViewNode.ViewStructureImpl(parentId, virtualId);
+    }
+
+    private boolean isContentCaptureEnabled() {
+        return mService != null && !mDisabled.get();
+    }
+
+    void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        pw.print(prefix); pw.print("id: "); pw.println(mId);
+        pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
+        pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
+        if (mService != null) {
+            pw.print(prefix); pw.print("mService: "); pw.println(mService);
+        }
+        if (mClientContext != null) {
+            // NOTE: we don't dump clientContent because it could have PII
+            pw.print(prefix); pw.println("hasClientContext");
+
+        }
+        pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
+        pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
+        if (mContentCaptureSessionId != null) {
+            pw.print(prefix); pw.print("public id: "); pw.println(mContentCaptureSessionId);
+        }
+        pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
+        pw.print(getStateAsString(mState)); pw.println(")");
+        if (mApplicationToken != null) {
+            pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
+        }
+        if (mComponentName != null) {
+            pw.print(prefix); pw.print("component name: ");
+            pw.println(mComponentName.flattenToShortString());
+        }
+        if (mEvents != null && !mEvents.isEmpty()) {
+            final int numberEvents = mEvents.size();
+            pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
+            pw.print('/'); pw.println(MAX_BUFFER_SIZE);
+            if (VERBOSE && numberEvents > 0) {
+                final String prefix3 = prefix + "  ";
+                for (int i = 0; i < numberEvents; i++) {
+                    final ContentCaptureEvent event = mEvents.get(i);
+                    pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
+                    pw.println();
+                }
+            }
+            pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
+            pw.print(prefix); pw.print("next flush: ");
+            TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
+        }
+    }
+
+    /**
+     * Gets a string that can be used to identify the activity on logging statements.
+     */
+    private String getActivityDebugName() {
+        return mComponentName == null ? mContext.getPackageName()
+                : mComponentName.flattenToShortString();
+    }
+
+    @Override
+    public String toString() {
+        return mId;
+    }
+
+    @NonNull
+    private static String getStateAsString(int state) {
+        switch (state) {
+            case STATE_UNKNOWN:
+                return "UNKNOWN";
+            case STATE_WAITING_FOR_SERVER:
+                return "WAITING_FOR_SERVER";
+            case STATE_ACTIVE:
+                return "ACTIVE";
+            case STATE_DISABLED:
+                return "DISABLED";
+            default:
+                return "INVALID:" + state;
+        }
+    }
+}
diff --git a/core/java/android/service/contentcapture/InteractionSessionId.java b/core/java/android/view/contentcapture/ContentCaptureSessionId.java
similarity index 77%
rename from core/java/android/service/contentcapture/InteractionSessionId.java
rename to core/java/android/view/contentcapture/ContentCaptureSessionId.java
index 8411947..d7f9fcc 100644
--- a/core/java/android/service/contentcapture/InteractionSessionId.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSessionId.java
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package android.service.contentcapture;
+package android.view.contentcapture;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,11 +24,8 @@
 
 /**
  * Identifier for a Content Capture session.
- *
- * @hide
  */
-@SystemApi
-public final class InteractionSessionId implements Parcelable {
+public final class ContentCaptureSessionId implements Parcelable {
 
     private final @NonNull String mValue;
 
@@ -40,7 +36,7 @@
      *
      * @hide
      */
-    public InteractionSessionId(@NonNull String value) {
+    public ContentCaptureSessionId(@NonNull String value) {
         mValue = value;
     }
 
@@ -64,7 +60,7 @@
         if (this == obj) return true;
         if (obj == null) return false;
         if (getClass() != obj.getClass()) return false;
-        final InteractionSessionId other = (InteractionSessionId) obj;
+        final ContentCaptureSessionId other = (ContentCaptureSessionId) obj;
         if (mValue == null) {
             if (other.mValue != null) return false;
         } else if (!mValue.equals(other.mValue)) {
@@ -100,17 +96,17 @@
         parcel.writeString(mValue);
     }
 
-    public static final Parcelable.Creator<InteractionSessionId> CREATOR =
-            new Parcelable.Creator<InteractionSessionId>() {
+    public static final Parcelable.Creator<ContentCaptureSessionId> CREATOR =
+            new Parcelable.Creator<ContentCaptureSessionId>() {
 
         @Override
-        public InteractionSessionId createFromParcel(Parcel parcel) {
-            return new InteractionSessionId(parcel.readString());
+        public ContentCaptureSessionId createFromParcel(Parcel parcel) {
+            return new ContentCaptureSessionId(parcel.readString());
         }
 
         @Override
-        public InteractionSessionId[] newArray(int size) {
-            return new InteractionSessionId[size];
+        public ContentCaptureSessionId[] newArray(int size) {
+            return new ContentCaptureSessionId[size];
         }
     };
 }
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 8704dad..2002c5c 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
 package android.view.contentcapture;
 
 import android.content.ComponentName;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.os.IBinder;
 
@@ -29,7 +30,8 @@
  */
 oneway interface IContentCaptureManager {
     void startSession(int userId, IBinder activityToken, in ComponentName componentName,
-                      String sessionId, int flags, in IResultReceiver result);
+                      String sessionId, in ContentCaptureContext clientContext, int flags,
+                      in IResultReceiver result);
     void finishSession(int userId, String sessionId, in List<ContentCaptureEvent> events);
     void sendEvents(int userId, in String sessionId, in List<ContentCaptureEvent> events);
 }
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
new file mode 100644
index 0000000..0261b70
--- /dev/null
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used by apps to request the Content Capture service to remove user-data associated with
+ * some context.
+ */
+public final class UserDataRemovalRequest implements Parcelable {
+
+    private UserDataRemovalRequest(Builder builder) {
+        // TODO(b/111276913): implement
+    }
+
+    /**
+     * Gets the name of the app that's making the request.
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public String getPackageName() {
+        // TODO(b/111276913): implement
+        // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
+        return null;
+    }
+
+    /**
+     * Checks if app is requesting to remove all user data associated with its package.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isForEverything() {
+        // TODO(b/111276913): implement
+        return false;
+    }
+
+    /**
+     * Gets the list of {@code Uri}s the apps is requesting to remove.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public List<UriRequest> getUriRequests() {
+        // TODO(b/111276913): implement
+        return null;
+    }
+
+    /**
+     * Builder for {@link UserDataRemovalRequest} objects.
+     */
+    public static final class Builder {
+
+        /**
+         * Requests servive to remove all user data associated with the app's package.
+         *
+         * @return this builder
+         */
+        @NonNull
+        public Builder forEverything() {
+            // TODO(b/111276913): implement
+            return this;
+        }
+
+        /**
+         * Request service to remove data associated with a given {@link Uri}.
+         *
+         * @param uri URI being requested to be removed.
+         * @param recursive whether it should remove the data associated with just the URI or its
+         * tree of descendants.
+         *
+         * @return this builder
+         */
+        public Builder addUri(@NonNull Uri uri, boolean recursive) {
+            // TODO(b/111276913): implement
+            return this;
+        }
+
+        /**
+         * Builds the {@link UserDataRemovalRequest}.
+         */
+        @NonNull
+        public UserDataRemovalRequest build() {
+            // TODO(b/111276913): implement / unit test / check built / document exceptions
+            return null;
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        // TODO(b/111276913): implement
+    }
+
+    public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
+            new Parcelable.Creator<UserDataRemovalRequest>() {
+
+        @Override
+        public UserDataRemovalRequest createFromParcel(Parcel parcel) {
+            // TODO(b/111276913): implement
+            return null;
+        }
+
+        @Override
+        public UserDataRemovalRequest[] newArray(int size) {
+            return new UserDataRemovalRequest[size];
+        }
+    };
+
+    /**
+     * Representation of a request to remove data associated with an {@link Uri}.
+     * @hide
+     */
+    @SystemApi
+    public final class UriRequest {
+        private final @NonNull Uri mUri;
+        private final boolean mRecursive;
+
+        private UriRequest(@NonNull Uri uri, boolean recursive) {
+            this.mUri = uri;
+            this.mRecursive = recursive;
+        }
+
+        /**
+         * Gets the URI per se.
+         */
+        @NonNull
+        public Uri getUri() {
+            return mUri;
+        }
+
+        /**
+         * Checks whether the request is to remove just the data associated with the URI per se, or
+         * also its descendants.
+         */
+        @NonNull
+        public boolean isRecursive() {
+            return mRecursive;
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 04924c9..3f690f7 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -139,17 +139,21 @@
      */
     public static final String HINT_FOR_NOTIFICATION = "notification";
 
-    private List<ConversationAction> mConversationActions;
+    private final List<ConversationAction> mConversationActions;
+    private final String mId;
 
     /** Constructs a {@link ConversationActions} object. */
-    public ConversationActions(@NonNull List<ConversationAction> conversationActions) {
+    public ConversationActions(
+            @NonNull List<ConversationAction> conversationActions, @Nullable String id) {
         mConversationActions =
                 Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions));
+        mId = id;
     }
 
     private ConversationActions(Parcel in) {
         mConversationActions =
                 Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR));
+        mId = in.readString();
     }
 
     @Override
@@ -160,14 +164,26 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeTypedList(mConversationActions);
+        parcel.writeString(mId);
     }
 
-    /** Returns an immutable list of {@link ConversationAction} objects. */
+    /**
+     * Returns an immutable list of {@link ConversationAction} objects, which are ordered from high
+     * confidence to low confidence.
+     */
     @NonNull
     public List<ConversationAction> getConversationActions() {
         return mConversationActions;
     }
 
+    /**
+     * Returns the id, if one exists, for this object.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
     /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
     public static final class ConversationAction implements Parcelable {
 
@@ -678,35 +694,37 @@
         private final List<String> mHints;
         @Nullable
         private String mCallingPackageName;
+        @Nullable
+        private final String mConversationId;
 
         private Request(
                 @NonNull List<Message> conversation,
                 @NonNull TypeConfig typeConfig,
                 int maxSuggestions,
+                String conversationId,
                 @Nullable @Hint List<String> hints) {
             mConversation = Preconditions.checkNotNull(conversation);
             mTypeConfig = Preconditions.checkNotNull(typeConfig);
             mMaxSuggestions = maxSuggestions;
+            mConversationId = conversationId;
             mHints = hints;
         }
 
         private static Request readFromParcel(Parcel in) {
             List<Message> conversation = new ArrayList<>();
             in.readParcelableList(conversation, null);
-
             TypeConfig typeConfig = in.readParcelable(null);
-
             int maxSuggestions = in.readInt();
-
+            String conversationId = in.readString();
             List<String> hints = new ArrayList<>();
             in.readStringList(hints);
-
             String callingPackageName = in.readString();
 
             Request request = new Request(
                     conversation,
                     typeConfig,
                     maxSuggestions,
+                    conversationId,
                     hints);
             request.setCallingPackageName(callingPackageName);
             return request;
@@ -717,6 +735,7 @@
             parcel.writeParcelableList(mConversation, flags);
             parcel.writeParcelable(mTypeConfig, flags);
             parcel.writeInt(mMaxSuggestions);
+            parcel.writeString(mConversationId);
             parcel.writeStringList(mHints);
             parcel.writeString(mCallingPackageName);
         }
@@ -759,6 +778,16 @@
             return mMaxSuggestions;
         }
 
+        /**
+         * Return an unique identifier of the conversation that is generating actions for. This
+         * identifier is unique within the calling package only, so use it with
+         * {@link #getCallingPackageName()}.
+         */
+        @Nullable
+        public String getConversationId() {
+            return mConversationId;
+        }
+
         /** Returns an immutable list of hints */
         @Nullable
         @Hint
@@ -794,6 +823,8 @@
             private TypeConfig mTypeConfig;
             private int mMaxSuggestions;
             @Nullable
+            private String mConversationId;
+            @Nullable
             @Hint
             private List<String> mHints;
 
@@ -823,7 +854,8 @@
                 return this;
             }
 
-            /** Sets the maximum number of suggestions you want.
+            /**
+             * Sets the maximum number of suggestions you want.
              * <p>
              * Value 0 means no restriction.
              */
@@ -833,6 +865,15 @@
                 return this;
             }
 
+            /**
+             * Sets an unique identifier of the conversation that is generating actions for.
+             */
+            @NonNull
+            public Builder setConversationId(@Nullable String conversationId) {
+                mConversationId = conversationId;
+                return this;
+            }
+
             /** Builds the {@link Request} object. */
             @NonNull
             public Request build() {
@@ -840,6 +881,7 @@
                         Collections.unmodifiableList(mConversation),
                         mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig,
                         mMaxSuggestions,
+                        mConversationId,
                         mHints == null
                                 ? Collections.emptyList()
                                 : Collections.unmodifiableList(mHints));
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index c24489c..8b370f5 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -147,6 +147,18 @@
     }
 
     @Override
+    public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
+        Preconditions.checkNotNull(event);
+        Utils.checkMainThread();
+
+        try {
+            mManagerService.onTextClassifierEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
+        }
+    }
+
+    @Override
     public TextLanguage detectLanguage(TextLanguage.Request request) {
         Preconditions.checkNotNull(request);
         Utils.checkMainThread();
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ea82bf3..8709e09 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -127,32 +127,34 @@
     @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
             WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
             WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
-            WIDGET_TYPE_UNKNOWN})
+            WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
     @interface WidgetType {}
 
-    /** The widget involved in the text classification session is a standard
+    /** The widget involved in the text classification context is a standard
      * {@link android.widget.TextView}. */
     String WIDGET_TYPE_TEXTVIEW = "textview";
-    /** The widget involved in the text classification session is a standard
+    /** The widget involved in the text classification context is a standard
      * {@link android.widget.EditText}. */
     String WIDGET_TYPE_EDITTEXT = "edittext";
-    /** The widget involved in the text classification session is a standard non-selectable
+    /** The widget involved in the text classification context is a standard non-selectable
      * {@link android.widget.TextView}. */
     String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
-    /** The widget involved in the text classification session is a standard
+    /** The widget involved in the text classification context is a standard
      * {@link android.webkit.WebView}. */
     String WIDGET_TYPE_WEBVIEW = "webview";
-    /** The widget involved in the text classification session is a standard editable
+    /** The widget involved in the text classification context is a standard editable
      * {@link android.webkit.WebView}. */
     String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
-    /** The widget involved in the text classification session is a custom text widget. */
+    /** The widget involved in the text classification context is a custom text widget. */
     String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
-    /** The widget involved in the text classification session is a custom editable text widget. */
+    /** The widget involved in the text classification context is a custom editable text widget. */
     String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
-    /** The widget involved in the text classification session is a custom non-selectable text
+    /** The widget involved in the text classification context is a custom non-selectable text
      * widget. */
     String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
-    /** The widget involved in the text classification session is of an unknown/unspecified type. */
+    /** The widget involved in the text classification context is a notification */
+    String WIDGET_TYPE_NOTIFICATION = "notification";
+    /** The widget involved in the text classification context is of an unknown/unspecified type. */
     String WIDGET_TYPE_UNKNOWN = "unknown";
 
     /**
@@ -349,16 +351,30 @@
             @NonNull ConversationActions.Request request) {
         Preconditions.checkNotNull(request);
         Utils.checkMainThread();
-        return new ConversationActions(Collections.emptyList());
+        return new ConversationActions(Collections.emptyList(), null);
     }
 
     /**
+     * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
+     * <p>
      * Reports a selection event.
      *
      * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
      * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
      */
-    default void onSelectionEvent(@NonNull SelectionEvent event) {}
+    default void onSelectionEvent(@NonNull SelectionEvent event) {
+        // TODO: Consider rerouting to onTextClassifierEvent()
+    }
+
+    /**
+     * Reports a text classifier event.
+     * <p>
+     * <strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @throws IllegalStateException if this TextClassifier has been destroyed.
+     * @see #isDestroyed()
+     */
+    default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
 
     /**
      * Destroys this TextClassifier.
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/core/java/android/view/textclassifier/TextClassifierEvent.aidl
similarity index 73%
copy from core/java/android/service/contentcapture/InteractionContext.aidl
copy to core/java/android/view/textclassifier/TextClassifierEvent.aidl
index 982e095..86fa4f8 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.contentcapture;
+package android.view.textclassifier;
 
-parcelable InteractionContext;
+parcelable TextClassifierEvent;
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
new file mode 100644
index 0000000..3bb9ee8
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 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.view.textclassifier;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A text classifier event.
+ */
+// TODO: Comprehensive javadoc.
+public final class TextClassifierEvent implements Parcelable {
+
+    public static final Creator<TextClassifierEvent> CREATOR = new Creator<TextClassifierEvent>() {
+        @Override
+        public TextClassifierEvent createFromParcel(Parcel in) {
+            return readFromParcel(in);
+        }
+
+        @Override
+        public TextClassifierEvent[] newArray(int size) {
+            return new TextClassifierEvent[size];
+        }
+    };
+
+    /** @hide **/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({CATEGORY_UNDEFINED, CATEGORY_SELECTION, CATEGORY_LINKIFY,
+            CATEGORY_CONVERSATION_ACTIONS, CATEGORY_LANGUAGE_DETECTION})
+    public @interface Category {
+        // For custom event categories, use range 1000+.
+    }
+    /** Undefined category */
+    public static final int CATEGORY_UNDEFINED = 0;
+    /** Smart selection */
+    public static final int CATEGORY_SELECTION = 1;
+    /** Linkify */
+    public static final int CATEGORY_LINKIFY = 2;
+    /** Conversation actions */
+    public static final int CATEGORY_CONVERSATION_ACTIONS = 3;
+    /** Language detection */
+    public static final int CATEGORY_LANGUAGE_DETECTION = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_UNDEFINED, TYPE_SELECTION_STARTED, TYPE_SELECTION_MODIFIED,
+             TYPE_SMART_SELECTION_SINGLE, TYPE_SMART_SELECTION_MULTI, TYPE_AUTO_SELECTION,
+             TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
+             TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
+             TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
+             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY})
+    public @interface Type {
+        // For custom event types, use range 1,000,000+.
+    }
+    /** User started a new selection. */
+    public static final int TYPE_UNDEFINED = 0;
+    /** User started a new selection. */
+    public static final int TYPE_SELECTION_STARTED = 1;
+    /** User modified an existing selection. */
+    public static final int TYPE_SELECTION_MODIFIED = 2;
+    /** Smart selection triggered for a single token (word). */
+    public static final int TYPE_SMART_SELECTION_SINGLE = 3;
+    /** Smart selection triggered spanning multiple tokens (words). */
+    public static final int TYPE_SMART_SELECTION_MULTI = 4;
+    /** Something else other than user or the default TextClassifier triggered a selection. */
+    public static final int TYPE_AUTO_SELECTION = 5;
+    /** Smart actions shown to the user. */
+    public static final int TYPE_ACTIONS_SHOWN = 6;
+    /** User clicked a link. */
+    public static final int TYPE_LINK_CLICKED = 7;
+    /** User typed over the selection. */
+    public static final int TYPE_OVERTYPE = 8;
+    /** User clicked on Copy action. */
+    public static final int TYPE_COPY_ACTION = 9;
+    /** User clicked on Paste action. */
+    public static final int TYPE_PASTE_ACTION = 10;
+    /** User clicked on Cut action. */
+    public static final int TYPE_CUT_ACTION = 11;
+    /** User clicked on Share action. */
+    public static final int TYPE_SHARE_ACTION = 12;
+    /** User clicked on a Smart action. */
+    public static final int TYPE_SMART_ACTION = 13;
+    /** User dragged+dropped the selection. */
+    public static final int TYPE_SELECTION_DRAG = 14;
+    /** Selection is destroyed. */
+    public static final int TYPE_SELECTION_DESTROYED = 15;
+    /** User clicked on a custom action. */
+    public static final int TYPE_OTHER_ACTION = 16;
+    /** User clicked on Select All action */
+    public static final int TYPE_SELECT_ALL = 17;
+    /** User reset the smart selection. */
+    public static final int TYPE_SELECTION_RESET = 18;
+    /** User composed a reply. */
+    public static final int TYPE_MANUAL_REPLY = 19;
+
+    @Category private final int mEventCategory;
+    @Type private final int mEventType;
+    @Nullable private final String mEntityType;
+    @Nullable private final TextClassificationContext mEventContext;
+    @Nullable private final String mResultId;
+    private final int mEventIndex;
+    private final long mEventTime;
+    private final Bundle mExtras;
+
+    // Smart selection.
+    private final int mRelativeWordStartIndex;
+    private final int mRelativeWordEndIndex;
+    private final int mRelativeSuggestedWordStartIndex;
+    private final int mRelativeSuggestedWordEndIndex;
+
+    // Smart action.
+    private final int[] mActionIndices;
+
+    // Language detection.
+    @Nullable private final String mLanguage;
+
+    private TextClassifierEvent(
+            int eventCategory,
+            int eventType,
+            String entityType,
+            TextClassificationContext eventContext,
+            String resultId,
+            int eventIndex,
+            long eventTime,
+            Bundle extras,
+            int relativeWordStartIndex,
+            int relativeWordEndIndex,
+            int relativeSuggestedWordStartIndex,
+            int relativeSuggestedWordEndIndex,
+            int[] actionIndex,
+            String language) {
+        mEventCategory = eventCategory;
+        mEventType = eventType;
+        mEntityType = entityType;
+        mEventContext = eventContext;
+        mResultId = resultId;
+        mEventIndex = eventIndex;
+        mEventTime = eventTime;
+        mExtras = extras;
+        mRelativeWordStartIndex = relativeWordStartIndex;
+        mRelativeWordEndIndex = relativeWordEndIndex;
+        mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex;
+        mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
+        mActionIndices = actionIndex;
+        mLanguage = language;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mEventCategory);
+        dest.writeInt(mEventType);
+        dest.writeString(mEntityType);
+        dest.writeParcelable(mEventContext, flags);
+        dest.writeString(mResultId);
+        dest.writeInt(mEventIndex);
+        dest.writeLong(mEventTime);
+        dest.writeBundle(mExtras);
+        dest.writeInt(mRelativeWordStartIndex);
+        dest.writeInt(mRelativeWordEndIndex);
+        dest.writeInt(mRelativeSuggestedWordStartIndex);
+        dest.writeInt(mRelativeSuggestedWordEndIndex);
+        dest.writeIntArray(mActionIndices);
+        dest.writeString(mLanguage);
+    }
+
+    private static TextClassifierEvent readFromParcel(Parcel in) {
+        return new TextClassifierEvent(
+                /* eventCategory= */ in.readInt(),
+                /* eventType= */ in.readInt(),
+                /* entityType= */ in.readString(),
+                /* eventContext= */ in.readParcelable(null),
+                /* resultId= */ in.readString(),
+                /* eventIndex= */ in.readInt(),
+                /* eventTime= */ in.readLong(),
+                /* extras= */ in.readBundle(),
+                /* relativeWordStartIndex= */ in.readInt(),
+                /* relativeWordEndIndex= */ in.readInt(),
+                /* relativeSuggestedWordStartIndex= */ in.readInt(),
+                /* relativeSuggestedWordEndIndex= */ in.readInt(),
+                /* actionIndices= */ in.createIntArray(),
+                /* language= */ in.readString());
+    }
+
+    /**
+     * Returns the event category. e.g. {@link #CATEGORY_SELECTION}.
+     */
+    @Category
+    public int getEventCategory() {
+        return mEventCategory;
+    }
+
+    /**
+     * Returns the event type. e.g. {@link #TYPE_SELECTION_STARTED}.
+     */
+    @Type
+    public int getEventType() {
+        return mEventType;
+    }
+
+    /**
+     * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+     */
+    @Nullable
+    public String getEntityType() {
+        return mEntityType;
+    }
+
+    /**
+     * Returns the event context.
+     */
+    @Nullable
+    public TextClassificationContext getEventContext() {
+        return mEventContext;
+    }
+
+    /**
+     * Returns the id of the text classifier result related to this event.
+     */
+    @Nullable
+    public String getResultId() {
+        return mResultId;
+    }
+
+    /**
+     * Returns the index of this event in the series of event it belongs to.
+     */
+    public int getEventIndex() {
+        return mEventIndex;
+    }
+
+    /**
+     * Returns the time this event occurred. This is the number of milliseconds since
+     * January 1, 1970, 00:00:00 GMT. 0 indicates not set.
+     */
+    public long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Returns a bundle containing non-structured extra information about this event.
+     *
+     * <p><b>NOTE: </b>Do not modify this bundle.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * For smart selection. Returns the relative word index of the start of the selection.
+     */
+    public int getRelativeWordStartIndex() {
+        return mRelativeWordStartIndex;
+    }
+
+    /**
+     * For smart selection. Returns the relative word (exclusive) index of the end of the selection.
+     */
+    public int getRelativeWordEndIndex() {
+        return mRelativeWordEndIndex;
+    }
+
+    /**
+     * For smart selection. Returns the relative word index of the start of the smart selection.
+     */
+    public int getRelativeSuggestedWordStartIndex() {
+        return mRelativeSuggestedWordStartIndex;
+    }
+
+    /**
+     * For smart selection. Returns the relative word (exclusive) index of the end of the
+     * smart selection.
+     */
+    public int getRelativeSuggestedWordEndIndex() {
+        return mRelativeSuggestedWordEndIndex;
+    }
+
+    /**
+     * Returns the indices of the actions relating to this event.
+     * Actions are usually returned by the text classifier in priority order with the most
+     * preferred action at index 0. This list gives an indication of the position of the actions
+     * that are being reported.
+     */
+    @NonNull
+    public int[] getActionIndices() {
+        return mActionIndices;
+    }
+
+    /**
+     * For language detection. Returns the language tag for the detected locale.
+     * @see java.util.Locale#forLanguageTag(String).
+     */
+    @Nullable
+    public String getLanguage() {
+        return mLanguage;
+    }
+
+    /**
+     * Builder to build a text classifier event.
+     */
+    public static final class Builder {
+
+        private final int mEventCategory;
+        private final int mEventType;
+        @Nullable private String mEntityType;
+        @Nullable private TextClassificationContext mEventContext;
+        @Nullable private String mResultId;
+        private int mEventIndex;
+        private long mEventTime;
+        @Nullable private Bundle mExtras;
+        private int mRelativeWordStartIndex;
+        private int mRelativeWordEndIndex;
+        private int mRelativeSuggestedWordStartIndex;
+        private int mRelativeSuggestedWordEndIndex;
+        private int[] mActionIndices = new int[0];
+        @Nullable private String mLanguage;
+
+        /**
+         * Creates a builder for building {@link TextClassifierEvent}s.
+         *
+         * @param eventCategory The event category. e.g. {@link #CATEGORY_SELECTION}
+         * @param eventType The event type. e.g. {@link #TYPE_SELECTION_STARTED}
+         */
+        public Builder(@Category int eventCategory, @Type int eventType) {
+            mEventCategory = eventCategory;
+            mEventType = eventType;
+        }
+
+        /**
+         * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+         */
+        @NonNull
+        public Builder setEntityType(@Nullable String entityType) {
+            mEntityType = entityType;
+            return this;
+        }
+
+        /**
+         * Sets the event context.
+         */
+        @NonNull
+        public Builder setEventContext(@Nullable TextClassificationContext eventContext) {
+            mEventContext = eventContext;
+            return this;
+        }
+
+        /**
+         * Sets the id of the text classifier result related to this event.
+         */
+        @NonNull
+        public Builder setResultId(@Nullable String resultId) {
+            mResultId = resultId;
+            return this;
+        }
+
+        /**
+         * Sets the index of this events in the series of events it belongs to.
+         */
+        @NonNull
+        public Builder setEventIndex(int eventIndex) {
+            mEventIndex = eventIndex;
+            return this;
+        }
+
+        /**
+         * Sets the time this event occurred. This is the number of milliseconds since
+         * January 1, 1970, 00:00:00 GMT. 0 indicates not set.
+         */
+        @NonNull
+        public Builder setEventTime(long eventTime) {
+            mEventTime = eventTime;
+            return this;
+        }
+
+        /**
+         * Sets a bundle containing non-structured extra information about the event.
+         *
+         * <p><b>NOTE: </b>Prefer to set only immutable values on the bundle otherwise, avoid
+         * updating the internals of this bundle as it may have unexpected consequences on the
+         * clients of the built event object. For similar reasons, avoid depending on mutable
+         * objects in this bundle.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = Preconditions.checkNotNull(extras);
+            return this;
+        }
+
+        /**
+         * For smart selection. Sets the relative word index of the start of the selection.
+         */
+        @NonNull
+        public Builder setRelativeWordStartIndex(int relativeWordStartIndex) {
+            mRelativeWordStartIndex = relativeWordStartIndex;
+            return this;
+        }
+
+        /**
+         * For smart selection. Sets the relative word (exclusive) index of the end of the
+         * selection.
+         */
+        @NonNull
+        public Builder setRelativeWordEndIndex(int relativeWordEndIndex) {
+            mRelativeWordEndIndex = relativeWordEndIndex;
+            return this;
+        }
+
+        /**
+         * For smart selection. Sets the relative word index of the start of the smart selection.
+         */
+        @NonNull
+        public Builder setRelativeSuggestedWordStartIndex(int relativeSuggestedWordStartIndex) {
+            mRelativeSuggestedWordStartIndex = relativeSuggestedWordStartIndex;
+            return this;
+        }
+
+        /**
+         * For smart selection. Sets the relative word (exclusive) index of the end of the
+         * smart selection.
+         */
+        @NonNull
+        public Builder setRelativeSuggestedWordEndIndex(int relativeSuggestedWordEndIndex) {
+            mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
+            return this;
+        }
+
+        /**
+         * Sets the indices of the actions involved in this event. Actions are usually returned by
+         * the text classifier in priority order with the most preferred action at index 0.
+         * This index gives an indication of the position of the action that is being reported.
+         */
+        @NonNull
+        public Builder setActionIndices(@NonNull int... actionIndices) {
+            mActionIndices = new int[actionIndices.length];
+            System.arraycopy(actionIndices, 0, mActionIndices, 0, actionIndices.length);
+            return this;
+        }
+
+        /**
+         * For language detection. Sets the language tag for the detected locale.
+         * @see java.util.Locale#forLanguageTag(String).
+         */
+        @NonNull
+        public Builder setLanguage(@Nullable String language) {
+            mLanguage = language;
+            return this;
+        }
+
+        /**
+         * Builds and returns a text classifier event.
+         */
+        @NonNull
+        public TextClassifierEvent build() {
+            mExtras = mExtras == null ? Bundle.EMPTY : mExtras;
+            return new TextClassifierEvent(
+                    mEventCategory,
+                    mEventType,
+                    mEntityType,
+                    mEventContext,
+                    mResultId,
+                    mEventIndex,
+                    mEventTime,
+                    mExtras,
+                    mRelativeWordStartIndex,
+                    mRelativeWordEndIndex,
+                    mRelativeSuggestedWordStartIndex,
+                    mRelativeSuggestedWordEndIndex,
+                    mActionIndices,
+                    mLanguage);
+        }
+        // TODO: Add build(boolean validate).
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index deda926..9b0f9c6 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -341,6 +341,11 @@
         }
     }
 
+    @Override
+    public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
+        // TODO: Implement.
+    }
+
     /** @inheritDoc */
     @Override
     public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
@@ -387,7 +392,10 @@
 
             Collection<String> expectedTypes = resolveActionTypesFromRequest(request);
             List<ConversationActions.ConversationAction> conversationActions = new ArrayList<>();
-            int maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length);
+            int maxSuggestions = nativeSuggestions.length;
+            if (request.getMaxSuggestions() > 0) {
+                maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length);
+            }
             for (int i = 0; i < maxSuggestions; i++) {
                 ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = nativeSuggestions[i];
                 String actionType = nativeSuggestion.getActionType();
@@ -400,7 +408,7 @@
                                 .setConfidenceScore(nativeSuggestion.getScore())
                                 .build());
             }
-            return new ConversationActions(conversationActions);
+            return new ConversationActions(conversationActions, /*id*/ null);
         } catch (Throwable t) {
             // Avoid throwing from this method. Log the error.
             Log.e(LOG_TAG, "Error suggesting conversation actions.", t);
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index aae3056..e66596b 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -41,13 +41,21 @@
     private InputStream mInputStream;
 
     /**
-     * Constructs a resource response with the given MIME type, encoding, and
-     * input stream. Callers must implement
+     * Constructs a resource response with the given MIME type, character encoding,
+     * and input stream. Callers must implement
      * {@link InputStream#read(byte[]) InputStream.read(byte[])} for the input
      * stream.
      *
-     * @param mimeType the resource response's MIME type, for example text/html
-     * @param encoding the resource response's encoding
+     * <p class="note"><b>Note:</b> The MIME type and character encoding must
+     * be specified as separate parameters (for example {@code "text/html"} and
+     * {@code "utf-8"}), not a single value like the {@code "text/html; charset=utf-8"}
+     * format used in the HTTP Content-Type header. Do not use the value of a HTTP
+     * Content-Encoding header for {@code encoding}, as that header does not specify a
+     * character encoding. Content without a defined character encoding (for example
+     * image resources) should pass {@code null} for {@code encoding}.
+     *
+     * @param mimeType the resource response's MIME type, for example {@code "text/html"}.
+     * @param encoding the resource response's character encoding, for example {@code "utf-8"}.
      * @param data the input stream that provides the resource response's data. Must not be a
      *             StringBufferInputStream.
      */
@@ -63,8 +71,11 @@
      * implement {@link InputStream#read(byte[]) InputStream.read(byte[])} for
      * the input stream.
      *
-     * @param mimeType the resource response's MIME type, for example text/html
-     * @param encoding the resource response's encoding
+     * <p class="note"><b>Note:</b> See {@link #WebResourceResponse(String,String,InputStream)}
+     * for details on what should be specified for {@code mimeType} and {@code encoding}.
+     *
+     * @param mimeType the resource response's MIME type, for example {@code "text/html"}.
+     * @param encoding the resource response's character encoding, for example {@code "utf-8"}.
      * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
      *                   Causing a redirect by specifying a 3xx code is not supported.
      * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 90da812..b5cdedc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -158,6 +158,7 @@
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -719,7 +720,7 @@
     @ViewDebug.ExportedProperty(category = "text")
     @UnsupportedAppUsage
     private int mGravity = Gravity.TOP | Gravity.START;
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private boolean mHorizontallyScrolling;
 
     private int mAutoLinkMask;
@@ -5095,13 +5096,24 @@
     }
 
     /**
-     * Returns whether the text is allowed to be wider than the View is.
+     * Returns whether the text is allowed to be wider than the View.
+     * If false, the text will be wrapped to the width of the View.
+     *
+     * @attr ref android.R.styleable#TextView_scrollHorizontally
+     * @see #setHorizontallyScrolling(boolean)
+     */
+    public final boolean isHorizontallyScrolling() {
+        return mHorizontallyScrolling;
+    }
+
+    /**
+     * Returns whether the text is allowed to be wider than the View.
      * If false, the text will be wrapped to the width of the View.
      *
      * @attr ref android.R.styleable#TextView_scrollHorizontally
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public boolean getHorizontallyScrolling() {
         return mHorizontallyScrolling;
     }
@@ -10216,12 +10228,19 @@
             }
         }
 
+        // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
+        // of using isLaidout(), so it's not called in cases where it's laid out but a
+        // notifyAppeared was not sent.
+
         // ContentCapture
         if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
             final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
             if (cm != null && cm.isContentCaptureEnabled()) {
-                // TODO(b/111276913): pass flags when edited by user / add CTS test
-                cm.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0);
+                final ContentCaptureSession session = getContentCaptureSession();
+                if (session != null) {
+                    // TODO(b/111276913): pass flags when edited by user / add CTS test
+                    session.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0);
+                }
             }
         }
     }
@@ -10812,6 +10831,25 @@
                         return onTextContextMenuItem(ID_PASTE);
                     }
                     break;
+                case KeyEvent.KEYCODE_INSERT:
+                    if (canCopy()) {
+                        return onTextContextMenuItem(ID_COPY);
+                    }
+                    break;
+            }
+        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+            // Handle Shift-only shortcuts.
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_FORWARD_DEL:
+                    if (canCut()) {
+                        return onTextContextMenuItem(ID_CUT);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_INSERT:
+                    if (canPaste()) {
+                        return onTextContextMenuItem(ID_PASTE);
+                    }
+                    break;
             }
         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
             // Handle Ctrl-Shift shortcuts.
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 3842f66..4da3391 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -445,8 +445,18 @@
         }
     }
 
+    public boolean hasProcess(String procName) {
+        final int NSRC = mSources.size();
+        for (int isrc = 0; isrc < NSRC; isrc++) {
+            if (mSources.keyAt(isrc).mProcess.equals(procName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
-            long now, long totalTime, boolean dumpDetails, boolean dumpAll) {
+            long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) {
         if (dumpAll) {
             pw.print(prefix);
             pw.print("mNumActive=");
@@ -456,6 +466,9 @@
         for (int isrc = 0; isrc < NSRC; isrc++) {
             final SourceKey key = mSources.keyAt(isrc);
             final SourceState src = mSources.valueAt(isrc);
+            if (reqPackage != null && !reqPackage.equals(key.mProcess)) {
+                continue;
+            }
             pw.print(prefixInner);
             pw.print("<- ");
             pw.print(key.mProcess);
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 54aa533..9ee583a 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -1475,6 +1475,7 @@
                         final int NSRVS = pkgState.mServices.size();
                         final int NASCS = pkgState.mAssociations.size();
                         final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+                        boolean onlyAssociations = false;
                         if (!pkgMatch) {
                             boolean procMatch = false;
                             for (int iproc = 0; iproc < NPROCS; iproc++) {
@@ -1485,7 +1486,18 @@
                                 }
                             }
                             if (!procMatch) {
-                                continue;
+                                // Check if this app has any associations with the requested
+                                // package, so that if so we print those.
+                                for (int iasc = 0; iasc < NASCS; iasc++) {
+                                    AssociationState asc = pkgState.mAssociations.valueAt(iasc);
+                                    if (asc.hasProcess(reqPackage)) {
+                                        onlyAssociations = true;
+                                        break;
+                                    }
+                                }
+                                if (!onlyAssociations) {
+                                    continue;
+                                }
                             }
                         }
                         if (NPROCS > 0 || NSRVS > 0 || NASCS > 0) {
@@ -1503,7 +1515,7 @@
                             pw.print(vers);
                             pw.println(":");
                         }
-                        if ((section & REPORT_PKG_PROC_STATS) != 0) {
+                        if ((section & REPORT_PKG_PROC_STATS) != 0 && !onlyAssociations) {
                             if (!dumpSummary || dumpAll) {
                                 for (int iproc = 0; iproc < NPROCS; iproc++) {
                                     ProcessState proc = pkgState.mProcesses.valueAt(iproc);
@@ -1550,7 +1562,7 @@
                                         now, totalTime);
                             }
                         }
-                        if ((section & REPORT_PKG_SVC_STATS) != 0) {
+                        if ((section & REPORT_PKG_SVC_STATS) != 0 && !onlyAssociations) {
                             for (int isvc = 0; isvc < NSRVS; isvc++) {
                                 ServiceState svc = pkgState.mServices.valueAt(isvc);
                                 if (!pkgMatch && !reqPackage.equals(svc.getProcessName())) {
@@ -1579,7 +1591,9 @@
                             for (int iasc = 0; iasc < NASCS; iasc++) {
                                 AssociationState asc = pkgState.mAssociations.valueAt(iasc);
                                 if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) {
-                                    continue;
+                                    if (!onlyAssociations || !asc.hasProcess(reqPackage)) {
+                                        continue;
+                                    }
                                 }
                                 if (activeOnly && !asc.isInUse()) {
                                     pw.print("      (Not active association: ");
@@ -1597,7 +1611,8 @@
                                 pw.print("        Process: ");
                                 pw.println(asc.getProcessName());
                                 asc.dumpStats(pw, "        ", "          ", "    ",
-                                        now, totalTime, dumpDetails, dumpAll);
+                                        now, totalTime, onlyAssociations ? reqPackage : null,
+                                        dumpDetails, dumpAll);
                             }
                         }
                     }
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 5465485..051a96c 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -80,7 +80,8 @@
     private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
     private final Object mLock = new Object();
     private final Random mRandom;
-    private long mStartTime = System.currentTimeMillis();
+    private long mStartCurrentTime = System.currentTimeMillis();
+    private long mStartElapsedTime = SystemClock.elapsedRealtime();
     private long mCallStatsCount = 0;
     private boolean mAddDebugEntries = false;
 
@@ -329,8 +330,8 @@
 
         // Debug entries added to help validate the data.
         if (mAddDebugEntries && mBatteryStopwatch != null) {
-            resultCallStats.add(createDebugEntry("start_time_millis", mStartTime));
-            resultCallStats.add(createDebugEntry("end_time_millis", System.currentTimeMillis()));
+            resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+            resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
             resultCallStats.add(
                     createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
         }
@@ -370,7 +371,7 @@
         long totalRecordedCallsCount = 0;
         long totalCpuTime = 0;
         pw.print("Start time: ");
-        pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
+        pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
         pw.print("On battery time (ms): ");
         pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
         pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
@@ -520,7 +521,8 @@
             mCallStatsCount = 0;
             mUidEntries.clear();
             mExceptionCounts.clear();
-            mStartTime = System.currentTimeMillis();
+            mStartCurrentTime = System.currentTimeMillis();
+            mStartElapsedTime = SystemClock.elapsedRealtime();
             if (mBatteryStopwatch != null) {
                 mBatteryStopwatch.reset();
             }
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 01fd8ba..9a7fb9f 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -50,7 +50,8 @@
     private int mSamplingInterval;
     private CachedDeviceState.Readonly mDeviceState;
     private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
-    private long mStartTime = System.currentTimeMillis();
+    private long mStartCurrentTime = System.currentTimeMillis();
+    private long mStartElapsedTime = SystemClock.elapsedRealtime();
     private boolean mAddDebugEntries = false;
 
     public LooperStats(int samplingInterval, int entriesSizeCap) {
@@ -155,8 +156,8 @@
         maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry);
         // Debug entries added to help validate the data.
         if (mAddDebugEntries && mBatteryStopwatch != null) {
-            exportedEntries.add(createDebugEntry("start_time_millis", mStartTime));
-            exportedEntries.add(createDebugEntry("end_time_millis", System.currentTimeMillis()));
+            exportedEntries.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+            exportedEntries.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
             exportedEntries.add(
                     createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
         }
@@ -173,7 +174,11 @@
 
     /** Returns a timestamp indicating when the statistics were last reset. */
     public long getStartTimeMillis() {
-        return mStartTime;
+        return mStartCurrentTime;
+    }
+
+    public long getStartElapsedTimeMillis() {
+        return mStartElapsedTime;
     }
 
     public long getBatteryTimeMillis() {
@@ -199,7 +204,8 @@
         synchronized (mOverflowEntry) {
             mOverflowEntry.reset();
         }
-        mStartTime = System.currentTimeMillis();
+        mStartCurrentTime = System.currentTimeMillis();
+        mStartElapsedTime = SystemClock.elapsedRealtime();
         if (mBatteryStopwatch != null) {
             mBatteryStopwatch.reset();
         }
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f669e94..226b8db 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -24,6 +24,7 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.File;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -42,6 +43,8 @@
     private static final int CACHE_SIZE = 73;
     private static Object[] sCache = new Object[CACHE_SIZE];
 
+    public static final File[] EMPTY_FILE = new File[0];
+
     private ArrayUtils() { /* cannot be instantiated */ }
 
     public static byte[] newUnpaddedByteArray(int minLen) {
@@ -645,6 +648,10 @@
         return (val != null) ? val : EmptyArray.STRING;
     }
 
+    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+        return (val != null) ? val : EMPTY_FILE;
+    }
+
     /**
      * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
      *
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 151901b..470533f2 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -326,4 +326,8 @@
             throw ExceptionUtils.propagate(e);
         }
     }
+
+    public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
+        return (val != null) ? val : Collections.emptyList();
+    }
 }
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c8834a8..ae5c67d 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -26,9 +26,9 @@
 import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.IWindowSession;
-import android.view.PointerIcon;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.PointerIcon;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -76,7 +76,7 @@
     }
 
     @Override
-    public void windowFocusChanged(boolean hasFocus, boolean touchEnabled, boolean reportToClient) {
+    public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
     }
 
     @Override
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 2e674a5..841e5b6 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -67,6 +67,7 @@
     private static final int ALLOW_PRIVAPP_PERMISSIONS = 0x10;
     private static final int ALLOW_OEM_PERMISSIONS = 0x20;
     private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x40;
+    private static final int ALLOW_ASSOCIATIONS = 0x80;
     private static final int ALLOW_ALL = ~0;
 
     // property for runtime configuration differentiation
@@ -195,6 +196,12 @@
 
     final ArrayMap<String, ArrayMap<String, Boolean>> mOemPermissions = new ArrayMap<>();
 
+    // Allowed associations between applications.  If there are any entries
+    // for an app, those are the only associations allowed; otherwise, all associations
+    // are allowed.  Allowing an association from app A to app B means app A can not
+    // associate with any other apps, but does not limit what apps B can associate with.
+    final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>();
+
     public static SystemConfig getInstance() {
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
@@ -320,6 +327,10 @@
         return Collections.emptyMap();
     }
 
+    public ArrayMap<String, ArraySet<String>> getAllowedAssociations() {
+        return mAllowedAssociations;
+    }
+
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
@@ -329,8 +340,9 @@
         readPermissions(Environment.buildPath(
                 Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
 
-        // Vendors are only allowed to customze libs, features and privapp permissions
-        int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
+        // Vendors are only allowed to customize these
+        int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
+                | ALLOW_ASSOCIATIONS;
         if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) {
             // For backward compatibility
             vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -359,8 +371,8 @@
                     odmPermissionFlag);
         }
 
-        // Allow OEM to customize features and OEM permissions
-        int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS;
+        // Allow OEM to customize these
+        int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS;
         readPermissions(Environment.buildPath(
                 Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
         readPermissions(Environment.buildPath(
@@ -423,6 +435,11 @@
         }
     }
 
+    private void logNotAllowedInPartition(String name, File permFile, XmlPullParser parser) {
+        Slog.w(TAG, "<" + name + "> not allowed in partition of "
+                + permFile + " at " + parser.getPositionDescription());
+    }
+
     private void readPermissionsFromXml(File permFile, int permissionFlag) {
         FileReader permReader = null;
         try {
@@ -453,14 +470,17 @@
                         + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
             }
 
-            boolean allowAll = permissionFlag == ALLOW_ALL;
-            boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
-            boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
-            boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
-            boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
-            boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0;
-            boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
-            boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0;
+            final boolean allowAll = permissionFlag == ALLOW_ALL;
+            final boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
+            final boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
+            final boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
+            final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
+            final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
+                    != 0;
+            final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
+            final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
+                    != 0;
+            final boolean allowAssociations = (permissionFlag & ALLOW_ASSOCIATIONS) != 0;
             while (true) {
                 XmlUtils.nextElement(parser);
                 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -468,297 +488,425 @@
                 }
 
                 String name = parser.getName();
-                if ("group".equals(name) && allowAll) {
-                    String gidStr = parser.getAttributeValue(null, "gid");
-                    if (gidStr != null) {
-                        int gid = android.os.Process.getGidForName(gidStr);
-                        mGlobalGids = appendInt(mGlobalGids, gid);
-                    } else {
-                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    }
-
+                if (name == null) {
                     XmlUtils.skipCurrentTag(parser);
                     continue;
-                } else if ("permission".equals(name) && allowPermissions) {
-                    String perm = parser.getAttributeValue(null, "name");
-                    if (perm == null) {
-                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
-                                + parser.getPositionDescription());
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    perm = perm.intern();
-                    readPermission(parser, perm);
-
-                } else if ("assign-permission".equals(name) && allowPermissions) {
-                    String perm = parser.getAttributeValue(null, "name");
-                    if (perm == null) {
-                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
-                                + parser.getPositionDescription());
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    String uidStr = parser.getAttributeValue(null, "uid");
-                    if (uidStr == null) {
-                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
-                                + parser.getPositionDescription());
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    int uid = Process.getUidForName(uidStr);
-                    if (uid < 0) {
-                        Slog.w(TAG, "<assign-permission> with unknown uid \""
-                                + uidStr + "  in " + permFile + " at "
-                                + parser.getPositionDescription());
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    perm = perm.intern();
-                    ArraySet<String> perms = mSystemPermissions.get(uid);
-                    if (perms == null) {
-                        perms = new ArraySet<String>();
-                        mSystemPermissions.put(uid, perms);
-                    }
-                    perms.add(perm);
-                    XmlUtils.skipCurrentTag(parser);
-
-                } else if ("split-permission".equals(name) && allowPermissions) {
-                    readSplitPermission(parser, permFile);
-                } else if ("library".equals(name) && allowLibs) {
-                    String lname = parser.getAttributeValue(null, "name");
-                    String lfile = parser.getAttributeValue(null, "file");
-                    String ldependency = parser.getAttributeValue(null, "dependency");
-                    if (lname == null) {
-                        Slog.w(TAG, "<library> without name in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else if (lfile == null) {
-                        Slog.w(TAG, "<library> without file in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else {
-                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
-                        SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
-                                ldependency == null ? new String[0] : ldependency.split(":"));
-                        mSharedLibraries.put(lname, entry);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-                } else if ("feature".equals(name) && allowFeatures) {
-                    String fname = parser.getAttributeValue(null, "name");
-                    int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
-                    boolean allowed;
-                    if (!lowRam) {
-                        allowed = true;
-                    } else {
-                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
-                        allowed = !"true".equals(notLowRam);
-                    }
-                    if (fname == null) {
-                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else if (allowed) {
-                        addFeature(fname, fversion);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("unavailable-feature".equals(name) && allowFeatures) {
-                    String fname = parser.getAttributeValue(null, "name");
-                    if (fname == null) {
-                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else {
-                        mUnavailableFeatures.add(fname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
-                                + permFile + " at " + parser.getPositionDescription());
-                    } else {
-                        mAllowInPowerSaveExceptIdle.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("allow-in-power-save".equals(name) && allowAll) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else {
-                        mAllowInPowerSave.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<allow-in-data-usage-save> without package in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mAllowInDataUsageSave.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("allow-unthrottled-location".equals(name) && allowAll) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<allow-unthrottled-location> without package in "
-                            + permFile + " at " + parser.getPositionDescription());
-                    } else {
-                        mAllowUnthrottledLocation.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("allow-implicit-broadcast".equals(name) && allowAll) {
-                    String action = parser.getAttributeValue(null, "action");
-                    if (action == null) {
-                        Slog.w(TAG, "<allow-implicit-broadcast> without action in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mAllowImplicitBroadcasts.add(action);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-
-                } else if ("app-link".equals(name) && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<app-link> without package in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else {
-                        mLinkedApps.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mSystemUserWhitelistedApps.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mSystemUserBlacklistedApps.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    String clsname = parser.getAttributeValue(null, "class");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else if (clsname == null) {
-                        Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
-                    String serviceName = parser.getAttributeValue(null, "service");
-                    if (serviceName == null) {
-                        Slog.w(TAG, "<backup-transport-whitelisted-service> without service in "
-                                + permFile + " at " + parser.getPositionDescription());
-                    } else {
-                        ComponentName cn = ComponentName.unflattenFromString(serviceName);
-                        if (cn == null) {
-                            Slog.w(TAG,
-                                    "<backup-transport-whitelisted-service> with invalid service name "
-                                    + serviceName + " in "+ permFile
-                                    + " at " + parser.getPositionDescription());
-                        } else {
-                            mBackupTransportWhitelist.add(cn);
-                        }
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
-                        && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
-                    if (pkgname == null || carrierPkgname == null) {
-                        Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
-                                + " without package or carrierAppPackage in " + permFile + " at "
-                                + parser.getPositionDescription());
-                    } else {
-                        List<String> associatedPkgs =
-                                mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
-                                        carrierPkgname);
-                        if (associatedPkgs == null) {
-                            associatedPkgs = new ArrayList<>();
-                            mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
-                                    carrierPkgname, associatedPkgs);
-                        }
-                        associatedPkgs.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("disabled-until-used-preinstalled-carrier-app".equals(name)
-                        && allowAppConfigs) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG,
-                                "<disabled-until-used-preinstalled-carrier-app> without "
-                                        + "package in " + permFile + " at "
+                }
+                switch (name) {
+                    case "group": {
+                        if (allowAll) {
+                            String gidStr = parser.getAttributeValue(null, "gid");
+                            if (gidStr != null) {
+                                int gid = android.os.Process.getGidForName(gidStr);
+                                mGlobalGids = appendInt(mGlobalGids, gid);
+                            } else {
+                                Slog.w(TAG, "<" + name + "> without gid in " + permFile + " at "
                                         + parser.getPositionDescription());
-                    } else {
-                        mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) {
-                    // privapp permissions from system, vendor, product and product_services
-                    // partitions are stored separately. This is to prevent xml files in the vendor
-                    // partition from granting permissions to priv apps in the system partition and
-                    // vice versa.
-                    boolean vendor = permFile.toPath().startsWith(
-                            Environment.getVendorDirectory().toPath() + "/")
-                            || permFile.toPath().startsWith(
-                                Environment.getOdmDirectory().toPath() + "/");
-                    boolean product = permFile.toPath().startsWith(
-                            Environment.getProductDirectory().toPath() + "/");
-                    boolean productServices = permFile.toPath().startsWith(
-                            Environment.getProductServicesDirectory().toPath() + "/");
-                    if (vendor) {
-                        readPrivAppPermissions(parser, mVendorPrivAppPermissions,
-                                mVendorPrivAppDenyPermissions);
-                    } else if (product) {
-                        readPrivAppPermissions(parser, mProductPrivAppPermissions,
-                                mProductPrivAppDenyPermissions);
-                    } else if (productServices) {
-                        readPrivAppPermissions(parser, mProductServicesPrivAppPermissions,
-                                mProductServicesPrivAppDenyPermissions);
-                    } else {
-                        readPrivAppPermissions(parser, mPrivAppPermissions,
-                                mPrivAppDenyPermissions);
-                    }
-                } else if ("oem-permissions".equals(name) && allowOemPermissions) {
-                    readOemPermissions(parser);
-                } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
-                    String pkgname = parser.getAttributeValue(null, "package");
-                    if (pkgname == null) {
-                        Slog.w(TAG, "<hidden-api-whitelisted-app> without package in " + permFile
-                                + " at " + parser.getPositionDescription());
-                    } else {
-                        mHiddenApiPackageWhitelist.add(pkgname);
-                    }
-                    XmlUtils.skipCurrentTag(parser);
-                } else {
-                    Slog.w(TAG, "Tag " + name + " is unknown or not allowed in "
-                            + permFile.getParent());
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "permission": {
+                        if (allowPermissions) {
+                            String perm = parser.getAttributeValue(null, "name");
+                            if (perm == null) {
+                                Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+                                        + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            perm = perm.intern();
+                            readPermission(parser, perm);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
+                    case "assign-permission": {
+                        if (allowPermissions) {
+                            String perm = parser.getAttributeValue(null, "name");
+                            if (perm == null) {
+                                Slog.w(TAG, "<" + name + "> without name in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            String uidStr = parser.getAttributeValue(null, "uid");
+                            if (uidStr == null) {
+                                Slog.w(TAG, "<" + name + "> without uid in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            int uid = Process.getUidForName(uidStr);
+                            if (uid < 0) {
+                                Slog.w(TAG, "<" + name + "> with unknown uid \""
+                                        + uidStr + "  in " + permFile + " at "
+                                        + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            perm = perm.intern();
+                            ArraySet<String> perms = mSystemPermissions.get(uid);
+                            if (perms == null) {
+                                perms = new ArraySet<String>();
+                                mSystemPermissions.put(uid, perms);
+                            }
+                            perms.add(perm);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "split-permission": {
+                        if (allowPermissions) {
+                            readSplitPermission(parser, permFile);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
+                    case "library": {
+                        if (allowLibs) {
+                            String lname = parser.getAttributeValue(null, "name");
+                            String lfile = parser.getAttributeValue(null, "file");
+                            String ldependency = parser.getAttributeValue(null, "dependency");
+                            if (lname == null) {
+                                Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+                                        + parser.getPositionDescription());
+                            } else if (lfile == null) {
+                                Slog.w(TAG, "<" + name + "> without file in " + permFile + " at "
+                                        + parser.getPositionDescription());
+                            } else {
+                                //Log.i(TAG, "Got library " + lname + " in " + lfile);
+                                SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
+                                        ldependency == null ? new String[0] : ldependency.split(":"));
+                                mSharedLibraries.put(lname, entry);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "feature": {
+                        if (allowFeatures) {
+                            String fname = parser.getAttributeValue(null, "name");
+                            int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
+                            boolean allowed;
+                            if (!lowRam) {
+                                allowed = true;
+                            } else {
+                                String notLowRam = parser.getAttributeValue(null, "notLowRam");
+                                allowed = !"true".equals(notLowRam);
+                            }
+                            if (fname == null) {
+                                Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+                                        + parser.getPositionDescription());
+                            } else if (allowed) {
+                                addFeature(fname, fversion);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "unavailable-feature": {
+                        if (allowFeatures) {
+                            String fname = parser.getAttributeValue(null, "name");
+                            if (fname == null) {
+                                Slog.w(TAG, "<" + name + "> without name in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                            } else {
+                                mUnavailableFeatures.add(fname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-in-power-save-except-idle": {
+                        if (allowAll) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowInPowerSaveExceptIdle.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-in-power-save": {
+                        if (allowAll) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowInPowerSave.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-in-data-usage-save": {
+                        if (allowAll) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowInDataUsageSave.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-unthrottled-location": {
+                        if (allowAll) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowUnthrottledLocation.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-implicit-broadcast": {
+                        if (allowAll) {
+                            String action = parser.getAttributeValue(null, "action");
+                            if (action == null) {
+                                Slog.w(TAG, "<" + name + "> without action in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowImplicitBroadcasts.add(action);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "app-link": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                            } else {
+                                mLinkedApps.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "system-user-whitelisted-app": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mSystemUserWhitelistedApps.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "system-user-blacklisted-app": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mSystemUserBlacklistedApps.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "default-enabled-vr-app": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            String clsname = parser.getAttributeValue(null, "class");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else if (clsname == null) {
+                                Slog.w(TAG, "<" + name + "> without class in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "backup-transport-whitelisted-service": {
+                        if (allowFeatures) {
+                            String serviceName = parser.getAttributeValue(null, "service");
+                            if (serviceName == null) {
+                                Slog.w(TAG, "<" + name + "> without service in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                ComponentName cn = ComponentName.unflattenFromString(serviceName);
+                                if (cn == null) {
+                                    Slog.w(TAG, "<" + name + "> with invalid service name "
+                                            + serviceName + " in " + permFile
+                                            + " at " + parser.getPositionDescription());
+                                } else {
+                                    mBackupTransportWhitelist.add(cn);
+                                }
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "disabled-until-used-preinstalled-carrier-associated-app": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            String carrierPkgname = parser.getAttributeValue(null,
+                                    "carrierAppPackage");
+                            if (pkgname == null || carrierPkgname == null) {
+                                Slog.w(TAG, "<" + name
+                                        + "> without package or carrierAppPackage in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                            } else {
+                                List<String> associatedPkgs =
+                                        mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
+                                                carrierPkgname);
+                                if (associatedPkgs == null) {
+                                    associatedPkgs = new ArrayList<>();
+                                    mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
+                                            carrierPkgname, associatedPkgs);
+                                }
+                                associatedPkgs.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "disabled-until-used-preinstalled-carrier-app": {
+                        if (allowAppConfigs) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG,
+                                        "<" + name + "> without "
+                                                + "package in " + permFile + " at "
+                                                + parser.getPositionDescription());
+                            } else {
+                                mDisabledUntilUsedPreinstalledCarrierApps.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "privapp-permissions": {
+                        if (allowPrivappPermissions) {
+                            // privapp permissions from system, vendor, product and product_services
+                            // partitions are stored separately. This is to prevent xml files in
+                            // the vendor partition from granting permissions to priv apps in the
+                            // system partition and vice versa.
+                            boolean vendor = permFile.toPath().startsWith(
+                                    Environment.getVendorDirectory().toPath() + "/")
+                                    || permFile.toPath().startsWith(
+                                    Environment.getOdmDirectory().toPath() + "/");
+                            boolean product = permFile.toPath().startsWith(
+                                    Environment.getProductDirectory().toPath() + "/");
+                            boolean productServices = permFile.toPath().startsWith(
+                                    Environment.getProductServicesDirectory().toPath() + "/");
+                            if (vendor) {
+                                readPrivAppPermissions(parser, mVendorPrivAppPermissions,
+                                        mVendorPrivAppDenyPermissions);
+                            } else if (product) {
+                                readPrivAppPermissions(parser, mProductPrivAppPermissions,
+                                        mProductPrivAppDenyPermissions);
+                            } else if (productServices) {
+                                readPrivAppPermissions(parser, mProductServicesPrivAppPermissions,
+                                        mProductServicesPrivAppDenyPermissions);
+                            } else {
+                                readPrivAppPermissions(parser, mPrivAppPermissions,
+                                        mPrivAppDenyPermissions);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
+                    case "oem-permissions": {
+                        if (allowOemPermissions) {
+                            readOemPermissions(parser);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
+                    case "hidden-api-whitelisted-app": {
+                        if (allowApiWhitelisting) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mHiddenApiPackageWhitelist.add(pkgname);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    case "allow-association": {
+                        if (allowAssociations) {
+                            String target = parser.getAttributeValue(null, "target");
+                            if (target == null) {
+                                Slog.w(TAG, "<" + name + "> without target in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            String allowed = parser.getAttributeValue(null, "allowed");
+                            if (allowed == null) {
+                                Slog.w(TAG, "<" + name + "> without allowed in " + permFile
+                                        + " at " + parser.getPositionDescription());
+                                XmlUtils.skipCurrentTag(parser);
+                                break;
+                            }
+                            target = target.intern();
+                            allowed = allowed.intern();
+                            ArraySet<String> associations = mAllowedAssociations.get(target);
+                            if (associations == null) {
+                                associations = new ArraySet<>();
+                                mAllowedAssociations.put(target, associations);
+                            }
+                            Slog.i(TAG, "Adding association: " + target + " <- " + allowed);
+                            associations.add(allowed);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
+                    default: {
+                        Slog.w(TAG, "Tag " + name + " is unknown in "
+                                + permFile + " at " + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                 }
             }
         } catch (XmlPullParserException e) {
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index 6ebf35c..a54571b 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -36,7 +36,7 @@
         return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SafeUnref));
     }
 
-    static jlong CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
+    static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
         SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
         return reinterpret_cast<jlong>(SkColorFilter::MakeModeFilter(srcColor, mode).release());
     }
@@ -61,8 +61,8 @@
     {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
 };
 
-static const JNINativeMethod porterduff_methods[] = {
-    { "native_CreatePorterDuffFilter", "(II)J", (void*) SkColorFilterGlue::CreatePorterDuffFilter },
+static const JNINativeMethod blendmode_methods[] = {
+    { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
 };
 
 static const JNINativeMethod lighting_methods[] = {
@@ -76,8 +76,10 @@
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
                                   NELEM(colorfilter_methods));
-    android::RegisterMethodsOrDie(env, "android/graphics/PorterDuffColorFilter", porterduff_methods,
-                                  NELEM(porterduff_methods));
+    android::RegisterMethodsOrDie(env, "android/graphics/PorterDuffColorFilter", blendmode_methods,
+                                  NELEM(blendmode_methods));
+    android::RegisterMethodsOrDie(env, "android/graphics/BlendModeColorFilter", blendmode_methods,
+                                  NELEM(blendmode_methods));
     android::RegisterMethodsOrDie(env, "android/graphics/LightingColorFilter", lighting_methods,
                                   NELEM(lighting_methods));
     android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index c249e20..e97c9bc 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -577,7 +577,7 @@
         }
     }
 
-    static SkScalar getMetricsInternal(jlong paintHandle, Paint::FontMetrics *metrics) {
+    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
         const int kElegantAscent = 1900;
@@ -609,7 +609,7 @@
     }
 
     static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
         SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
 
         if (metricsObj) {
@@ -624,7 +624,7 @@
     }
 
     static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
 
         getMetricsInternal(paintHandle, &metrics);
         int ascent = SkScalarRoundToInt(metrics.fAscent);
@@ -845,12 +845,23 @@
         static_assert(9 == static_cast<int>(SkBlendMode::kSrcATop), "xfermode_mismatch");
         static_assert(10 == static_cast<int>(SkBlendMode::kDstATop), "xfermode_mismatch");
         static_assert(11 == static_cast<int>(SkBlendMode::kXor), "xfermode_mismatch");
-        static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "xfermode_mismatch");
-        static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "xfermode_mismatch");
+        static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "xfermode_mismatch");
         static_assert(13 == static_cast<int>(SkBlendMode::kModulate), "xfermode_mismatch");
         static_assert(14 == static_cast<int>(SkBlendMode::kScreen), "xfermode_mismatch");
-        static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "xfermode_mismatch");
         static_assert(15 == static_cast<int>(SkBlendMode::kOverlay), "xfermode_mismatch");
+        static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "xfermode_mismatch");
+        static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "xfermode_mismatch");
+        static_assert(18 == static_cast<int>(SkBlendMode::kColorDodge), "xfermode mismatch");
+        static_assert(19 == static_cast<int>(SkBlendMode::kColorBurn), "xfermode mismatch");
+        static_assert(20 == static_cast<int>(SkBlendMode::kHardLight), "xfermode mismatch");
+        static_assert(21 == static_cast<int>(SkBlendMode::kSoftLight), "xfermode mismatch");
+        static_assert(22 == static_cast<int>(SkBlendMode::kDifference), "xfermode mismatch");
+        static_assert(23 == static_cast<int>(SkBlendMode::kExclusion), "xfermode mismatch");
+        static_assert(24 == static_cast<int>(SkBlendMode::kMultiply), "xfermode mismatch");
+        static_assert(25 == static_cast<int>(SkBlendMode::kHue), "xfermode mismatch");
+        static_assert(26 == static_cast<int>(SkBlendMode::kSaturation), "xfermode mismatch");
+        static_assert(27 == static_cast<int>(SkBlendMode::kColor), "xfermode mismatch");
+        static_assert(28 == static_cast<int>(SkBlendMode::kLuminosity), "xfermode mismatch");
 
         SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
@@ -959,19 +970,19 @@
     }
 
     static jfloat ascent(jlong paintHandle) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
         getMetricsInternal(paintHandle, &metrics);
         return SkScalarToFloat(metrics.fAscent);
     }
 
     static jfloat descent(jlong paintHandle) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
         getMetricsInternal(paintHandle, &metrics);
         return SkScalarToFloat(metrics.fDescent);
     }
 
     static jfloat getUnderlinePosition(jlong paintHandle) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
         getMetricsInternal(paintHandle, &metrics);
         SkScalar position;
         if (metrics.hasUnderlinePosition(&position)) {
@@ -983,7 +994,7 @@
     }
 
     static jfloat getUnderlineThickness(jlong paintHandle) {
-        Paint::FontMetrics metrics;
+        SkFontMetrics metrics;
         getMetricsInternal(paintHandle, &metrics);
         SkScalar thickness;
         if (metrics.hasUnderlineThickness(&thickness)) {
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 752624b..0d75de9 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -338,6 +338,11 @@
     return renderNode->stagingProperties().hasOverlappingRendering();
 }
 
+static jboolean android_view_RenderNode_getClipToBounds(jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    return renderNode->stagingProperties().getClipToBounds();
+}
+
 static jboolean android_view_RenderNode_getClipToOutline(jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     return renderNode->stagingProperties().getOutline().getShouldClip();
@@ -409,6 +414,11 @@
     return !renderNode->stagingProperties().hasTransformMatrix();
 }
 
+static jint android_view_RenderNode_getLayerType(jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    return static_cast<int>(renderNode->stagingProperties().layerProperties().type());
+}
+
 // ----------------------------------------------------------------------------
 // RenderProperties - computed getters
 // ----------------------------------------------------------------------------
@@ -623,10 +633,12 @@
 // ----------------------------------------------------------------------------
     { "nIsValid",              "(J)Z",   (void*) android_view_RenderNode_isValid },
     { "nSetLayerType",         "(JI)Z",  (void*) android_view_RenderNode_setLayerType },
+    { "nGetLayerType",         "(J)I",   (void*) android_view_RenderNode_getLayerType },
     { "nSetLayerPaint",        "(JJ)Z",  (void*) android_view_RenderNode_setLayerPaint },
     { "nSetStaticMatrix",      "(JJ)Z",  (void*) android_view_RenderNode_setStaticMatrix },
     { "nSetAnimationMatrix",   "(JJ)Z",  (void*) android_view_RenderNode_setAnimationMatrix },
     { "nSetClipToBounds",      "(JZ)Z",  (void*) android_view_RenderNode_setClipToBounds },
+    { "nGetClipToBounds",      "(J)Z",   (void*) android_view_RenderNode_getClipToBounds },
     { "nSetClipBounds",        "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
     { "nSetClipBoundsEmpty",   "(J)Z",   (void*) android_view_RenderNode_setClipBoundsEmpty },
     { "nSetProjectBackwards",  "(JZ)Z",  (void*) android_view_RenderNode_setProjectBackwards },
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7032081..ff4591f 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -418,7 +418,7 @@
     }
     endmntent(fp);
 
-    for (auto path : toUnmount) {
+    for (const auto& path : toUnmount) {
         if (umount2(path.c_str(), MNT_DETACH)) {
             ALOGW("Failed to unmount %s: %s", path.c_str(), strerror(errno));
         }
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index a398e49..33b2689 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -331,11 +331,13 @@
     return false;
   }
 
-  if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
+  int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+  if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed dup2(%d, %d) (%s): %s",
+    *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
                                              fd,
                                              new_fd,
+                                             dupFlags,
                                              file_path.c_str(),
                                              strerror(errno));
     return false;
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 514f306..3e1c5a3 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -82,5 +82,7 @@
 
     // OPEN: WifiDppEnrolleeActivity (android.settings.WIFI_DPP_ENROLLEE_XXX action intents)
     SETTINGS_WIFI_DPP_ENROLLEE = 1596;
-}
 
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Financial Apps Sms Access
+    SETTINGS_FINANCIAL_APPS_SMS_ACCESS = 1597;
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 7fe3be8..0ec8c1a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -220,6 +220,13 @@
     // will use heartbeats, false will use a rolling window.
     optional bool use_heartbeats = 23;
 
+    message TimeController {
+        // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
+        // ready now.
+        optional bool skip_not_ready_jobs = 1;
+    }
+    optional TimeController time_controller = 25;
+
     message QuotaController {
         // How much time each app will have to run jobs within their standby bucket window.
         optional int64 allowed_time_per_period_ms = 1;
@@ -242,8 +249,12 @@
         // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
         // WINDOW_SIZE_MS.
         optional int64 rare_window_size_ms = 6;
+        // The maximum amount of time an app can have its jobs running within a 24 hour window.
+        optional int64 max_execution_time_ms = 7;
     }
     optional QuotaController quota_controller = 24;
+
+    // Next tag: 26
 }
 
 message StateControllerProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index c7a6b68..82460ec 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -141,4 +141,7 @@
   PM_UNINSTALL = 113;
   WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114;
   WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115;
+  QUERY_SUMMARY_FOR_DEVICE = 116;
+  REMOVE_CROSS_PROFILE_WIDGET_PROVIDER = 117;
+  ESTABLISH_VPN = 118;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dca15bd..cc8927f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1642,6 +1642,12 @@
     <permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"
         android:protectionLevel="signature" />
 
+    <!-- #SystemApi @hide Allows device mobility state to be set so that Wifi scan interval can be increased
+         when the device is stationary in order to save power.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -4342,6 +4348,10 @@
          @hide -->
     <permission android:name="android.permission.AMBIENT_WALLPAPER"
                 android:protectionLevel="signature|preinstalled" />
+    <!-- @SystemApi Allows sensor privacy to be modified.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
+                android:protectionLevel="signature" />
 
     <application android:process="system"
                  android:persistent="true"
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 84c6446..43b3552 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -59,4 +59,6 @@
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. -->
     <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert" />
-</resources>
+
+    <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault" />
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8dbea38..91faa55 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7632,103 +7632,143 @@
     <declare-styleable name="VoiceInteractionSession">
     </declare-styleable>
 
+    <!-- {@deprecated Copy this definition into your own application project.} -->
     <declare-styleable name="KeyboardView">
-        <!-- Default KeyboardView style. -->
+        <!-- Default KeyboardView style.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyboardViewStyle" format="reference" />
 
         <!-- Image for the key. This image needs to be a StateListDrawable, with the following
              possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
-             checkable+checked+pressed. -->
+             checkable+checked+pressed.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyBackground" format="reference" />
 
-        <!-- Size of the text for character keys. -->
+        <!-- Size of the text for character keys.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyTextSize" format="dimension" />
 
-        <!-- Size of the text for custom keys with some text and no icon. -->
+        <!-- Size of the text for custom keys with some text and no icon.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="labelTextSize" format="dimension" />
 
-        <!-- Color to use for the label in a key. -->
+        <!-- Color to use for the label in a key.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyTextColor" format="color" />
 
-        <!-- Layout resource for key press feedback.-->
+        <!-- Layout resource for key press feedback.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyPreviewLayout" format="reference" />
 
-        <!-- Vertical offset of the key press feedback from the key. -->
+        <!-- Vertical offset of the key press feedback from the key.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyPreviewOffset" format="dimension" />
 
-        <!-- Height of the key press feedback popup. -->
+        <!-- Height of the key press feedback popup.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyPreviewHeight" format="dimension" />
 
-        <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
+        <!-- Amount to offset the touch Y coordinate by, for bias correction.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="verticalCorrection" format="dimension" />
 
-        <!-- Layout resource for popup keyboards. -->
+        <!-- Layout resource for popup keyboards.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="popupLayout" format="reference" />
 
+        <!-- {@deprecated Copy this definition into your own application project.} -->
         <attr name="shadowColor" />
+        <!-- {@deprecated Copy this definition into your own application project.} -->
         <attr name="shadowRadius" />
     </declare-styleable>
 
+    <!-- {@deprecated Copy this definition into your own application project.} -->
     <declare-styleable name="KeyboardViewPreviewState">
         <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView}
-                key preview background. -->
+                key preview background.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="state_long_pressable" format="boolean" />
     </declare-styleable>
 
+    <!-- {@deprecated Copy this definition into your own application project.} -->
     <declare-styleable name="Keyboard">
-        <!-- Default width of a key, in pixels or percentage of display width. -->
+        <!-- Default width of a key, in pixels or percentage of display width.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyWidth" format="dimension|fraction" />
-        <!-- Default height of a key, in pixels or percentage of display width. -->
+        <!-- Default height of a key, in pixels or percentage of display width.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyHeight" format="dimension|fraction" />
-        <!-- Default horizontal gap between keys. -->
+        <!-- Default horizontal gap between keys.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="horizontalGap" format="dimension|fraction" />
-        <!-- Default vertical gap between rows of keys. -->
+        <!-- Default vertical gap between rows of keys.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="verticalGap" format="dimension|fraction" />
     </declare-styleable>
 
+    <!-- {@deprecated Copy this definition into your own application project.} -->
     <declare-styleable name="Keyboard_Row">
-        <!-- Row edge flags. -->
+        <!-- Row edge flags.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="rowEdgeFlags">
-            <!-- Row is anchored to the top of the keyboard. -->
+            <!-- Row is anchored to the top of the keyboard.
+             {@deprecated Copy this definition into your own application project.} -->
             <flag name="top" value="4" />
-            <!-- Row is anchored to the bottom of the keyboard. -->
+            <!-- Row is anchored to the bottom of the keyboard.
+             {@deprecated Copy this definition into your own application project.} -->
             <flag name="bottom" value="8" />
         </attr>
         <!-- Mode of the keyboard. If the mode doesn't match the
-             requested keyboard mode, the row will be skipped. -->
+             requested keyboard mode, the row will be skipped.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyboardMode" format="reference" />
     </declare-styleable>
 
+    <!-- {@deprecated Copy this definition into your own application project.} -->
     <declare-styleable name="Keyboard_Key">
-        <!-- The unicode value or comma-separated values that this key outputs. -->
+        <!-- The unicode value or comma-separated values that this key outputs.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="codes" format="integer|string" />
-        <!-- The XML keyboard layout of any popup keyboard. -->
+        <!-- The XML keyboard layout of any popup keyboard.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="popupKeyboard" format="reference" />
-        <!-- The characters to display in the popup keyboard. -->
+        <!-- The characters to display in the popup keyboard.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="popupCharacters" format="string" />
-        <!-- Key edge flags. -->
+        <!-- Key edge flags.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyEdgeFlags">
-            <!-- Key is anchored to the left of the keyboard. -->
+            <!-- Key is anchored to the left of the keyboard.
+                 {@deprecated Copy this definition into your own application project.} -->
             <flag name="left" value="1" />
-            <!-- Key is anchored to the right of the keyboard. -->
+            <!-- Key is anchored to the right of the keyboard.
+                 {@deprecated Copy this definition into your own application project.} -->
             <flag name="right" value="2" />
         </attr>
-        <!-- Whether this is a modifier key such as Alt or Shift. -->
+        <!-- Whether this is a modifier key such as Alt or Shift.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="isModifier" format="boolean" />
-        <!-- Whether this is a toggle key. -->
+        <!-- Whether this is a toggle key.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="isSticky" format="boolean" />
-        <!-- Whether long-pressing on this key will make it repeat. -->
+        <!-- Whether long-pressing on this key will make it repeat.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="isRepeatable" format="boolean" />
-        <!-- The icon to show in the popup preview. -->
+        <!-- The icon to show in the popup preview.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="iconPreview" format="reference" />
-        <!-- The string of characters to output when this key is pressed. -->
+        <!-- The string of characters to output when this key is pressed.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyOutputText" format="string" />
-        <!-- The label to display on the key. -->
+        <!-- The label to display on the key.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyLabel" format="string" />
-        <!-- The icon to display on the key instead of the label. -->
+        <!-- The icon to display on the key instead of the label.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyIcon" format="reference" />
         <!-- Mode of the keyboard. If the mode doesn't match the
-             requested keyboard mode, the key will be skipped. -->
+             requested keyboard mode, the key will be skipped.
+             {@deprecated Copy this definition into your own application project.} -->
         <attr name="keyboardMode" />
     </declare-styleable>
 
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 35263a3..dd51cb6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1294,6 +1294,20 @@
          supports any size. -->
     <attr name="maxAspectRatio" format="float" />
 
+    <!-- This value indicates the minimum aspect ratio the activity supports. If the app runs on a
+         device with a narrower aspect ratio, the system automatically letterboxes the app, leaving
+         portions of the screen unused so the app can run at its specified minimum aspect ratio.
+         <p>
+         Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+         form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.
+         <p>
+         Value needs to be greater or equal to 1.0, otherwise it is ignored.
+         <p>
+         NOTE: This attribute is ignored if the activity has
+         {@link android.R.attr#resizeableActivity} set to true, since that means your activity
+         supports any size. -->
+    <attr name="minAspectRatio" format="float" />
+
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
          While in lockTask mode the system will not launch non-permitted tasks until
          lockTask mode is disabled.
@@ -1571,6 +1585,7 @@
         <attr name="directBootAware" />
         <attr name="resizeableActivity" />
         <attr name="maxAspectRatio" />
+        <attr name="minAspectRatio" />
         <attr name="networkSecurityConfig" />
         <!-- Declare the category of this app. Categories are used to cluster multiple apps
              together into meaningful groups, such as when summarizing battery, network, or
@@ -2386,6 +2401,7 @@
         <attr name="resizeableActivity" />
         <attr name="supportsPictureInPicture" />
         <attr name="maxAspectRatio" />
+        <attr name="minAspectRatio" />
         <attr name="lockTaskMode" />
         <attr name="showForAllUsers" />
 
diff --git a/core/res/res/values/colors_car.xml b/core/res/res/values/colors_car.xml
index ea7c009..f4aeff7 100644
--- a/core/res/res/values/colors_car.xml
+++ b/core/res/res/values/colors_car.xml
@@ -50,7 +50,7 @@
     <color name="car_headline4_dark">@android:color/black</color>
     <color name="car_headline4">@color/car_headline4_light</color>
 
-    <color name="car_body1_light">@color/car_grey_100</color>
+    <color name="car_body1_light">@color/car_grey_50</color>
     <color name="car_body1_dark">@color/car_grey_900</color>
     <color name="car_body1">@color/car_body1_light</color>
 
@@ -58,7 +58,7 @@
     <color name="car_body2_dark">@color/car_grey_700</color>
     <color name="car_body2">@color/car_body2_light</color>
 
-    <color name="car_body3_light">@android:color/white</color>
+    <color name="car_body3_light">@color/car_grey_400</color>
     <color name="car_body3_dark">@android:color/black</color>
     <color name="car_body3">@color/car_body3_light</color>
 
@@ -137,7 +137,7 @@
     <color name="car_toast_background">#E6282a2d</color>
 
     <!-- Misc colors -->
-    <color name="car_highlight_light">@color/car_teal_700</color>
+    <color name="car_highlight_light">@color/car_teal_200</color>
     <color name="car_highlight_dark">@color/car_teal_200</color>
     <color name="car_highlight">@color/car_highlight_dark</color>
     <color name="car_accent_light">@color/car_highlight_light</color>
@@ -148,6 +148,7 @@
     <color name="car_user_switcher_user_image_fgcolor">@color/car_grey_900</color>
 
     <!-- Color palette for cars -->
+    <color name="car_grey_972">#ff090A0C</color>
     <color name="car_grey_958">#ff0e1013</color>
     <color name="car_grey_928">#ff17181b</color>
     <color name="car_grey_900">#ff202124</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 97a21a5..dd0b1ee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1165,6 +1165,10 @@
     <!-- The default suggested battery % at which we enable battery saver automatically.  -->
     <integer name="config_lowBatteryAutoTriggerDefaultLevel">15</integer>
 
+    <!-- The app which will handle routine based automatic battery saver, if empty the UI for
+             routine based battery saver will be hidden -->
+    <string name="config_batterySaverScheduleProvider"></string>
+
     <!-- Close low battery warning when battery level reaches the lowBatteryWarningLevel
          plus this -->
     <integer name="config_lowBatteryCloseWarningBump">5</integer>
@@ -1437,26 +1441,6 @@
     <integer-array name="config_autoBrightnessKeyboardBacklightValues">
     </integer-array>
 
-    <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
-         percent. The length of this array is assumed to be one greater than
-         config_dynamicHysteresisLuxLevels. The brightening threshold is calculated as
-         lux * (1.0f + CONSTRAINT_VALUE). When the current lux is higher than this threshold,
-         the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
-         description for how the constraint value is chosen. -->
-    <integer-array name="config_dynamicHysteresisBrightLevels">
-        <item>100</item>
-    </integer-array>
-
-    <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
-         percent. The length of this array is assumed to be one greater than
-         config_dynamicHysteresisLuxLevels. The darkening threshold is calculated as
-         lux * (1.0f - CONSTRAINT_VALUE). When the current lux is lower than this threshold,
-         the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
-         description for how the constraint value is chosen. -->
-    <integer-array name="config_dynamicHysteresisDarkLevels">
-        <item>200</item>
-    </integer-array>
-
     <!-- An array describing the screen's backlight values corresponding to the brightness
          values in the config_screenBrightnessNits array.
 
@@ -1474,19 +1458,73 @@
     <array name="config_screenBrightnessNits">
     </array>
 
-
     <!-- Array of ambient lux threshold values. This is used for determining hysteresis constraint
          values by calculating the index to use for lookup and then setting the constraint value
          to the corresponding value of the array. The new brightening hysteresis constraint value
-         is the n-th element of config_dynamicHysteresisBrightLevels, and the new darkening
-         hysteresis constraint value is the n-th element of config_dynamicHysteresisDarkLevels.
+         is the n-th element of config_ambientBrighteningThresholds, and the new darkening
+         hysteresis constraint value is the n-th element of config_ambientDarkeningThresholds.
 
          The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
-         condition                      calculated index
-         value < lux[0]                 0
-         lux[n] <= value < lux[n+1]     n+1
-         lux[MAX] <= value              MAX+1 -->
-    <integer-array name="config_dynamicHysteresisLuxLevels">
+         condition                       calculated index
+         value < level[0]                0
+         level[n] <= value < level[n+1]  n+1
+         level[MAX] <= value             MAX+1 -->
+    <integer-array name="config_ambientThresholdLevels">
+    </integer-array>
+
+    <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_ambientThresholdLevels. The brightening threshold is calculated as
+         lux * (1.0f + CONSTRAINT_VALUE). When the current lux is higher than this threshold,
+         the screen brightness is recalculated. See the config_ambientThresholdLevels
+         description for how the constraint value is chosen. -->
+    <integer-array name="config_ambientBrighteningThresholds">
+        <item>100</item>
+    </integer-array>
+
+    <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_ambientThresholdLevels. The darkening threshold is calculated as
+         lux * (1.0f - CONSTRAINT_VALUE). When the current lux is lower than this threshold,
+         the screen brightness is recalculated. See the config_ambientThresholdLevels
+         description for how the constraint value is chosen. -->
+    <integer-array name="config_ambientDarkeningThresholds">
+        <item>200</item>
+    </integer-array>
+
+    <!-- Array of screen brightness threshold values. This is used for determining hysteresis
+         constraint values by calculating the index to use for lookup and then setting the
+         constraint value to the corresponding value of the array. The new brightening hysteresis
+         constraint value is the n-th element of config_screenBrighteningThresholds, and the new
+         darkening hysteresis constraint value is the n-th element of
+         config_screenDarkeningThresholds.
+
+         The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
+         condition                       calculated index
+         value < level[0]                0
+         level[n] <= value < level[n+1]  n+1
+         level[MAX] <= value             MAX+1 -->
+    <integer-array name="config_screenThresholdLevels">
+    </integer-array>
+
+    <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_screenThresholdLevels. The brightening threshold is calculated as
+         screenBrightness * (1.0f + CONSTRAINT_VALUE). When the new screen brightness is higher
+         than this threshold, it is applied. See the config_screenThresholdLevels description for
+         how the constraint value is chosen. -->
+    <integer-array name="config_screenBrighteningThresholds">
+        <item>100</item>
+    </integer-array>
+ 
+    <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_screenThresholdLevels. The darkening threshold is calculated as
+         screenBrightness * (1.0f - CONSTRAINT_VALUE). When the new screen brightness is lower than
+         this threshold, it is applied. See the config_screenThresholdLevels description for how
+         the constraint value is chosen. -->
+    <integer-array name="config_screenDarkeningThresholds">
+        <item>200</item>
     </integer-array>
 
     <!-- Amount of time it takes for the light sensor to warm up in milliseconds.
@@ -2019,6 +2057,10 @@
          This is intended to allow packaging drivers or tools for installation on a PC. -->
     <string translatable="false" name="config_isoImagePath"></string>
 
+    <!-- Whether the system enables per-display focus. If the system has the input method for each
+         display, this value should be true. -->
+    <bool name="config_perDisplayFocusEnabled">false</bool>
+
     <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
          autodetected from the Configuration. -->
     <bool name="config_showNavigationBar">false</bool>
@@ -3602,4 +3644,6 @@
          (android.view.InputEventCompatProcessor). -->
     <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
 
+    <!-- Component name for the default module metadata provider on this device -->
+    <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index eac8b48..6f75d90 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1067,7 +1067,8 @@
   <public type="id" name="inputExtractEditText" id="0x01020025" />
 
   <!-- View ID of the {@link android.inputmethodservice.KeyboardView} within
-        an input method's input area. -->
+       an input method's input area.
+       {@deprecated Use Copy this definition into your own application project.} -->
   <public type="id" name="keyboardView" id="0x01020026" />
   <!-- View ID of a {@link android.view.View} to close a popup keyboard -->
   <public type="id" name="closeButton" id="0x01020027" />
@@ -1082,6 +1083,7 @@
   <public type="style" name="Theme.InputMethod" id="0x01030054" />
   <public type="style" name="Theme.NoDisplay" id="0x01030055" />
   <public type="style" name="Animation.InputMethod" id="0x01030056" />
+  <!-- {@deprecated Use Copy this definition into your own application project.} -->
   <public type="style" name="Widget.KeyboardView" id="0x01030057" />
   <public type="style" name="ButtonBar" id="0x01030058" />
   <public type="style" name="Theme.Panel" id="0x01030059" />
@@ -2931,6 +2933,7 @@
         <public name="selectionDividerHeight" />
         <public name="foregroundServiceType" />
         <public name="hasFragileUserData" />
+        <public name="minAspectRatio" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cab01f9..f25427a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3200,6 +3200,8 @@
     <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
     <string name="android_start_title" product="default">Phone is starting\u2026</string>
     <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
+    <string name="android_start_title" product="automotive">Android is starting\u2026</string>
+    <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
     <string name="android_start_title" product="tablet">Tablet is starting\u2026</string>
     <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
     <string name="android_start_title" product="device">Device is starting\u2026</string>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 2c04ec8..4b97fe75 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -41,7 +41,9 @@
         <item name="textAppearance">?attr/textAppearanceButton</item>
         <item name="textColor">@color/btn_colored_text_material</item>
     </style>
-    <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView"/>
+    <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView">
+        <item name="textAppearance">@string/config_bodyFontFamily</item>
+    </style>
     <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
     <style name="Widget.DeviceDefault.AutoCompleteTextView" parent="Widget.Material.AutoCompleteTextView"/>
     <style name="Widget.DeviceDefault.CompoundButton.CheckBox" parent="Widget.Material.CompoundButton.CheckBox"/>
@@ -266,6 +268,12 @@
     <style name="TextAppearance.DeviceDefault.Notification.Reply" parent="TextAppearance.Material.Notification.Reply">
         <item name="fontFamily">@string/config_bodyFontFamily</item>
     </style>
+    <style name="TextAppearance.DeviceDefault.Notification.Info" parent="TextAppearance.Material.Notification.Info">
+        <item name="fontFamily">@string/config_bodyFontFamily</item>
+    </style>
+    <style name="TextAppearance.DeviceDefault.Notification.Info.Ambient" parent="TextAppearance.Material.Notification.Info.Ambient">
+        <item name="fontFamily">@string/config_bodyFontFamily</item>
+    </style>
     <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget">
         <item name="fontFamily">@string/config_bodyFontFamily</item>
     </style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 161e416..87fdc1f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1670,6 +1670,7 @@
   <java-symbol type="bool" name="config_lockDayNightMode" />
   <java-symbol type="bool" name="config_lockUiMode" />
   <java-symbol type="bool" name="config_reverseDefaultRotation" />
+  <java-symbol type="bool" name="config_perDisplayFocusEnabled" />
   <java-symbol type="bool" name="config_showNavigationBar" />
   <java-symbol type="bool" name="config_supportAutoRotation" />
   <java-symbol type="bool" name="target_honeycomb_needs_options_menu" />
@@ -1828,9 +1829,12 @@
   <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLevels" />
-  <java-symbol type="array" name="config_dynamicHysteresisBrightLevels" />
-  <java-symbol type="array" name="config_dynamicHysteresisDarkLevels" />
-  <java-symbol type="array" name="config_dynamicHysteresisLuxLevels" />
+  <java-symbol type="array" name="config_ambientThresholdLevels" />
+  <java-symbol type="array" name="config_ambientBrighteningThresholds" />
+  <java-symbol type="array" name="config_ambientDarkeningThresholds" />
+  <java-symbol type="array" name="config_screenThresholdLevels" />
+  <java-symbol type="array" name="config_screenBrighteningThresholds" />
+  <java-symbol type="array" name="config_screenDarkeningThresholds" />
   <java-symbol type="array" name="config_minimumBrightnessCurveLux" />
   <java-symbol type="array" name="config_minimumBrightnessCurveNits" />
   <java-symbol type="array" name="config_protectedNetworks" />
@@ -3459,6 +3463,7 @@
   <java-symbol type="integer" name="config_lowBatteryAutoTriggerDefaultLevel" />
   <java-symbol type="bool" name="config_batterySaverStickyBehaviourDisabled" />
   <java-symbol type="integer" name="config_dynamicPowerSavingsDefaultDisableThreshold" />
+  <java-symbol type="string" name="config_batterySaverScheduleProvider" />
 
   <!-- For car devices -->
   <java-symbol type="string" name="car_loading_profile" />
@@ -3515,4 +3520,6 @@
   <java-symbol type="dimen" name="rounded_corner_radius" />
   <java-symbol type="dimen" name="rounded_corner_radius_top" />
   <java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+
+  <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
 </resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index fec101a..0ed8212 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -211,7 +211,7 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-
+        <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
@@ -936,6 +936,7 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
     </style>
 
     <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
@@ -1646,8 +1647,10 @@
 
     <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
 
+    <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault.Light" />
+
     <!-- Theme used for the intent picker activity. -->
-    <style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
+    <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.DayNight">
         <item name="windowEnterTransition">@empty</item>
         <item name="windowExitTransition">@empty</item>
         <item name="windowIsTranslucent">true</item>
@@ -1659,30 +1662,8 @@
         <item name="colorControlActivated">?attr/colorControlHighlight</item>
         <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
         <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
-
-        <!-- Dialog attributes -->
-        <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
-        <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
-
-        <!-- Button styles -->
-        <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
-        <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
-
-        <!-- Color palette -->
-        <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
-        <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorError">@color/error_color_device_default_light</item>
-
-        <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
-        <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
-
-        <!-- Toolbar attributes -->
-        <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
     </style>
 
-
     <!-- @hide DeviceDefault themes for the autofill FillUi -->
     <style name="Theme.DeviceDefault.Autofill" />
     <style name="Theme.DeviceDefault.Light.Autofill" />
@@ -1725,9 +1706,11 @@
     </style>
 
     <style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification">
+        <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
     </style>
 
     <style name="Theme.DeviceDefault.Notification.Ambient" parent="@style/Theme.Material.Notification.Ambient">
+        <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info.Ambient</item>
     </style>
 
 </resources>
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 55e21a7..514ea0c 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -517,6 +517,28 @@
     }
 
     @Test
+    public void testMalformedTransate_int() throws Exception {
+        try {
+            // The non-standard Linux access mode 3 should throw
+            // an IllegalArgumentException.
+            translateModePosixToPfd(O_RDWR | O_WRONLY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testMalformedTransate_string() throws Exception {
+        try {
+            // The non-standard Linux access mode 3 should throw
+            // an IllegalArgumentException.
+            translateModePosixToString(O_RDWR | O_WRONLY);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testTranslateMode_Invalid() throws Exception {
         try {
             translateModeStringToPosix("rwx");
diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java
index 985fa4f..2b84126 100644
--- a/core/tests/coretests/src/android/os/OsTests.java
+++ b/core/tests/coretests/src/android/os/OsTests.java
@@ -33,7 +33,6 @@
         suite.addTestSuite(MessageQueueTest.class);
         suite.addTestSuite(MessengerTest.class);
         suite.addTestSuite(PatternMatcherTest.class);
-        suite.addTestSuite(SystemPropertiesTest.class);
 
         return suite;
     }
diff --git a/core/tests/coretests/src/android/os/SystemPropertiesTest.java b/core/tests/coretests/src/android/os/SystemPropertiesTest.java
deleted file mode 100644
index 25868ce..0000000
--- a/core/tests/coretests/src/android/os/SystemPropertiesTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import static junit.framework.Assert.assertEquals;
-import junit.framework.TestCase;
-
-import android.os.SystemProperties;
-import android.test.suitebuilder.annotation.SmallTest;
-
-public class SystemPropertiesTest extends TestCase {
-    private static final String KEY = "com.android.frameworks.coretests";
-    @SmallTest
-    public void testProperties() throws Exception {
-        if (false) {
-        String value;
-       
-        SystemProperties.set(KEY, "");
-        value = SystemProperties.get(KEY, "default");
-        assertEquals("default", value);
-
-        SystemProperties.set(KEY, "AAA");
-        value = SystemProperties.get(KEY, "default");
-        assertEquals("AAA", value);
-
-        value = SystemProperties.get(KEY);
-        assertEquals("AAA", value);
-
-        SystemProperties.set(KEY, "");
-        value = SystemProperties.get(KEY, "default");
-        assertEquals("default", value);
-
-        value = SystemProperties.get(KEY);
-        assertEquals("", value);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
new file mode 100644
index 0000000..800b864
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.provider;
+
+import static android.provider.DeviceConfig.OnPropertyChangedListener;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Tests that ensure appropriate settings are backed up. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DeviceConfigTest {
+    // TODO(b/109919982): Migrate tests to CTS
+    private static final String sNamespace = "namespace1";
+    private static final String sKey = "key1";
+    private static final String sValue = "value1";
+    private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+
+    private final Object mLock = new Object();
+
+    @After
+    public void cleanUp() {
+        deleteViaContentProvider(sNamespace, sKey);
+    }
+
+    @Test
+    public void getProperty_empty() {
+        String result = DeviceConfig.getProperty(sNamespace, sKey);
+        assertNull(result);
+    }
+
+    @Test
+    public void setAndGetProperty_sameNamespace() {
+        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+        String result = DeviceConfig.getProperty(sNamespace, sKey);
+        assertEquals(sValue, result);
+    }
+
+    @Test
+    public void setAndGetProperty_differentNamespace() {
+        String newNamespace = "namespace2";
+        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+        String result = DeviceConfig.getProperty(newNamespace, sKey);
+        assertNull(result);
+    }
+
+    @Test
+    public void setAndGetProperty_multipleNamespaces() {
+        String newNamespace = "namespace2";
+        String newValue = "value2";
+        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+        DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
+        String result = DeviceConfig.getProperty(sNamespace, sKey);
+        assertEquals(sValue, result);
+        result = DeviceConfig.getProperty(newNamespace, sKey);
+        assertEquals(newValue, result);
+
+        // clean up
+        deleteViaContentProvider(newNamespace, sKey);
+    }
+
+    @Test
+    public void setAndGetProperty_overrideValue() {
+        String newValue = "value2";
+        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+        DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
+        String result = DeviceConfig.getProperty(sNamespace, sKey);
+        assertEquals(newValue, result);
+    }
+
+    @Test
+    public void testListener() {
+        setPropertyAndAssertSuccessfulChange(sNamespace, sKey, sValue);
+    }
+
+    private void setPropertyAndAssertSuccessfulChange(String setNamespace, String setName,
+            String setValue) {
+        final AtomicBoolean success = new AtomicBoolean();
+
+        OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
+                    @Override
+                    public void onPropertyChanged(String namespace, String name, String value) {
+                        assertEquals(setNamespace, namespace);
+                        assertEquals(setName, name);
+                        assertEquals(setValue, value);
+                        success.set(true);
+
+                        synchronized (mLock) {
+                            mLock.notifyAll();
+                        }
+                    }
+                };
+        Executor executor = ActivityThread.currentApplication().getMainExecutor();
+        DeviceConfig.addOnPropertyChangedListener(setNamespace, executor, changeListener);
+        try {
+            DeviceConfig.setProperty(setNamespace, setName, setValue, false);
+
+            final long startTimeMillis = SystemClock.uptimeMillis();
+            synchronized (mLock) {
+                while (true) {
+                    if (success.get()) {
+                        return;
+                    }
+                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                    if (elapsedTimeMillis >= WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS) {
+                        fail("Could not change setting for "
+                                + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS + " ms");
+                    }
+                    final long remainingTimeMillis = WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS
+                            - elapsedTimeMillis;
+                    try {
+                        mLock.wait(remainingTimeMillis);
+                    } catch (InterruptedException ie) {
+                        /* ignore */
+                    }
+                }
+            }
+        } finally {
+            DeviceConfig.removeOnPropertyChangedListener(changeListener);
+        }
+    }
+
+    private static boolean deleteViaContentProvider(String namespace, String key) {
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        String compositeName = namespace + "/" + key;
+        Bundle result = resolver.call(
+                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+        assertNotNull(result);
+        return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
+    }
+
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3a37fb6..6d1aae1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -543,7 +543,9 @@
                     Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
                     Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
                     Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
-                    Settings.Global.BACKUP_MULTI_USER_ENABLED);
+                    Settings.Global.BACKUP_MULTI_USER_ENABLED,
+                    Settings.Global.ISOLATED_STORAGE_LOCAL,
+                    Settings.Global.ISOLATED_STORAGE_REMOTE);
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
@@ -584,6 +586,7 @@
                  Settings.Secure.DEFAULT_INPUT_METHOD,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
+                 Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
                  Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
                  Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -605,6 +608,8 @@
                  Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
                  Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                  Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+                 Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED,
+                 Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED,
                  Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
                  Settings.Secure.MULTI_PRESS_TIMEOUT,
                  Settings.Secure.NFC_PAYMENT_FOREGROUND,
@@ -616,6 +621,7 @@
                  Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
                  Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
                  Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+                 Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED,
                  Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
                  Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
                  Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
@@ -639,6 +645,7 @@
                  Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
                  Settings.Secure.SELECTED_SPELL_CHECKER,  // Intentionally removed in Q
                  Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,  // Intentionally removed in Q
+                 Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE,
                  Settings.Secure.SETTINGS_CLASSNAME,
                  Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
                  Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 04e8802..cb6f0e6 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -44,12 +44,6 @@
 
 /** Unit test for SettingsProvider. */
 public class SettingsProviderTest extends AndroidTestCase {
-    /**
-     * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
-     *     API.
-     */
-    private static final Uri CONFIG_CONTENT_URI =
-            Uri.parse("content://" + Settings.AUTHORITY + "/config");
 
     @MediumTest
     public void testNameValueCache() {
@@ -406,27 +400,27 @@
         try {
             // value is empty
             Bundle results =
-                    r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
 
             // save value
-            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
             assertNull(results);
 
             // value is no longer empty
-            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // save new value
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // new value is returned
-            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -440,22 +434,23 @@
 
         try {
             // save value
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // get value
             Bundle results =
-                    r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // delete value
-            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+                    null);
 
             // value is empty now
-            results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -473,12 +468,12 @@
 
         try {
             // save both values
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
 
             // list all values
-            Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+            Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
                     null, null);
             Map<String, String> keyValueMap =
                     (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -488,14 +483,14 @@
 
             // list values for prefix
             args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-            result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+            result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
             keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
             assertThat(keyValueMap, aMapWithSize(1));
             assertEquals(value, keyValueMap.get(name));
         } finally {
             // clean up
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
-            r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 9b5c034..81ec85e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -18,7 +18,6 @@
 
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -375,7 +374,7 @@
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
                         ConversationActions.Message.PERSON_USER_REMOTE)
-                        .setText("Hello")
+                        .setText("Where are you?")
                         .build();
         ConversationActions.TypeConfig typeConfig =
                 new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
@@ -384,19 +383,44 @@
                         .build();
         ConversationActions.Request request =
                 new ConversationActions.Request.Builder(Collections.singletonList(message))
-                        .setMaxSuggestions(3)
+                        .setMaxSuggestions(1)
                         .setTypeConfig(typeConfig)
                         .build();
 
         ConversationActions conversationActions = mClassifier.suggestConversationActions(request);
         assertTrue(conversationActions.getConversationActions().size() > 0);
-        assertTrue(conversationActions.getConversationActions().size() <= 3);
+        assertTrue(conversationActions.getConversationActions().size() == 1);
         for (ConversationActions.ConversationAction conversationAction :
                 conversationActions.getConversationActions()) {
-            assertEquals(conversationAction.getType(), ConversationActions.TYPE_TEXT_REPLY);
-            assertNotNull(conversationAction.getTextReply());
-            assertTrue(conversationAction.getConfidenceScore() > 0);
-            assertTrue(conversationAction.getConfidenceScore() <= 1);
+            assertThat(conversationAction,
+                    isConversationAction(ConversationActions.TYPE_TEXT_REPLY));
+        }
+    }
+
+    @Test
+    public void testSuggestConversationActions_textReplyOnly_noMax() {
+        if (isTextClassifierDisabled()) return;
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(
+                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        .setText("Where are you?")
+                        .build();
+        ConversationActions.TypeConfig typeConfig =
+                new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY))
+                        .build();
+        ConversationActions.Request request =
+                new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .setTypeConfig(typeConfig)
+                        .build();
+
+        ConversationActions conversationActions = mClassifier.suggestConversationActions(request);
+        assertTrue(conversationActions.getConversationActions().size() > 1);
+        for (ConversationActions.ConversationAction conversationAction :
+                conversationActions.getConversationActions()) {
+            assertThat(conversationAction,
+                    isConversationAction(ConversationActions.TYPE_TEXT_REPLY));
         }
     }
 
@@ -498,4 +522,36 @@
             }
         };
     }
+
+    private static Matcher<ConversationActions.ConversationAction> isConversationAction(
+            String actionType) {
+        return new BaseMatcher<ConversationActions.ConversationAction>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof ConversationActions.ConversationAction)) {
+                    return false;
+                }
+                ConversationActions.ConversationAction conversationAction =
+                        (ConversationActions.ConversationAction) o;
+                if (!actionType.equals(conversationAction.getType())) {
+                    return false;
+                }
+                if (ConversationActions.TYPE_TEXT_REPLY.equals(actionType)) {
+                    if (conversationAction.getTextReply() == null) {
+                        return false;
+                    }
+                }
+                if (conversationAction.getConfidenceScore() < 0
+                        || conversationAction.getConfidenceScore() > 1) {
+                    return false;
+                }
+                return true;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("actionType=").appendValue(actionType);
+            }
+        };
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 97f02cb..dc3a12f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Binder;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -637,7 +638,7 @@
 
     @Test
     public void testAddsDebugEntries() {
-        long startTime = System.currentTimeMillis();
+        long startTime = SystemClock.elapsedRealtime();
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setAddDebugEntries(true);
         ArrayList<BinderCallsStats.ExportedCallStat> callStats = bcs.getExportedCallStats();
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index f26dfad..b65c1e6 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -442,11 +442,13 @@
         LooperStats.ExportedEntry debugEntry1 = entries.get(1);
         assertThat(debugEntry1.handlerClassName).isEqualTo("");
         assertThat(debugEntry1.messageName).isEqualTo("__DEBUG_start_time_millis");
-        assertThat(debugEntry1.totalLatencyMicros).isEqualTo(looperStats.getStartTimeMillis());
+        assertThat(debugEntry1.totalLatencyMicros).isEqualTo(
+                looperStats.getStartElapsedTimeMillis());
         LooperStats.ExportedEntry debugEntry2 = entries.get(2);
         assertThat(debugEntry2.handlerClassName).isEqualTo("");
         assertThat(debugEntry2.messageName).isEqualTo("__DEBUG_end_time_millis");
-        assertThat(debugEntry2.totalLatencyMicros).isAtLeast(looperStats.getStartTimeMillis());
+        assertThat(debugEntry2.totalLatencyMicros).isAtLeast(
+                looperStats.getStartElapsedTimeMillis());
         LooperStats.ExportedEntry debugEntry3 = entries.get(3);
         assertThat(debugEntry3.handlerClassName).isEqualTo("");
         assertThat(debugEntry3.messageName).isEqualTo("__DEBUG_battery_time_millis");
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 933e54e..928351e 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,13 +16,13 @@
 
 package android.os;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.TestCase;
 
-import android.os.SystemProperties;
-import android.test.suitebuilder.annotation.SmallTest;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class SystemPropertiesTest extends TestCase {
     private static final String KEY = "sys.testkey";
@@ -188,4 +188,25 @@
             fail("InterruptedException");
         }
     }
+
+    @SmallTest
+    public void testDigestOf() {
+        final String empty = SystemProperties.digestOf();
+        final String finger = SystemProperties.digestOf("ro.build.fingerprint");
+        final String fingerBrand = SystemProperties.digestOf(
+                "ro.build.fingerprint", "ro.product.brand");
+        final String brandFinger = SystemProperties.digestOf(
+                "ro.product.brand", "ro.build.fingerprint");
+
+        // Shouldn't change over time
+        assertTrue(Objects.equals(finger, SystemProperties.digestOf("ro.build.fingerprint")));
+
+        // Different properties means different results
+        assertFalse(Objects.equals(empty, finger));
+        assertFalse(Objects.equals(empty, fingerBrand));
+        assertFalse(Objects.equals(finger, fingerBrand));
+
+        // Same properties means same result
+        assertTrue(Objects.equals(fingerBrand, brandFinger));
+    }
 }
diff --git a/data/etc/Android.mk b/data/etc/Android.mk
index d24c140a..61ef426 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -50,6 +50,25 @@
 
 ########################
 include $(CLEAR_VARS)
+LOCAL_MODULE := privapp_whitelist_com.android.settings
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_MODULE_STEM := com.android.settings.xml
+LOCAL_SRC_FILES := com.android.settings.xml
+include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
+LOCAL_MODULE := privapp_whitelist_com.android.systemui
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_MODULE_STEM := com.android.systemui.xml
+LOCAL_SRC_FILES := com.android.systemui.xml
+include $(BUILD_PREBUILT)
+
+
+########################
+include $(CLEAR_VARS)
 LOCAL_MODULE := com.android.timezone.updater.xml
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_RELATIVE_PATH := permissions
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index bbec474..ea66ee3 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
new file mode 100644
index 0000000..2110a8f
--- /dev/null
+++ b/data/etc/com.android.settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.settings">
+        <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
+        <permission name="android.permission.ACCESS_NOTIFICATIONS"/>
+        <permission name="android.permission.BACKUP"/>
+        <permission name="android.permission.BATTERY_STATS"/>
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
+        <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
+        <permission name="android.permission.CHANGE_CONFIGURATION"/>
+        <permission name="android.permission.DELETE_PACKAGES"/>
+        <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+        <permission name="android.permission.MANAGE_DEBUGGING"/>
+        <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+        <permission name="android.permission.MANAGE_FINGERPRINT"/>
+        <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
+        <permission name="android.permission.MASTER_CLEAR"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+        <permission name="android.permission.MOVE_PACKAGE"/>
+        <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+        <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.SET_TIME"/>
+        <permission name="android.permission.STATUS_BAR"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
+        <permission name="android.permission.USER_ACTIVITY"/>
+        <permission name="android.permission.WRITE_APN_SETTINGS"/>
+        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
new file mode 100644
index 0000000..b65bc1d
--- /dev/null
+++ b/data/etc/com.android.systemui.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.systemui">
+        <permission name="android.permission.BATTERY_STATS"/>
+        <permission name="android.permission.BIND_APPWIDGET"/>
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
+        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+        <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
+        <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
+        <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
+        <permission name="android.permission.CONTROL_VPN"/>
+        <permission name="android.permission.DUMP"/>
+        <permission name="android.permission.GET_APP_OPS_STATS"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
+        <permission name="android.permission.MANAGE_DEBUGGING"/>
+        <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
+        <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MASTER_CLEAR"/>
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+        <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+        <permission name="android.permission.READ_DREAM_STATE"/>
+        <permission name="android.permission.READ_FRAME_BUFFER"/>
+        <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.REAL_GET_TASKS"/>
+        <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+        <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
+        <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
+        <permission name="android.permission.STATUS_BAR"/>
+        <permission name="android.permission.STOP_APP_SWITCHES"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.USE_RESERVED_DISK"/>
+        <permission name="android.permission.WRITE_DREAM_STATE"/>
+        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+        <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index dcf95fd..af570b3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -250,42 +250,6 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.settings">
-        <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
-        <permission name="android.permission.ACCESS_NOTIFICATIONS"/>
-        <permission name="android.permission.BACKUP"/>
-        <permission name="android.permission.BATTERY_STATS"/>
-        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
-        <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
-        <permission name="android.permission.CHANGE_CONFIGURATION"/>
-        <permission name="android.permission.DELETE_PACKAGES"/>
-        <permission name="android.permission.FORCE_STOP_PACKAGES"/>
-        <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
-        <permission name="android.permission.MANAGE_DEBUGGING"/>
-        <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
-        <permission name="android.permission.MANAGE_FINGERPRINT"/>
-        <permission name="android.permission.MANAGE_USB"/>
-        <permission name="android.permission.MANAGE_USERS"/>
-        <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
-        <permission name="android.permission.MASTER_CLEAR"/>
-        <permission name="android.permission.MODIFY_PHONE_STATE"/>
-        <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
-        <permission name="android.permission.MOVE_PACKAGE"/>
-        <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
-        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
-        <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
-        <permission name="android.permission.REBOOT"/>
-        <permission name="android.permission.SET_TIME"/>
-        <permission name="android.permission.STATUS_BAR"/>
-        <permission name="android.permission.TETHER_PRIVILEGED"/>
-        <permission name="android.permission.USE_RESERVED_DISK"/>
-        <permission name="android.permission.USER_ACTIVITY"/>
-        <permission name="android.permission.WRITE_APN_SETTINGS"/>
-        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
-        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
-    </privapp-permissions>
-
     <privapp-permissions package="com.android.settings.intelligence">
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
@@ -370,50 +334,6 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.systemui">
-        <permission name="android.permission.BATTERY_STATS"/>
-        <permission name="android.permission.BIND_APPWIDGET"/>
-        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
-        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
-        <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
-        <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
-        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
-        <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
-        <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
-        <permission name="android.permission.CONTROL_VPN"/>
-        <permission name="android.permission.DUMP"/>
-        <permission name="android.permission.GET_APP_OPS_STATS"/>
-        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-        <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
-        <permission name="android.permission.MANAGE_DEBUGGING"/>
-        <permission name="android.permission.MANAGE_USB"/>
-        <permission name="android.permission.MANAGE_USERS"/>
-        <permission name="android.permission.MASTER_CLEAR"/>
-        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
-        <permission name="android.permission.MODIFY_PHONE_STATE"/>
-        <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
-        <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
-        <permission name="android.permission.READ_DREAM_STATE"/>
-        <permission name="android.permission.READ_FRAME_BUFFER"/>
-        <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
-        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
-        <permission name="android.permission.REAL_GET_TASKS"/>
-        <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
-        <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
-        <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
-        <permission name="android.permission.STATUS_BAR"/>
-        <permission name="android.permission.STOP_APP_SWITCHES"/>
-        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
-        <permission name="android.permission.TETHER_PRIVILEGED"/>
-        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
-        <permission name="android.permission.USE_RESERVED_DISK"/>
-        <permission name="android.permission.WRITE_DREAM_STATE"/>
-        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
-        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
-        <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
-    </privapp-permissions>
-
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/docs/html/reference/images/graphics/blendmode_CLEAR.png b/docs/html/reference/images/graphics/blendmode_CLEAR.png
new file mode 100644
index 0000000..979782a
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_CLEAR.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_COLOR.png b/docs/html/reference/images/graphics/blendmode_COLOR.png
new file mode 100644
index 0000000..2f41bfb
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_COLOR.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_COLOR_BURN.png b/docs/html/reference/images/graphics/blendmode_COLOR_BURN.png
new file mode 100644
index 0000000..26059ce
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_COLOR_BURN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_COLOR_DODGE.png b/docs/html/reference/images/graphics/blendmode_COLOR_DODGE.png
new file mode 100644
index 0000000..922f1d9
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_COLOR_DODGE.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DARKEN.png b/docs/html/reference/images/graphics/blendmode_DARKEN.png
new file mode 100644
index 0000000..6c04aa3
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DARKEN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DIFFERENCE.png b/docs/html/reference/images/graphics/blendmode_DIFFERENCE.png
new file mode 100644
index 0000000..aab2bcb
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DIFFERENCE.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DST.png b/docs/html/reference/images/graphics/blendmode_DST.png
new file mode 100644
index 0000000..16f96b4
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DST.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DST_ATOP.png b/docs/html/reference/images/graphics/blendmode_DST_ATOP.png
new file mode 100644
index 0000000..d0ae2cd
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DST_ATOP.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DST_IN.png b/docs/html/reference/images/graphics/blendmode_DST_IN.png
new file mode 100644
index 0000000..9befe27
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DST_IN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DST_OUT.png b/docs/html/reference/images/graphics/blendmode_DST_OUT.png
new file mode 100644
index 0000000..e9227e9f
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DST_OUT.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_DST_OVER.png b/docs/html/reference/images/graphics/blendmode_DST_OVER.png
new file mode 100644
index 0000000..015be0a
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_DST_OVER.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_EXCLUSION.png b/docs/html/reference/images/graphics/blendmode_EXCLUSION.png
new file mode 100644
index 0000000..307dec9
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_EXCLUSION.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_HARD_LIGHT.png b/docs/html/reference/images/graphics/blendmode_HARD_LIGHT.png
new file mode 100644
index 0000000..3b62b98
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_HARD_LIGHT.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_HUE.png b/docs/html/reference/images/graphics/blendmode_HUE.png
new file mode 100644
index 0000000..012bd33
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_HUE.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_LIGHTEN.png b/docs/html/reference/images/graphics/blendmode_LIGHTEN.png
new file mode 100644
index 0000000..1c3be65
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_LIGHTEN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_LUMINOSITY.png b/docs/html/reference/images/graphics/blendmode_LUMINOSITY.png
new file mode 100644
index 0000000..3549082
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_LUMINOSITY.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_MODULATE.png b/docs/html/reference/images/graphics/blendmode_MODULATE.png
new file mode 100644
index 0000000..ed1b59d
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_MODULATE.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_MULTIPLY.png b/docs/html/reference/images/graphics/blendmode_MULTIPLY.png
new file mode 100644
index 0000000..c8c7ccb
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_MULTIPLY.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_OVERLAY.png b/docs/html/reference/images/graphics/blendmode_OVERLAY.png
new file mode 100644
index 0000000..6962f92
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_OVERLAY.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_PLUS.png b/docs/html/reference/images/graphics/blendmode_PLUS.png
new file mode 100644
index 0000000..015fa2b
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_PLUS.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SATURATION.png b/docs/html/reference/images/graphics/blendmode_SATURATION.png
new file mode 100644
index 0000000..5ac96c3
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SATURATION.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SCREEN.png b/docs/html/reference/images/graphics/blendmode_SCREEN.png
new file mode 100644
index 0000000..d2d70d2
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SCREEN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SOFT_LIGHT.png b/docs/html/reference/images/graphics/blendmode_SOFT_LIGHT.png
new file mode 100644
index 0000000..89fbacd
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SOFT_LIGHT.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SRC.png b/docs/html/reference/images/graphics/blendmode_SRC.png
new file mode 100644
index 0000000..990ec94
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SRC.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SRC_ATOP.png b/docs/html/reference/images/graphics/blendmode_SRC_ATOP.png
new file mode 100644
index 0000000..d574dfd
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SRC_ATOP.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SRC_IN.png b/docs/html/reference/images/graphics/blendmode_SRC_IN.png
new file mode 100644
index 0000000..dda45d7
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SRC_IN.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SRC_OUT.png b/docs/html/reference/images/graphics/blendmode_SRC_OUT.png
new file mode 100644
index 0000000..f5d43c1
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SRC_OUT.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_SRC_OVER.png b/docs/html/reference/images/graphics/blendmode_SRC_OVER.png
new file mode 100644
index 0000000..b1a405b
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_SRC_OVER.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/blendmode_XOR.png b/docs/html/reference/images/graphics/blendmode_XOR.png
new file mode 100644
index 0000000..31c110d
--- /dev/null
+++ b/docs/html/reference/images/graphics/blendmode_XOR.png
Binary files differ
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 3db240b..ca9dc47 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -241,10 +241,22 @@
         nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
     }
 
+    /**
+     * @deprecated use {@link Canvas#drawColor(int, BlendMode)}
+     */
+    @Deprecated
     public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
         nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
     }
 
+    /**
+     * Make lint happy.
+     * See {@link Canvas#drawColor(int, BlendMode)}
+     */
+    public void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+        nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode);
+    }
+
     public void drawLine(float startX, float startY, float stopX, float stopY,
             @NonNull Paint paint) {
         throwIfHasHwBitmapInSwMode(paint);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 4de7ca7..901c211 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -201,12 +201,21 @@
         nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
     }
 
+    /**
+     * @deprecated use {@link #drawColor(int, BlendMode)} instead
+     */
+    @Deprecated
     @Override
     public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
         nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
     }
 
     @Override
+    public final void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+        nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode);
+    }
+
+    @Override
     public final void drawLine(float startX, float startY, float stopX, float stopY,
             @NonNull Paint paint) {
         nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java
new file mode 100644
index 0000000..39392c8
--- /dev/null
+++ b/graphics/java/android/graphics/BlendMode.java
@@ -0,0 +1,472 @@
+/*
+ * 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.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public enum BlendMode {
+
+    /**
+     * <p>
+     *  <img src="{@docRoot}reference/android/images/graphics/blendmode_CLEAR.png" />
+     *  <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = 0\)</p>
+     * <p>\(C_{out} = 0\)</p>
+     */
+    CLEAR(0),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC.png" />
+     *     <figcaption>The source pixels replace the destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+     * <p>\(C_{out} = C_{src}\)</p>
+     */
+    SRC(1),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DST.png" />
+     *     <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{dst}\)</p>
+     */
+    DST(2),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OVER.png" />
+     *     <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+     */
+    SRC_OVER(3),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OVER.png" />
+     *     <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+     * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+     */
+    DST_OVER(4),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_IN.png" />
+     *     <figcaption>Keeps the source pixels that cover the destination pixels,
+     *     discards the remaining source and destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
+     */
+    SRC_IN(5),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_IN.png" />
+     *     <figcaption>Keeps the destination pixels that cover source pixels,
+     *     discards the remaining source and destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
+     */
+    DST_IN(6),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OUT.png" />
+     *     <figcaption>Keeps the source pixels that do not cover destination pixels.
+     *     Discards source pixels that cover destination pixels. Discards all
+     *     destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+     * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
+     */
+    SRC_OUT(7),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OUT.png" />
+     *     <figcaption>Keeps the destination pixels that are not covered by source pixels.
+     *     Discards destination pixels that are covered by source pixels. Discards all
+     *     source pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
+     */
+    DST_OUT(8),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_ATOP.png" />
+     *     <figcaption>Discards the source pixels that do not cover destination pixels.
+     *     Draws remaining source pixels over destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+     * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+     */
+    SRC_ATOP(9),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_ATOP.png" />
+     *     <figcaption>Discards the destination pixels that are not covered by source pixels.
+     *     Draws remaining destination pixels over source pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+     * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+     */
+    DST_ATOP(10),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_XOR.png" />
+     *     <figcaption>Discards the source and destination pixels where source pixels
+     *     cover destination pixels. Draws remaining source pixels.</figcaption>
+     * </p>
+     * <p>
+     *     \(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)
+     * </p>
+     * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+     */
+    XOR(11),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_PLUS.png" />
+     *     <figcaption>Adds the source pixels to the destination pixels and saturates
+     *     the result.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
+     * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
+     */
+    PLUS(12),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" />
+     *     <figcaption>Multiplies the source and destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{src} * C_{dst}\)</p>
+     *
+     */
+    MODULATE(13),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SCREEN.png" />
+     *     <figcaption>
+     *         Adds the source and destination pixels, then subtracts the
+     *         source pixels multiplied by the destination.
+     *     </figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
+     */
+    SCREEN(14),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_OVERLAY.png" />
+     *     <figcaption>
+     *         Multiplies or screens the source and destination depending on the
+     *         destination color.
+     *     </figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(\begin{equation}
+     * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
+     * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) &
+     * otherwise \end{cases}
+     * \end{equation}\)</p>
+     */
+    OVERLAY(15),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DARKEN.png" />
+     *     <figcaption>
+     *         Retains the smallest component of the source and
+     *         destination pixels.
+     *     </figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>
+     *     \(C_{out} =
+     *     (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)
+     * </p>
+     */
+    DARKEN(16),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_LIGHTEN.png" />
+     *     <figcaption>Retains the largest component of the source and
+     *     destination pixel.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>
+     *     \(C_{out} =
+     *      (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)
+     * </p>
+     */
+    LIGHTEN(17),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_DODGE.png" />
+     *     <figcaption>Makes destination brighter to reflect source.</figcaption>
+     * </p>
+     * <p>
+     *     \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+     * </p>
+     * <p>
+     *      \begin{equation}
+     *      C_{out} =
+     *      \begin{cases}
+     *          C_{src} * (1 - \alpha_{dst}) & C_{dst} = 0 \\
+     *          C_{src} + \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = \alpha_{src} \\
+     *          \alpha_{src} * min(\alpha_{dst}, C_{dst} * \alpha_{src}/(\alpha_{src} - C_{src}))
+     *              + C_{src} *(1 - \alpha_{dst} + \alpha_{dst}*(1 - \alpha_{src}) & otherwise
+     *      \end{cases}
+     *      \end{equation}
+     * </p>
+     */
+    COLOR_DODGE(18),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_BURN.png" />
+     *     <figcaption>Makes destination darker to reflect source.</figcaption>
+     * </p>
+     * <p>
+     *     \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *     C_{out} =
+     *     \begin{cases}
+     *         C_{dst} + C_{src}*(1 - \alpha_{dst}) & C_{dst} = \alpha_{dst} \\
+     *         \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = 0 \\
+     *         \alpha_{src}*(\alpha_{dst} - min(\alpha_{dst}, (\alpha_{dst}
+     *         - C_{dst})*\alpha_{src}/C_{src}))
+     *         + C_{src} * (1 - \alpha_{dst}) + \alpha_{dst}*(1-\alpha_{src}) & otherwise
+     *     \end{cases}
+     *     \end{equation}
+     * </p>
+     */
+    COLOR_BURN(19),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_HARD_LIGHT.png" />
+     *     <figcaption>Makes destination lighter or darker, depending on source.</figcaption>
+     * </p>
+     * <p>
+     *     \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *      C_{out} =
+     *      \begin{cases}
+     *           2*C_{src}*C_{dst} & C_{src}*(1-\alpha_{dst}) + C_{dst}*(1-\alpha_{src}) + 2*C_{src}
+     *              \leq \alpha_{src} \\
+     *           \alpha_{src}*\alpha_{dst}- 2*(\alpha_{dst} - C_{dst})*(\alpha_{src} - C_{src})
+     *              & otherwise
+     *      \end{cases}
+     *      \end{equation}
+     * </p>
+     */
+    HARD_LIGHT(20),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SOFT_LIGHT.png" />
+     *     <figcaption>Makes destination lighter or darker, depending on source.</figcaption>
+     * </p>
+     * <p>
+     *     Where
+     *       \begin{equation}
+     *       m =
+     *          \begin{cases}
+     *              C_{dst} / \alpha_{dst} & \alpha_{dst} \gt 0 \\
+     *              0 & otherwise
+     *          \end{cases}
+     *       \end{equation}
+     * </p>
+     * <p>
+     *       \begin{equation}
+     *       g =
+     *          \begin{cases}
+     *              (16 * m * m + 4 * m) * (m - 1) + 7 * m & 4 * C_{dst} \leq \alpha_{dst} \\
+     *              \sqrt m - m & otherwise
+     *          \end{cases}
+     *       \end{equation}
+     * </p>
+     * <p>
+     *       \begin{equation}
+     *       f =
+     *          \begin{cases}
+     *              C_{dst} * (\alpha_{src} + (2 * C_{src} - \alpha_{src}) * (1 - m))
+     *                  & 2 * C_{src} \leq \alpha_{src} \\
+     *              C_{dst} * \alpha_{src} + \alpha_{dst} * (2 * C_{src} - \alpha_{src}) * g
+     *                  & otherwise
+     *          \end{cases}
+     *       \end{equation}
+     * </p>
+     * <p>
+     *       \begin{equation}
+     *          \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+     *       \end{equation}
+     *       \begin{equation}
+     *          C_{out} = C_{src} / \alpha_{dst} + C_{dst} / \alpha_{src} + f
+     *       \end{equation}
+     * </p>
+     */
+    SOFT_LIGHT(21),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" />
+     *     <figcaption>Subtracts darker from lighter with higher contrast.</figcaption>
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *          \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+     *     \end{equation}
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *           C_{out} = C_{src} + C_{dst} - 2 * min(C_{src}
+     *                       * \alpha_{dst}, C_{dst} * \alpha_{src})
+     *     \end{equation}
+     * </p>
+     */
+    DIFFERENCE(22),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" />
+     *     <figcaption>Subtracts darker from lighter with lower contrast.</figcaption>
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *          \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+     *     \end{equation}
+     * </p>
+     * <p>
+     *     \begin{equation}
+     *          C_{out} = C_{src} + C_{dst} - 2 * C_{src} * C_{dst}
+     *     \end{equation}
+     * </p>
+     */
+    EXCLUSION(23),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" />
+     *     <figcaption>Multiplies the source and destination pixels.</figcaption>
+     * </p>
+     * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+     * <p>\(C_{out} =
+     *      C_{src} * (1 - \alpha_{dst}) + C_{dst} * (1 - \alpha_{src}) + (C_{src} * C_{dst})\)
+     * </p>
+     */
+    MULTIPLY(24),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_HUE.png" />
+     *     <figcaption>
+     *         Replaces hue of destination with hue of source, leaving saturation
+     *         and luminosity unchanged.
+     *     </figcaption>
+     * </p>
+     */
+    HUE(25),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_SATURATION.png" />
+     *     <figcaption>
+     *          Replaces saturation of destination saturation hue of source, leaving hue and
+     *          luminosity unchanged.
+     *     </figcaption>
+     * </p>
+     */
+    SATURATION(26),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR.png" />
+     *     <figcaption>
+     *          Replaces hue and saturation of destination with hue and saturation of source,
+     *          leaving luminosity unchanged.
+     *     </figcaption>
+     * </p>
+     */
+    COLOR(27),
+
+    /**
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/blendmode_LUMINOSITY.png" />
+     *     <figcaption>
+     *          Replaces luminosity of destination with luminosity of source, leaving hue and
+     *          saturation unchanged.
+     *     </figcaption>
+     * </p>
+     */
+    LUMINOSITY(28);
+
+    private static final BlendMode[] BLEND_MODES = values();
+
+    /**
+     * @hide
+     */
+    public static @Nullable BlendMode fromValue(int value) {
+        for (BlendMode mode : BLEND_MODES) {
+            if (mode.mXfermode.porterDuffMode == value) {
+                return mode;
+            }
+        }
+        return null;
+    }
+
+    @NonNull
+    private final Xfermode mXfermode;
+
+    BlendMode(int mode) {
+        mXfermode = new Xfermode();
+        mXfermode.porterDuffMode = mode;
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public Xfermode getXfermode() {
+        return mXfermode;
+    }
+}
diff --git a/graphics/java/android/graphics/BlendModeColorFilter.java b/graphics/java/android/graphics/BlendModeColorFilter.java
new file mode 100644
index 0000000..7caeb42
--- /dev/null
+++ b/graphics/java/android/graphics/BlendModeColorFilter.java
@@ -0,0 +1,84 @@
+/*
+ * 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.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+
+/**
+ * A color filter that can be used to tint the source pixels using a single
+ * color and a specific {@link BlendMode}.
+ */
+public final class BlendModeColorFilter extends ColorFilter {
+
+    @ColorInt final int mColor;
+    private final BlendMode mMode;
+
+    public BlendModeColorFilter(@ColorInt int color, @NonNull BlendMode mode) {
+        mColor = color;
+        mMode = mode;
+    }
+
+
+    /**
+     * Returns the ARGB color used to tint the source pixels when this filter
+     * is applied.
+     *
+     * @see Color
+     *
+     */
+    @ColorInt
+    public int getColor() {
+        return mColor;
+    }
+
+    /**
+     * Returns the Porter-Duff mode used to composite this color filter's
+     * color with the source pixel when this filter is applied.
+     *
+     * @see BlendMode
+     *
+     */
+    public BlendMode getMode() {
+        return mMode;
+    }
+
+    @Override
+    long createNativeInstance() {
+        return native_CreateBlendModeFilter(mColor, mMode.getXfermode().porterDuffMode);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null || getClass() != object.getClass()) {
+            return false;
+        }
+        final BlendModeColorFilter other = (BlendModeColorFilter) object;
+        return other.mMode == mMode;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 *  mMode.hashCode() + mColor;
+    }
+
+    private static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
+
+}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 135c137..6798ab2 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1684,12 +1684,26 @@
      *
      * @param color the color to draw with
      * @param mode the porter-duff mode to apply to the color
+     *
+     * @deprecated use {@link #drawColor(int, BlendMode)} instead
      */
+    @Deprecated
     public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
         super.drawColor(color, mode);
     }
 
     /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
+     * blendmode.
+     *
+     * @param color the color to draw with
+     * @param mode the blendmode to apply to the color
+     */
+    public void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+        super.drawColor(color, mode);
+    }
+
+    /**
      * Draw a line segment with the specified start and stop x,y coordinates, using the specified
      * paint.
      * <p>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 69ff3bc..6821282 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1168,12 +1168,29 @@
      * Get the paint's transfer mode object.
      *
      * @return the paint's transfer mode (or null)
+     *
+     * @deprecated use {@link #getBlendMode()} instead
      */
+    @Deprecated
     public Xfermode getXfermode() {
         return mXfermode;
     }
 
     /**
+     * Get the paint's blend mode object.
+     *
+     * @return the paint's blend mode (or null)
+     */
+    @Nullable
+    public BlendMode getBlendMode() {
+        if (mXfermode == null) {
+            return null;
+        } else {
+            return BlendMode.fromValue(mXfermode.porterDuffMode);
+        }
+    }
+
+    /**
      * Set or clear the transfer mode object. A transfer mode defines how
      * source pixels (generate by a drawing command) are composited with
      * the destination pixels (content of the render target).
@@ -1185,8 +1202,17 @@
      *
      * @param xfermode May be null. The xfermode to be installed in the paint
      * @return         xfermode
+     *
+     * @deprecated Use {@link #setBlendMode} to apply a Xfermode directly
+     * through usage of {@link BlendMode}
      */
+    @Deprecated
     public Xfermode setXfermode(Xfermode xfermode) {
+        return installXfermode(xfermode);
+    }
+
+    @Nullable
+    private Xfermode installXfermode(Xfermode xfermode) {
         int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
         int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
         if (newMode != curMode) {
@@ -1197,6 +1223,23 @@
     }
 
     /**
+     * Set or clear the blend mode. A blend mode defines how source pixels
+     * (generated by a drawing command) are composited with the destination pixels
+     * (content of the render target).
+     * <p />
+     * Pass null to clear any previous blend mode.
+     * As a convenience, the parameter passed is also returned.
+     * <p />
+     *
+     * @see BlendMode
+     *
+     * @param blendmode May be null. The blend mode to be installed in the paint
+     */
+    public void setBlendMode(@Nullable BlendMode blendmode) {
+        installXfermode(blendmode != null ? blendmode.getXfermode() : null);
+    }
+
+    /**
      * Get the paint's patheffect object.
      *
      * @return the paint's patheffect (or null)
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 6665220..c2a8eb7 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -23,7 +23,11 @@
 /**
  * A color filter that can be used to tint the source pixels using a single
  * color and a specific {@link PorterDuff Porter-Duff composite mode}.
+ *
+ * @deprecated Consider using {@link BlendModeColorFilter} instead as it supports a wider
+ * set of blend modes than those defined in {@link PorterDuff.Mode}
  */
+@Deprecated
 public class PorterDuffColorFilter extends ColorFilter {
     @ColorInt
     private int mColor;
@@ -71,7 +75,7 @@
 
     @Override
     long createNativeInstance() {
-        return native_CreatePorterDuffFilter(mColor, mMode.nativeInt);
+        return native_CreateBlendModeFilter(mColor, mMode.nativeInt);
     }
 
     @Override
@@ -91,5 +95,5 @@
         return 31 *  mMode.hashCode() + mColor;
     }
 
-    private static native long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode);
+    private static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
 }
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index d6f08b9..3b1d44b 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -446,7 +446,21 @@
     }
 
     /**
-     * Sets the clip bounds of the RenderNode.
+     * Gets whether or not a compositing layer is forced to be used. The default & recommended
+     * is false, as it is typically faster to avoid using compositing layers.
+     * See {@link #setUseCompositingLayer(boolean, Paint)}.
+     *
+     * @return true if a compositing layer is forced, false otherwise
+     */
+    public boolean getUseCompositingLayer() {
+        return nGetLayerType(mNativeRenderNode) != 0;
+    }
+
+    /**
+     * Sets the clip bounds of the RenderNode. If null, the clip bounds is removed from the
+     * RenderNode. If non-null, the RenderNode will be clipped to this rect. If
+     * {@link #setClipToBounds(boolean)} is true, then the RenderNode will be clipped to the
+     * intersection of this rectangle and the bounds of the render node.
      *
      * @param rect the bounds to clip to. If null, the clip bounds are reset
      * @return True if the clip bounds changed, false otherwise
@@ -460,16 +474,30 @@
     }
 
     /**
-     * Set whether the Render node should clip itself to its bounds. This property is controlled by
-     * the view's parent.
+     * Set whether the Render node should clip itself to its bounds. This defaults to true,
+     * and is useful to the renderer in enable quick-rejection of chunks of the tree as well as
+     * better partial invalidation support. Clipping can be further restricted or controlled
+     * through the combination of this property as well as {@link #setClipBounds(Rect)}, which
+     * allows for a different clipping rectangle to be used in addition to or instead of the
+     * {@link #setLeftTopRightBottom(int, int, int, int)} or the RenderNode.
      *
-     * @param clipToBounds true if the display list should clip to its bounds
+     * @param clipToBounds true if the display list should clip to its bounds, false otherwise.
      */
     public boolean setClipToBounds(boolean clipToBounds) {
         return nSetClipToBounds(mNativeRenderNode, clipToBounds);
     }
 
     /**
+     * Returns whether or not the RenderNode is clipping to its bounds. See
+     * {@link #setClipToBounds(boolean)} and {@link #setLeftTopRightBottom(int, int, int, int)}
+     *
+     * @return true if the render node clips to its bounds, false otherwise.
+     */
+    public boolean getClipToBounds() {
+        return nGetClipToBounds(mNativeRenderNode);
+    }
+
+    /**
      * Sets whether the RenderNode should be drawn immediately after the
      * closest ancestor RenderNode containing a projection receiver.
      *
@@ -1339,12 +1367,18 @@
     private static native boolean nSetLayerType(long renderNode, int layerType);
 
     @CriticalNative
+    private static native int nGetLayerType(long renderNode);
+
+    @CriticalNative
     private static native boolean nSetLayerPaint(long renderNode, long paint);
 
     @CriticalNative
     private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
 
     @CriticalNative
+    private static native boolean nGetClipToBounds(long renderNode);
+
+    @CriticalNative
     private static native boolean nSetClipBounds(long renderNode, int left, int top,
             int right, int bottom);
 
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 5bd59d4..09d0a58 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -595,7 +595,12 @@
      * <p class="note"><strong>Note:</strong> Setting a color filter disables
      * {@link #setTintList(ColorStateList) tint}.
      * </p>
+     *
+     * @see {@link #setColorFilter(ColorFilter)} }
+     * @deprecated use {@link #setColorFilter(ColorFilter)} with an instance
+     * of {@link android.graphics.BlendModeColorFilter}
      */
+    @Deprecated
     public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
         if (getColorFilter() instanceof PorterDuffColorFilter) {
             PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
new file mode 100644
index 0000000..a58b47f
--- /dev/null
+++ b/libs/androidfw/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "libandroidfw_tests",
+      "host": true
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 441356b..22d587a 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -365,6 +365,6 @@
 // structs with size fields (like Res_value, ResTable_entry) should be
 // backwards and forwards compatible (aka checking the size field against
 // sizeof(Res_value) might not be backwards compatible.
-TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+// TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
 
 }  // namespace android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d22eaf3..6585bfc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -60,6 +60,7 @@
         "libutils",
         "libEGL",
         "libGLESv2",
+        "libGLESv3",
         "libvulkan",
         "libui",
         "libgui",
@@ -207,6 +208,7 @@
         "FrameInfoVisualizer.cpp",
         "GpuMemoryTracker.cpp",
         "HardwareBitmapUploader.cpp",
+        "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
         "Layer.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 0b9d82b..ed167e5 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -20,6 +20,7 @@
 
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
+#include <ui/GraphicTypes.h>
 
 #include <mutex>
 #include <thread>
@@ -61,6 +62,50 @@
     return displayInfo;
 }
 
+static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
+                                          sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
+    if (Properties::isolatedProcess) {
+        *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+        *colorSpace = SkColorSpace::MakeSRGB();
+        *colorType = SkColorType::kN32_SkColorType;
+        return;
+    }
+    ui::Dataspace defaultDataspace, wcgDataspace;
+    ui::PixelFormat defaultPixelFormat, wcgPixelFormat;
+    status_t status =
+        SurfaceComposerClient::getCompositionPreference(&defaultDataspace, &defaultPixelFormat,
+                                                        &wcgDataspace, &wcgPixelFormat);
+    LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status);
+    switch (wcgDataspace) {
+        case ui::Dataspace::DISPLAY_P3:
+            *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut;
+            *colorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+                                                SkColorSpace::Gamut::kDCIP3_D65_Gamut);
+            break;
+        case ui::Dataspace::V0_SCRGB:
+            *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+            *colorSpace = SkColorSpace::MakeSRGB();
+            break;
+        case ui::Dataspace::V0_SRGB:
+            // when sRGB is returned, it means wide color gamut is not supported.
+            *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+            *colorSpace = SkColorSpace::MakeSRGB();
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+    }
+    switch (wcgPixelFormat) {
+        case ui::PixelFormat::RGBA_8888:
+            *colorType = SkColorType::kN32_SkColorType;
+            break;
+        case ui::PixelFormat::RGBA_FP16:
+            *colorType = SkColorType::kRGBA_F16_SkColorType;
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Unreachable: unsupported pixel format.");
+    }
+}
+
 DeviceInfo::DeviceInfo() {
 #if HWUI_NULL_GPU
         mMaxTextureSize = NULL_GPU_MAX_TEXTURE_SIZE;
@@ -68,6 +113,7 @@
         mMaxTextureSize = -1;
 #endif
     mDisplayInfo = QueryDisplayInfo();
+    queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType);
 }
 
 int DeviceInfo::maxTextureSize() const {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 5956215..9bcc8e8 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -16,6 +16,7 @@
 #ifndef DEVICEINFO_H
 #define DEVICEINFO_H
 
+#include <SkImageInfo.h>
 #include <ui/DisplayInfo.h>
 
 #include "utils/Macros.h"
@@ -37,6 +38,9 @@
     // context or if you are using the HWUI_NULL_GPU
     int maxTextureSize() const;
     const DisplayInfo& displayInfo() const { return mDisplayInfo; }
+    SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; }
+    sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
+    SkColorType getWideColorType() const { return mWideColorType; }
 
 private:
     friend class renderthread::RenderThread;
@@ -46,6 +50,9 @@
 
     int mMaxTextureSize;
     DisplayInfo mDisplayInfo;
+    SkColorSpace::Gamut mWideColorGamut;
+    sk_sp<SkColorSpace> mWideColorSpace;
+    SkColorType mWideColorType;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/HWUIProperties.sysprop b/libs/hwui/HWUIProperties.sysprop
new file mode 100644
index 0000000..42191ca
--- /dev/null
+++ b/libs/hwui/HWUIProperties.sysprop
@@ -0,0 +1,9 @@
+owner: Platform
+module: "android.uirenderer"
+prop {
+    api_name: "use_vulkan"
+    type: Boolean
+    prop_name: "ro.hwui.use_vulkan"
+    scope: Public
+    access: Readonly
+}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 8067313..046ffc4 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -18,6 +18,7 @@
 #include "Debug.h"
 #include "DeviceInfo.h"
 #include "SkTraceEventCommon.h"
+#include "HWUIProperties.sysprop.h"
 
 #include <algorithm>
 #include <cstdlib>
@@ -174,8 +175,13 @@
     if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
         return sRenderPipelineType;
     }
+    bool useVulkan = use_vulkan().value_or(false);
     char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_RENDERER, prop, "skiagl");
+    if (useVulkan) {
+        property_get(PROPERTY_RENDERER, prop, "skiavk");
+    } else {
+        property_get(PROPERTY_RENDERER, prop, "skiagl");
+    }
     if (!strcmp(prop, "skiavk")) {
         ALOGD("Skia Vulkan Pipeline");
         sRenderPipelineType = RenderPipelineType::SkiaVulkan;
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 35cf707..de8777b 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -65,6 +65,7 @@
     void applyColorTransform(ColorTransform transform);
 
     bool hasText() const { return mHasText; }
+    size_t usedSize() const { return fUsed; }
 
 private:
     friend class RecordingCanvas;
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 04379ae..ddb7e4e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -98,15 +98,15 @@
 
     LayerProperties& operator=(const LayerProperties& other);
 
+    // Strongly recommend using effectiveLayerType instead
+    LayerType type() const { return mType; }
+
 private:
     LayerProperties();
     ~LayerProperties();
     void reset();
     bool setColorFilter(SkColorFilter* filter);
 
-    // Private since external users should go through properties().effectiveLayerType()
-    LayerType type() const { return mType; }
-
     friend class RenderProperties;
 
     LayerType mType = LayerType::None;
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index e2ea2bc..a09da6b 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -52,7 +52,7 @@
         const SkScalar left = x;
         const SkScalar right = x + length;
         if (flags & SkPaint::kUnderlineText_ReserveFlag) {
-            Paint::FontMetrics metrics;
+            SkFontMetrics metrics;
             paint.getFontMetrics(&metrics);
             SkScalar position;
             if (!metrics.hasUnderlinePosition(&position)) {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index d7879e7..45f3a4c 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -49,7 +49,7 @@
  */
 class SkiaDisplayList {
 public:
-    size_t getUsedSize() { return allocator.usedSize(); }
+    size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); }
 
     ~SkiaDisplayList() {
         /* Given that we are using a LinearStdAllocator to store some of the
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 07979a2..4338b1c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -162,22 +162,17 @@
         mEglSurface = EGL_NO_SURFACE;
     }
 
+    setSurfaceColorProperties(colorMode);
+
     if (surface) {
         mRenderThread.requireGlContext();
-        auto newSurface = mEglManager.createSurface(surface, colorMode);
+        auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut);
         if (!newSurface) {
             return false;
         }
         mEglSurface = newSurface.unwrap();
     }
 
-    if (colorMode == ColorMode::SRGB) {
-        mSurfaceColorType = SkColorType::kN32_SkColorType;
-    } else if (colorMode == ColorMode::WideColorGamut) {
-        mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
-    }
-    mSurfaceColorSpace = SkColorSpace::MakeSRGB();
-
     if (mEglSurface != EGL_NO_SURFACE) {
         const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
         mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 7a255c1..7f62ab5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -17,6 +17,7 @@
 #include "SkiaPipeline.h"
 
 #include <SkImageEncoder.h>
+#include <SkImageInfo.h>
 #include <SkImagePriv.h>
 #include <SkOverdrawCanvas.h>
 #include <SkOverdrawColorFilter.h>
@@ -453,6 +454,20 @@
     ALOGD("%s", log.c_str());
 }
 
+void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
+    if (colorMode == ColorMode::SRGB) {
+        mSurfaceColorType = SkColorType::kN32_SkColorType;
+        mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+        mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+    } else if (colorMode == ColorMode::WideColorGamut) {
+        mSurfaceColorType = DeviceInfo::get()->getWideColorType();
+        mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut();
+        mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
+    } else {
+        LOG_ALWAYS_FATAL("Unreachable: unsupported color mode.");
+    }
+}
+
 // Overdraw debugging
 
 // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 42a411a..af58f63 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -107,9 +107,11 @@
 
 protected:
     void dumpResourceCacheUsage() const;
+    void setSurfaceColorProperties(renderthread::ColorMode colorMode);
 
     renderthread::RenderThread& mRenderThread;
     SkColorType mSurfaceColorType;
+    SkColorSpace::Gamut mSurfaceColorGamut;
     sk_sp<SkColorSpace> mSurfaceColorSpace;
 
 private:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 437b5dc..65ae0dd 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -122,15 +122,10 @@
         mVkSurface = nullptr;
     }
 
-    mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+    setSurfaceColorProperties(colorMode);
     if (surface) {
-        mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace);
-    }
-
-    if (colorMode == ColorMode::SRGB) {
-        mSurfaceColorType = SkColorType::kN32_SkColorType;
-    } else if (colorMode == ColorMode::WideColorGamut) {
-        mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+        mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace,
+                                              mSurfaceColorGamut, mSurfaceColorType);
     }
 
     return mVkSurface != nullptr;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 8230dfd..56eedff 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -89,7 +89,8 @@
         , mEglConfigWideGamut(nullptr)
         , mEglContext(EGL_NO_CONTEXT)
         , mPBufferSurface(EGL_NO_SURFACE)
-        , mCurrentSurface(EGL_NO_SURFACE) {}
+        , mCurrentSurface(EGL_NO_SURFACE)
+        , mHasWideColorGamutSupport(false) {}
 
 EglManager::~EglManager() {
     destroy();
@@ -128,6 +129,81 @@
     createContext();
     createPBufferSurface();
     makeCurrent(mPBufferSurface, nullptr, /* force */ true);
+
+    SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut();
+    bool hasWideColorSpaceExtension = false;
+    if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+        hasWideColorSpaceExtension = EglExtensions.displayP3;
+    } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+        hasWideColorSpaceExtension = EglExtensions.scRGB;
+    } else {
+        LOG_ALWAYS_FATAL("Unsupported wide color space.");
+    }
+    mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension &&
+                                mEglConfigWideGamut != EGL_NO_CONFIG_KHR;
+}
+
+EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
+    EGLint eglSwapBehavior =
+            (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+    EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+                        EGL_OPENGL_ES2_BIT,
+                        EGL_RED_SIZE,
+                        8,
+                        EGL_GREEN_SIZE,
+                        8,
+                        EGL_BLUE_SIZE,
+                        8,
+                        EGL_ALPHA_SIZE,
+                        8,
+                        EGL_DEPTH_SIZE,
+                        0,
+                        EGL_CONFIG_CAVEAT,
+                        EGL_NONE,
+                        EGL_STENCIL_SIZE,
+                        STENCIL_BUFFER_SIZE,
+                        EGL_SURFACE_TYPE,
+                        EGL_WINDOW_BIT | eglSwapBehavior,
+                        EGL_NONE};
+    EGLConfig config = EGL_NO_CONFIG_KHR;
+    EGLint numConfigs = 1;
+    if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) ||
+        numConfigs != 1) {
+        return EGL_NO_CONFIG_KHR;
+    }
+    return config;
+}
+
+EGLConfig EglManager::loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior) {
+    EGLint eglSwapBehavior =
+            (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+    // If we reached this point, we have a valid swap behavior
+    EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+                        EGL_OPENGL_ES2_BIT,
+                        EGL_COLOR_COMPONENT_TYPE_EXT,
+                        EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
+                        EGL_RED_SIZE,
+                        16,
+                        EGL_GREEN_SIZE,
+                        16,
+                        EGL_BLUE_SIZE,
+                        16,
+                        EGL_ALPHA_SIZE,
+                        16,
+                        EGL_DEPTH_SIZE,
+                        0,
+                        EGL_STENCIL_SIZE,
+                        STENCIL_BUFFER_SIZE,
+                        EGL_SURFACE_TYPE,
+                        EGL_WINDOW_BIT | eglSwapBehavior,
+                        EGL_NONE};
+    EGLConfig config = EGL_NO_CONFIG_KHR;
+    EGLint numConfigs = 1;
+    if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) ||
+        numConfigs != 1) {
+        return EGL_NO_CONFIG_KHR;
+    }
+    return config;
 }
 
 void EglManager::initExtensions() {
@@ -146,12 +222,8 @@
     EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace");
     EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context");
     EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float");
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
-    EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb_linear");
-#else
     EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb");
-#endif
-    EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3");
+    EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough");
     EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority");
     EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
 }
@@ -162,77 +234,35 @@
 
 void EglManager::loadConfigs() {
     ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
-    EGLint swapBehavior =
-            (mSwapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
 
     // Note: The default pixel format is RGBA_8888, when other formats are
     // available, we should check the target pixel format and configure the
     // attributes list properly.
-    EGLint attribs[] = {EGL_RENDERABLE_TYPE,
-                        EGL_OPENGL_ES2_BIT,
-                        EGL_RED_SIZE,
-                        8,
-                        EGL_GREEN_SIZE,
-                        8,
-                        EGL_BLUE_SIZE,
-                        8,
-                        EGL_ALPHA_SIZE,
-                        8,
-                        EGL_DEPTH_SIZE,
-                        0,
-                        EGL_CONFIG_CAVEAT,
-                        EGL_NONE,
-                        EGL_STENCIL_SIZE,
-                        STENCIL_BUFFER_SIZE,
-                        EGL_SURFACE_TYPE,
-                        EGL_WINDOW_BIT | swapBehavior,
-                        EGL_NONE};
-
-    EGLint numConfigs = 1;
-    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs) ||
-        numConfigs != 1) {
+    mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior);
+    if (mEglConfig == EGL_NO_CONFIG_KHR) {
         if (mSwapBehavior == SwapBehavior::Preserved) {
             // Try again without dirty regions enabled
             ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
             mSwapBehavior = SwapBehavior::Discard;
-            loadConfigs();
-            return;  // the call to loadConfigs() we just made picks the wide gamut config
+            ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
+            mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior);
         } else {
             // Failed to get a valid config
             LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString());
         }
     }
+    SkColorType wideColorType = DeviceInfo::get()->getWideColorType();
 
-    if (EglExtensions.pixelFormatFloat) {
-        // If we reached this point, we have a valid swap behavior
-        EGLint attribs16F[] = {EGL_RENDERABLE_TYPE,
-                               EGL_OPENGL_ES2_BIT,
-                               EGL_COLOR_COMPONENT_TYPE_EXT,
-                               EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
-                               EGL_RED_SIZE,
-                               16,
-                               EGL_GREEN_SIZE,
-                               16,
-                               EGL_BLUE_SIZE,
-                               16,
-                               EGL_ALPHA_SIZE,
-                               16,
-                               EGL_DEPTH_SIZE,
-                               0,
-                               EGL_STENCIL_SIZE,
-                               STENCIL_BUFFER_SIZE,
-                               EGL_SURFACE_TYPE,
-                               EGL_WINDOW_BIT | swapBehavior,
-                               EGL_NONE};
-
-        numConfigs = 1;
-        if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs,
-                             &numConfigs) ||
-            numConfigs != 1) {
+    // When we reach this point, we have a valid swap behavior
+    if (wideColorType == SkColorType::kRGBA_F16_SkColorType && EglExtensions.pixelFormatFloat) {
+        mEglConfigWideGamut = loadFP16Config(mEglDisplay, mSwapBehavior);
+        if (mEglConfigWideGamut == EGL_NO_CONFIG_KHR) {
             ALOGE("Device claims wide gamut support, cannot find matching config, error = %s",
                     eglErrorString());
             EglExtensions.pixelFormatFloat = false;
         }
+    } else if (wideColorType == SkColorType::kN32_SkColorType) {
+        mEglConfigWideGamut = load8BitsConfig(mEglDisplay, mSwapBehavior);
     }
 }
 
@@ -263,11 +293,12 @@
     }
 }
 
-Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
+Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
+                                                     ColorMode colorMode,
+                                                     SkColorSpace::Gamut colorGamut) {
     LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
 
-    bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace &&
-                          EglExtensions.scRGB && EglExtensions.pixelFormatFloat &&
+    bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport &&
                           EglExtensions.noConfigContext;
 
     // The color space we want to use depends on whether linear blending is turned
@@ -285,8 +316,8 @@
     // When wide gamut rendering is on we cannot rely on the GPU performing
     // linear blending for us. We use two different color spaces to tag the
     // surface appropriately for SurfaceFlinger:
-    // - Gamma blending (default) requires the use of the scRGB-nl color space
-    // - Linear blending requires the use of the scRGB color space
+    // - Gamma blending (default) requires the use of the non-linear color space
+    // - Linear blending requires the use of the linear color space
 
     // Not all Android targets support the EGL_GL_COLORSPACE_KHR extension
     // We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value.
@@ -296,19 +327,20 @@
 
     if (EglExtensions.glColorSpace) {
         attribs[0] = EGL_GL_COLORSPACE_KHR;
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
         if (wideColorGamut) {
-            attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
-        } else {
-            attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR;
-        }
-#else
-        if (wideColorGamut) {
-            attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+            switch (colorGamut) {
+                case SkColorSpace::Gamut::kDCIP3_D65_Gamut:
+                    attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+                    break;
+                case SkColorSpace::Gamut::kSRGB_Gamut:
+                    attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+                    break;
+                default:
+                    LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+            }
         } else {
             attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
         }
-#endif
     }
 
     EGLSurface surface = eglCreateWindowSurface(
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 2a44f7e..4dd9096 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -48,7 +48,8 @@
 
     bool hasEglContext();
 
-    Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode);
+    Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode,
+                                             SkColorSpace::Gamut colorGamut);
     void destroySurface(EGLSurface surface);
 
     void destroy();
@@ -80,6 +81,14 @@
     status_t createReleaseFence(bool useFenceSync, EGLSyncKHR* eglFence, sp<Fence>& nativeFence);
 
 private:
+    enum class SwapBehavior {
+        Discard,
+        Preserved,
+        BufferAge,
+    };
+
+    static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior);
+    static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior);
 
     void initExtensions();
     void createPBufferSurface();
@@ -93,12 +102,7 @@
     EGLContext mEglContext;
     EGLSurface mPBufferSurface;
     EGLSurface mCurrentSurface;
-
-    enum class SwapBehavior {
-        Discard,
-        Preserved,
-        BufferAge,
-    };
+    bool mHasWideColorGamutSupport;
     SwapBehavior mSwapBehavior = SwapBehavior::Discard;
 };
 
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 4be8bd9..aa7a141 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -473,8 +473,10 @@
     if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) {
         ColorMode colorMode = surface->mColorMode;
         sk_sp<SkColorSpace> colorSpace = surface->mColorSpace;
+        SkColorSpace::Gamut colorGamut = surface->mColorGamut;
+        SkColorType colorType = surface->mColorType;
         destroySurface(surface);
-        *surfaceOut = createSurface(window, colorMode, colorSpace);
+        *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType);
         surface = *surfaceOut;
     }
 
@@ -647,8 +649,7 @@
         VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i];
         imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget(
                 mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin,
-                surface->mColorMode == ColorMode::WideColorGamut ? kRGBA_F16_SkColorType
-                : kRGBA_8888_SkColorType, surface->mColorSpace, &props);
+                surface->mColorType, surface->mColorSpace, &props);
     }
 
     SkASSERT(mCommandPool != VK_NULL_HANDLE);
@@ -767,10 +768,20 @@
 
     VkFormat surfaceFormat = VK_FORMAT_R8G8B8A8_UNORM;
     VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
-    if (surface->mColorMode == ColorMode::WideColorGamut) {
+    if (surface->mColorType == SkColorType::kRGBA_F16_SkColorType) {
         surfaceFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
-        colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
     }
+
+    if (surface->mColorMode == ColorMode::WideColorGamut) {
+        if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+            colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
+        } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+            colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT;
+        } else {
+            LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+        }
+    }
+
     bool foundSurfaceFormat = false;
     for (uint32_t i = 0; i < surfaceFormatCount; ++i) {
         if (surfaceFormat == surfaceFormats[i].format
@@ -840,14 +851,17 @@
 }
 
 VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode,
-        sk_sp<SkColorSpace> surfaceColorSpace) {
+                                            sk_sp<SkColorSpace> surfaceColorSpace,
+                                            SkColorSpace::Gamut surfaceColorGamut,
+                                            SkColorType surfaceColorType) {
     initialize();
 
     if (!window) {
         return nullptr;
     }
 
-    VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace);
+    VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace,
+                                               surfaceColorGamut, surfaceColorType);
 
     VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
     memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index d67d2c8..69ca23a 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -38,8 +38,10 @@
 
 class VulkanSurface {
 public:
-    VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace)
-            : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace) {}
+    VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace,
+                  SkColorSpace::Gamut colorGamut, SkColorType colorType)
+            : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace),
+              mColorGamut(colorGamut), mColorType(colorType) {}
 
     sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
 
@@ -80,6 +82,8 @@
     int mWindowWidth = 0;
     int mWindowHeight = 0;
     sk_sp<SkColorSpace> mColorSpace;
+    SkColorSpace::Gamut mColorGamut;
+    SkColorType mColorType;
 };
 
 // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue,
@@ -98,7 +102,9 @@
     // Given a window this creates a new VkSurfaceKHR and VkSwapchain and stores them inside a new
     // VulkanSurface object which is returned.
     VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode,
-            sk_sp<SkColorSpace> surfaceColorSpace);
+                                 sk_sp<SkColorSpace> surfaceColorSpace,
+                                 SkColorSpace::Gamut surfaceColorGamut,
+                                 SkColorType surfaceColorType);
 
     // Destroy the VulkanSurface and all associated vulkan objects.
     void destroySurface(VulkanSurface* surface);
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
index 0d87776..d189a93 100644
--- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -76,7 +76,7 @@
                             paint.setStrokeWidth(strokeWidth);
                             // fill column with each op
                             int middleCount = canvas.save(SaveFlags::MatrixClip);
-                            for (auto op : ops) {
+                            for (const auto& op : ops) {
                                 int innerCount = canvas.save(SaveFlags::MatrixClip);
                                 canvas.clipRect(0, 0, cellSize, cellSize, SkClipOp::kIntersect);
                                 canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index a6073eb..75fb0ef 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -306,6 +306,7 @@
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
             CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+    canvasContext->setSurface(nullptr);
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     LayerUpdateQueue layerUpdateQueue;
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index ee6beba..b11eaa9 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -251,7 +251,7 @@
 }
 
 TEST(PathParser, parseStringForData) {
-    for (TestData testData : sTestDataSet) {
+    for (const TestData& testData : sTestDataSet) {
         PathParser::ParseResult result;
         // Test generated path data against the given data.
         PathData pathData;
@@ -271,7 +271,7 @@
 }
 
 TEST(VectorDrawableUtils, createSkPathFromPathData) {
-    for (TestData testData : sTestDataSet) {
+    for (const TestData& testData : sTestDataSet) {
         SkPath expectedPath;
         testData.skPathLamda(&expectedPath);
         SkPath actualPath;
@@ -281,7 +281,7 @@
 }
 
 TEST(PathParser, parseAsciiStringForSkPath) {
-    for (TestData testData : sTestDataSet) {
+    for (const TestData& testData : sTestDataSet) {
         PathParser::ParseResult result;
         size_t length = strlen(testData.pathString);
         // Check the return value as well as the SkPath generated.
@@ -304,8 +304,8 @@
 }
 
 TEST(VectorDrawableUtils, morphPathData) {
-    for (TestData fromData : sTestDataSet) {
-        for (TestData toData : sTestDataSet) {
+    for (const TestData& fromData : sTestDataSet) {
+        for (const TestData& toData : sTestDataSet) {
             bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
             if (fromData.pathData == toData.pathData) {
                 EXPECT_TRUE(canMorph);
@@ -319,8 +319,8 @@
 
 TEST(VectorDrawableUtils, interpolatePathData) {
     // Interpolate path data with itself and every other path data
-    for (TestData fromData : sTestDataSet) {
-        for (TestData toData : sTestDataSet) {
+    for (const TestData& fromData : sTestDataSet) {
+        for (const TestData& toData : sTestDataSet) {
             PathData outData;
             bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
                                                                     toData.pathData, 0.5);
@@ -331,7 +331,7 @@
 
     float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
     // Now try to interpolate with a slightly modified version of self and expect success
-    for (TestData fromData : sTestDataSet) {
+    for (const TestData& fromData : sTestDataSet) {
         PathData toPathData = fromData.pathData;
         for (size_t i = 0; i < toPathData.points.size(); i++) {
             toPathData.points[i]++;
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 32c7520..05d49e5 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -88,6 +88,10 @@
     boolean providerMeetsCriteria(String provider, in Criteria criteria);
     ProviderProperties getProviderProperties(String provider);
     String getNetworkProviderPackage();
+    void setLocationControllerExtraPackage(String packageName);
+    String getLocationControllerExtraPackage();
+    void setLocationControllerExtraPackageEnabled(boolean enabled);
+    boolean isLocationControllerExtraPackageEnabled();
 
     boolean isProviderEnabledForUser(String provider, int userId);
     boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 334170e..1cd3d86 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2396,4 +2396,65 @@
             return null;
         }
     }
+
+    /**
+     * Set the extra location controller package for location services on the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setLocationControllerExtraPackage(String packageName) {
+        try {
+            mService.setLocationControllerExtraPackage(packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the extra location controller package on the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @Nullable String getLocationControllerExtraPackage() {
+        try {
+            return mService.getLocationControllerExtraPackage();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
+     * Set whether the extra location controller package is currently enabled on the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setLocationControllerExtraPackageEnabled(boolean enabled) {
+        try {
+            mService.setLocationControllerExtraPackageEnabled(enabled);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether extra location controller package is currently enabled on the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isLocationControllerExtraPackageEnabled() {
+        try {
+            return mService.isLocationControllerExtraPackageEnabled();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
 }
diff --git a/media/java/android/media/DataSourceCallback.java b/media/java/android/media/DataSourceCallback.java
index 0d4d531..1afcd20 100644
--- a/media/java/android/media/DataSourceCallback.java
+++ b/media/java/android/media/DataSourceCallback.java
@@ -47,7 +47,7 @@
      * @param offset the offset within buffer to read the data into.
      * @param size the number of bytes to read.
      * @throws IOException on fatal errors.
-     * @return the number of bytes read, or -1 if there was an error.
+     * @return the number of bytes read, or -1 if end of stream is reached.
      */
     public abstract int readAt(long position, byte[] buffer, int offset, int size)
             throws IOException;
diff --git a/media/java/android/media/MediaDataSource.java b/media/java/android/media/MediaDataSource.java
index 4ba2120..4bdc1ad 100644
--- a/media/java/android/media/MediaDataSource.java
+++ b/media/java/android/media/MediaDataSource.java
@@ -46,7 +46,7 @@
      * @param offset the offset within buffer to read the data into.
      * @param size the number of bytes to read.
      * @throws IOException on fatal errors.
-     * @return the number of bytes read, or -1 if there was an error.
+     * @return the number of bytes read, or -1 if end of stream is reached.
      */
     public abstract int readAt(long position, byte[] buffer, int offset, int size)
             throws IOException;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 18d36eb..0057875 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
 import android.content.ContentProvider;
@@ -1680,37 +1679,6 @@
     public native boolean isPlaying();
 
     /**
-     * Gets the current buffering management params used by the source component.
-     * Calling it only after {@code setDataSource} has been called.
-     * Each type of data source might have different set of default params.
-     *
-     * @return the current buffering management params used by the source component.
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized, or {@code setDataSource} has not been called.
-     * @hide
-     */
-    @NonNull
-    @TestApi
-    public native BufferingParams getBufferingParams();
-
-    /**
-     * Sets buffering management params.
-     * The object sets its internal BufferingParams to the input, except that the input is
-     * invalid or not supported.
-     * Call it only after {@code setDataSource} has been called.
-     * The input is a hint to MediaPlayer.
-     *
-     * @param params the buffering management params.
-     *
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized or has been released, or {@code setDataSource} has not been called.
-     * @throws IllegalArgumentException if params is invalid or not supported.
-     * @hide
-     */
-    @TestApi
-    public native void setBufferingParams(@NonNull BufferingParams params);
-
-    /**
      * Change playback speed of audio by resampling the audio.
      * <p>
      * Specifies resampling as audio mode for variable rate playback, i.e.,
diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java
deleted file mode 100644
index a426552..0000000
--- a/media/java/android/media/MediaPlayerBase.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- * Base class for all media players that want media session.
- */
-public abstract class MediaPlayerBase implements AutoCloseable {
-    /**
-     * @hide
-     */
-    @IntDef({
-        PLAYER_STATE_IDLE,
-        PLAYER_STATE_PAUSED,
-        PLAYER_STATE_PLAYING,
-        PLAYER_STATE_ERROR })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PlayerState {}
-
-    /**
-     * @hide
-     */
-    @IntDef({
-        BUFFERING_STATE_UNKNOWN,
-        BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
-        BUFFERING_STATE_BUFFERING_AND_STARVED,
-        BUFFERING_STATE_BUFFERING_COMPLETE })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface BuffState {}
-
-    /**
-     * State when the player is idle, and needs configuration to start playback.
-     */
-    public static final int PLAYER_STATE_IDLE = 0;
-
-    /**
-     * State when the player's playback is paused
-     */
-    public static final int PLAYER_STATE_PAUSED = 1;
-
-    /**
-     * State when the player's playback is ongoing
-     */
-    public static final int PLAYER_STATE_PLAYING = 2;
-
-    /**
-     * State when the player is in error state and cannot be recovered self.
-     */
-    public static final int PLAYER_STATE_ERROR = 3;
-
-    /**
-     * Buffering state is unknown.
-     */
-    public static final int BUFFERING_STATE_UNKNOWN = 0;
-
-    /**
-     * Buffering state indicating the player is buffering but enough has been buffered
-     * for this player to be able to play the content.
-     * See {@link #getBufferedPosition()} for how far is buffered already.
-     */
-    public static final int BUFFERING_STATE_BUFFERING_AND_PLAYABLE = 1;
-
-    /**
-     * Buffering state indicating the player is buffering, but the player is currently starved
-     * for data, and cannot play.
-     */
-    public static final int BUFFERING_STATE_BUFFERING_AND_STARVED = 2;
-
-    /**
-     * Buffering state indicating the player is done buffering, and the remainder of the content is
-     * available for playback.
-     */
-    public static final int BUFFERING_STATE_BUFFERING_COMPLETE = 3;
-
-    /**
-     * Starts or resumes playback.
-     */
-    public abstract void play();
-
-    /**
-     * Prepares the player for playback.
-     * See {@link PlayerEventCallback#onMediaPrepared(MediaPlayerBase, DataSourceDesc)} for being
-     * notified when the preparation phase completed. During this time, the player may allocate
-     * resources required to play, such as audio and video decoders.
-     */
-    public abstract void prepare();
-
-    /**
-     * Pauses playback.
-     */
-    public abstract void pause();
-
-    /**
-     * Resets the MediaPlayerBase to its uninitialized state.
-     */
-    public abstract void reset();
-
-    /**
-     *
-     */
-    public abstract void skipToNext();
-
-    /**
-     * Moves the playback head to the specified position
-     * @param pos the new playback position expressed in ms.
-     */
-    public abstract void seekTo(long pos);
-
-    public static final long UNKNOWN_TIME = -1;
-
-    /**
-     * Gets the current playback head position.
-     * @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown.
-     */
-    public long getCurrentPosition() { return UNKNOWN_TIME; }
-
-    /**
-     * Returns the duration of the current data source, or {@link #UNKNOWN_TIME} if unknown.
-     * @return the duration in ms, or {@link #UNKNOWN_TIME}.
-     */
-    public long getDuration() { return UNKNOWN_TIME; }
-
-    /**
-     * Gets the buffered position of current playback, or {@link #UNKNOWN_TIME} if unknown.
-     * @return the buffered position in ms, or {@link #UNKNOWN_TIME}.
-     */
-    public long getBufferedPosition() { return UNKNOWN_TIME; }
-
-    /**
-     * Returns the current player state.
-     * See also {@link PlayerEventCallback#onPlayerStateChanged(MediaPlayerBase, int)} for
-     * notification of changes.
-     * @return the current player state
-     */
-    public abstract @PlayerState int getPlayerState();
-
-    /**
-     * Returns the current buffering state of the player.
-     * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
-     * buffered.
-     * @return the buffering state.
-     */
-    public abstract @BuffState int getBufferingState();
-
-    /**
-     * Sets the {@link AudioAttributes} to be used during the playback of the media.
-     *
-     * @param attributes non-null <code>AudioAttributes</code>.
-     */
-    public abstract void setAudioAttributes(@NonNull AudioAttributes attributes);
-
-    /**
-     * Returns AudioAttributes that media player has.
-     */
-    public abstract @Nullable AudioAttributes getAudioAttributes();
-
-    /**
-     * Sets the data source to be played.
-     * @param dsd
-     */
-    public abstract void setDataSource(@NonNull DataSourceDesc dsd);
-
-    /**
-     * Sets the data source that will be played immediately after the current one is done playing.
-     * @param dsd
-     */
-    public abstract void setNextDataSource(@NonNull DataSourceDesc dsd);
-
-    /**
-     * Sets the list of data sources that will be sequentially played after the current one. Each
-     * data source is played immediately after the previous one is done playing.
-     * @param dsds
-     */
-    public abstract void setNextDataSources(@NonNull List<DataSourceDesc> dsds);
-
-    /**
-     * Returns the current data source.
-     * @return the current data source, or null if none is set, or none available to play.
-     */
-    public abstract @Nullable DataSourceDesc getCurrentDataSource();
-
-    /**
-     * Configures the player to loop on the current data source.
-     * @param loop true if the current data source is meant to loop.
-     */
-    public abstract void loopCurrent(boolean loop);
-
-    /**
-     * Sets the playback speed.
-     * A value of 1.0f is the default playback value.
-     * A negative value indicates reverse playback, check {@link #isReversePlaybackSupported()}
-     * before using negative values.<br>
-     * After changing the playback speed, it is recommended to query the actual speed supported
-     * by the player, see {@link #getPlaybackSpeed()}.
-     * @param speed
-     */
-    public abstract void setPlaybackSpeed(float speed);
-
-    /**
-     * Returns the actual playback speed to be used by the player when playing.
-     * Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
-     * @return the actual playback speed
-     */
-    public float getPlaybackSpeed() { return 1.0f; }
-
-    /**
-     * Indicates whether reverse playback is supported.
-     * Reverse playback is indicated by negative playback speeds, see
-     * {@link #setPlaybackSpeed(float)}.
-     * @return true if reverse playback is supported.
-     */
-    public boolean isReversePlaybackSupported() { return false; }
-
-    /**
-     * Sets the volume of the audio of the media to play, expressed as a linear multiplier
-     * on the audio samples.
-     * Note that this volume is specific to the player, and is separate from stream volume
-     * used across the platform.<br>
-     * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
-     * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
-     * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
-     */
-    public abstract void setPlayerVolume(float volume);
-
-    /**
-     * Returns the current volume of this player to this player.
-     * Note that it does not take into account the associated stream volume.
-     * @return the player volume.
-     */
-    public abstract float getPlayerVolume();
-
-    /**
-     * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
-     */
-    public float getMaxPlayerVolume() { return 1.0f; }
-
-    /**
-     * Adds a callback to be notified of events for this player.
-     * @param e the {@link Executor} to be used for the events.
-     * @param cb the callback to receive the events.
-     */
-    public abstract void registerPlayerEventCallback(@NonNull Executor e,
-            @NonNull PlayerEventCallback cb);
-
-    /**
-     * Removes a previously registered callback for player events
-     * @param cb the callback to remove
-     */
-    public abstract void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb);
-
-    /**
-     * A callback class to receive notifications for events on the media player.
-     * See {@link MediaPlayerBase#registerPlayerEventCallback(Executor, PlayerEventCallback)} to
-     * register this callback.
-     */
-    public static abstract class PlayerEventCallback {
-        /**
-         * Called when the player's current data source has changed.
-         *
-         * @param mpb the player whose data source changed.
-         * @param dsd the new current data source. null, if no more data sources available.
-         */
-        public void onCurrentDataSourceChanged(@NonNull MediaPlayerBase mpb,
-                @Nullable DataSourceDesc dsd) { }
-        /**
-         * Called when the player is <i>prepared</i>, i.e. it is ready to play the content
-         * referenced by the given data source.
-         * @param mpb the player that is prepared.
-         * @param dsd the data source that the player is prepared to play.
-         */
-        public void onMediaPrepared(@NonNull MediaPlayerBase mpb, @NonNull DataSourceDesc dsd) { }
-
-        /**
-         * Called to indicate that the state of the player has changed.
-         * See {@link MediaPlayerBase#getPlayerState()} for polling the player state.
-         * @param mpb the player whose state has changed.
-         * @param state the new state of the player.
-         */
-        public void onPlayerStateChanged(@NonNull MediaPlayerBase mpb, @PlayerState int state) { }
-
-        /**
-         * Called to report buffering events for a data source.
-         * @param mpb the player that is buffering
-         * @param dsd the data source for which buffering is happening.
-         * @param state the new buffering state.
-         */
-        public void onBufferingStateChanged(@NonNull MediaPlayerBase mpb,
-                @NonNull DataSourceDesc dsd, @BuffState int state) { }
-
-        /**
-         * Called to indicate that the playback speed has changed.
-         * @param mpb the player that has changed the playback speed.
-         * @param speed the new playback speed.
-         */
-        public void onPlaybackSpeedChanged(@NonNull MediaPlayerBase mpb, float speed) { }
-
-        /**
-         * Called to indicate that {@link #seekTo(long)} is completed.
-         *
-         * @param mpb the player that has completed seeking.
-         * @param position the previous seeking request.
-         * @see #seekTo(long)
-         */
-        public void onSeekCompleted(@NonNull MediaPlayerBase mpb, long position) { }
-    }
-
-}
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 7480fa0..52e9ae1 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -25,10 +25,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
+
 import java.lang.ref.WeakReference;
-import java.nio.ByteOrder;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -225,35 +229,12 @@
      * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
      * enumeration.
      */
-    public static class Descriptor {
+    public static final class Descriptor implements Parcelable {
 
         public Descriptor() {
         }
 
         /**
-         * @param type          UUID identifying the effect type. May be one of:
-         * {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
-         * {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
-         * {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
-         * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
-         * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
-         * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
-         * @param uuid         UUID for this particular implementation
-         * @param connectMode  {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
-         * @param name         human readable effect name
-         * @param implementor  human readable effect implementor name
-        *
-        */
-        public Descriptor(String type, String uuid, String connectMode,
-                String name, String implementor) {
-            this.type = UUID.fromString(type);
-            this.uuid = UUID.fromString(uuid);
-            this.connectMode = connectMode;
-            this.name = name;
-            this.implementor = implementor;
-        }
-
-        /**
          *  Indicates the generic type of the effect (Equalizer, Bass boost ...).
          *  One of {@link AudioEffect#EFFECT_TYPE_AEC},
          *  {@link AudioEffect#EFFECT_TYPE_AGC}, {@link AudioEffect#EFFECT_TYPE_BASS_BOOST},
@@ -289,7 +270,86 @@
          * Human readable effect implementor name
          */
         public String implementor;
-    };
+
+        /**
+         * @param type          UUID identifying the effect type. May be one of:
+         * {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
+         * {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
+         * {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
+         * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
+         * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
+         * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
+         * @param uuid         UUID for this particular implementation
+         * @param connectMode  {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
+         * @param name         human readable effect name
+         * @param implementor  human readable effect implementor name
+        *
+        */
+        public Descriptor(String type, String uuid, String connectMode,
+                String name, String implementor) {
+            this.type = UUID.fromString(type);
+            this.uuid = UUID.fromString(uuid);
+            this.connectMode = connectMode;
+            this.name = name;
+            this.implementor = implementor;
+        }
+
+        private Descriptor(Parcel in) {
+            type = UUID.fromString(in.readString());
+            uuid = UUID.fromString(in.readString());
+            connectMode = in.readString();
+            name = in.readString();
+            implementor = in.readString();
+        }
+
+        public static final Parcelable.Creator<Descriptor> CREATOR =
+                new Parcelable.Creator<Descriptor>() {
+                    /**
+                     * Rebuilds a Descriptor previously stored with writeToParcel().
+                     * @param p Parcel object to read the Descriptor from
+                     * @return a new Descriptor created from the data in the parcel
+                     */
+                    public Descriptor createFromParcel(Parcel p) {
+                        return new Descriptor(p);
+                    }
+                    public Descriptor[] newArray(int size) {
+                        return new Descriptor[size];
+                    }
+        };
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type, uuid, connectMode, name, implementor);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(type.toString());
+            dest.writeString(uuid.toString());
+            dest.writeString(connectMode);
+            dest.writeString(name);
+            dest.writeString(implementor);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || !(o instanceof Descriptor)) return false;
+
+            Descriptor that = (Descriptor) o;
+
+            return (type.equals(that.type)
+                    && uuid.equals(that.uuid)
+                    && connectMode.equals(that.connectMode)
+                    && name.equals(that.name)
+                    && implementor.equals(that.implementor));
+        }
+    }
 
     /**
      * Effect connection mode is insert. Specifying an audio session ID when creating the effect
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bd0019f..bfc05fa 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -39,7 +39,7 @@
     void destroy();
 
     // These commands are for the TransportPerformer
-    void setMetadata(in MediaMetadata metadata);
+    void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
     void setPlaybackState(in PlaybackState state);
     void setQueue(in ParceledListSlice queue);
     void setQueueTitle(CharSequence title);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index d43cd30..8962bb7 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -30,6 +30,7 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -40,7 +41,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
 import android.util.Log;
@@ -434,11 +434,21 @@
      * @see android.media.MediaMetadata.Builder#putBitmap
      */
     public void setMetadata(@Nullable MediaMetadata metadata) {
+        long duration = -1;
+        int fields = 0;
+        MediaDescription description = null;
         if (metadata != null) {
             metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+            if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+                duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            }
+            fields = metadata.size();
+            description = metadata.getDescription();
         }
+        String metadataDescription = "size=" + fields + ", description=" + description;
+
         try {
-            mBinder.setMetadata(metadata);
+            mBinder.setMetadata(metadata, duration, metadataDescription);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
         }
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 5dd01b0..76bbce7 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -39,7 +39,6 @@
 #include "utils/Errors.h"  // for status_t
 #include "utils/KeyedVector.h"
 #include "utils/String8.h"
-#include "android_media_BufferingParams.h"
 #include "android_media_MediaDataSource.h"
 #include "android_media_MediaMetricsJNI.h"
 #include "android_media_PlaybackParams.h"
@@ -94,7 +93,6 @@
 };
 static fields_t fields;
 
-static BufferingParams::fields_t gBufferingParamsFields;
 static PlaybackParams::fields_t gPlaybackParamsFields;
 static SyncParams::fields_t gSyncParamsFields;
 static VolumeShaperHelper::fields_t gVolumeShaperFields;
@@ -370,50 +368,6 @@
     setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
 }
 
-static jobject
-android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    BufferingParams bp;
-    BufferingSettings &settings = bp.settings;
-    process_media_player_call(
-            env, thiz, mp->getBufferingSettings(&settings),
-            "java/lang/IllegalStateException", "unexpected error");
-    if (env->ExceptionCheck()) {
-        return nullptr;
-    }
-    ALOGV("getBufferingSettings:{%s}", settings.toString().string());
-
-    return bp.asJobject(env, gBufferingParamsFields);
-}
-
-static void
-android_media_MediaPlayer_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
-{
-    if (params == NULL) {
-        return;
-    }
-
-    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    BufferingParams bp;
-    bp.fillFromJobject(env, gBufferingParamsFields, params);
-    ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
-
-    process_media_player_call(
-            env, thiz, mp->setBufferingSettings(bp.settings),
-            "java/lang/IllegalStateException", "unexpected error");
-}
-
 static void
 android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
 {
@@ -976,8 +930,6 @@
 
     env->DeleteLocalRef(clazz);
 
-    gBufferingParamsFields.init(env);
-
     // Modular DRM
     FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
     if (clazz) {
@@ -1426,8 +1378,6 @@
     {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
     {"_setDataSource",      "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
     {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)android_media_MediaPlayer_setVideoSurface},
-    {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams},
-    {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams},
     {"_prepare",            "()V",                              (void *)android_media_MediaPlayer_prepare},
     {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},
     {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
new file mode 100644
index 0000000..8d48a58
--- /dev/null
+++ b/native/webview/plat_support/draw_fn.h
@@ -0,0 +1,197 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+//******************************************************************************
+// This is a copy of the coresponding android_webview/public/browser header.
+// Any changes to the interface should be made there as well.
+//******************************************************************************
+
+#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_
+#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_
+
+#include <vulkan/vulkan.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// In order to make small changes backwards compatible, all structs passed from
+// android to chromium are versioned.
+//
+// 1 is Android Q. This matches kAwDrawGLInfoVersion version 3.
+static const int kAwDrawFnVersion = 1;
+
+struct AwDrawFn_OnSyncParams {
+  int version;
+
+  bool apply_force_dark;
+};
+
+struct AwDrawFn_DrawGLParams {
+  int version;
+
+  // Input: current clip rect in surface coordinates. Reflects the current state
+  // of the OpenGL scissor rect. Both the OpenGL scissor rect and viewport are
+  // set by the caller of the draw function and updated during View animations.
+  int clip_left;
+  int clip_top;
+  int clip_right;
+  int clip_bottom;
+
+  // Input: current width/height of destination surface.
+  int width;
+  int height;
+
+  // Input: is the View rendered into an independent layer.
+  // If false, the surface is likely to hold to the full screen contents, with
+  // the scissor box set by the caller to the actual View location and size.
+  // Also the transformation matrix will contain at least a translation to the
+  // position of the View to render, plus any other transformations required as
+  // part of any ongoing View animation. View translucency (alpha) is ignored,
+  // although the framework will set is_layer to true for non-opaque cases.
+  // Can be requested via the View.setLayerType(View.LAYER_TYPE_NONE, ...)
+  // Android API method.
+  //
+  // If true, the surface is dedicated to the View and should have its size.
+  // The viewport and scissor box are set by the caller to the whole surface.
+  // Animation transformations are handled by the caller and not reflected in
+  // the provided transformation matrix. Translucency works normally.
+  // Can be requested via the View.setLayerType(View.LAYER_TYPE_HARDWARE, ...)
+  // Android API method.
+  bool is_layer;
+
+  // Input: current transformation matrix in surface pixels.
+  // Uses the column-based OpenGL matrix format.
+  float transform[16];
+};
+
+struct AwDrawFn_InitVkParams {
+  int version;
+  VkInstance instance;
+  VkPhysicalDevice physical_device;
+  VkDevice device;
+  VkQueue queue;
+  uint32_t graphics_queue_index;
+  uint32_t instance_version;
+  const char* const* enabled_extension_names;
+  // Only one of device_features and device_features_2 should be non-null.
+  // If both are null then no features are enabled.
+  VkPhysicalDeviceFeatures* device_features;
+  VkPhysicalDeviceFeatures2* device_features_2;
+};
+
+struct AwDrawFn_DrawVkParams {
+  int version;
+
+  // Input: current width/height of destination surface.
+  int width;
+  int height;
+
+  // Input: is the render target a FBO
+  bool is_layer;
+
+  // Input: current transform matrix
+  float transform[16];
+
+  // Input WebView should do its main compositing draws into this. It cannot do
+  // anything that would require stopping the render pass.
+  VkCommandBuffer secondary_command_buffer;
+
+  // Input: The main color attachment index where secondary_command_buffer will
+  // eventually be submitted.
+  uint32_t color_attachment_index;
+
+  // Input: A render pass which will be compatible to the one which the
+  // secondary_command_buffer will be submitted into.
+  VkRenderPass compatible_render_pass;
+
+  // Input: Format of the destination surface.
+  VkFormat format;
+
+  // Input: Color space transformation from linear RGB to D50-adapted XYZ
+  float matrix[9];
+
+  // Input: current clip rect
+  int clip_left;
+  int clip_top;
+  int clip_right;
+  int clip_bottom;
+};
+
+struct AwDrawFn_PostDrawVkParams {
+  int version;
+
+  // Input: Fence for the composite command buffer to signal it has finished its
+  // work on the GPU.
+  int fd;
+};
+
+// Called on render thread while UI thread is blocked. Called for both GL and
+// VK.
+typedef void AwDrawFn_OnSync(int functor, AwDrawFn_OnSyncParams* params);
+
+// Called on render thread when either the context is destroyed _or_ when the
+// functor's last reference goes away. Will always be called with an active
+// context. Called for both GL and VK.
+typedef void AwDrawFn_OnContextDestroyed(int functor);
+
+// Called on render thread when the last reference to the handle goes away and
+// the handle is considered irrevocably destroyed. Will always be proceeded by
+// a call to OnContextDestroyed if this functor had ever been drawn. Called for
+// both GL and VK.
+typedef void AwDrawFn_OnDestroyed(int functor);
+
+// Only called for GL.
+typedef void AwDrawFn_DrawGL(int functor, AwDrawFn_DrawGLParams* params);
+
+// Initialize vulkan state. Needs to be called again after any
+// OnContextDestroyed. Only called for Vulkan.
+typedef void AwDrawFn_InitVk(int functor, AwDrawFn_InitVkParams* params);
+
+// Only called for Vulkan.
+typedef void AwDrawFn_DrawVk(int functor, AwDrawFn_DrawVkParams* params);
+
+// Only called for Vulkan.
+typedef void AwDrawFn_PostDrawVk(int functor,
+                                 AwDrawFn_PostDrawVkParams* params);
+
+struct AwDrawFnFunctorCallbacks {
+  // No version here since this is passed from chromium to android.
+  AwDrawFn_OnSync* on_sync;
+  AwDrawFn_OnContextDestroyed* on_context_destroyed;
+  AwDrawFn_OnDestroyed* on_destroyed;
+  AwDrawFn_DrawGL* draw_gl;
+  AwDrawFn_InitVk* init_vk;
+  AwDrawFn_DrawVk* draw_vk;
+  AwDrawFn_PostDrawVk* post_draw_vk;
+};
+
+enum AwDrawFnRenderMode {
+  AW_DRAW_FN_RENDER_MODE_OPENGL_ES = 0,
+  AW_DRAW_FN_RENDER_MODE_VULKAN = 1,
+};
+
+// Get the render mode. Result is static for the process.
+typedef AwDrawFnRenderMode AwDrawFn_QueryRenderMode(void);
+
+// Create a functor. |functor_callbacks| should be valid until OnDestroyed.
+typedef int AwDrawFn_CreateFunctor(AwDrawFnFunctorCallbacks* functor_callbacks);
+
+// May be called on any thread to signal that the functor should be destroyed.
+// The functor will receive an onDestroyed when the last usage of it is
+// released, and it should be considered alive & active until that point.
+typedef void AwDrawFn_ReleaseFunctor(int functor);
+
+struct AwDrawFnFunctionTable {
+  int version;
+  AwDrawFn_QueryRenderMode* query_render_mode;
+  AwDrawFn_CreateFunctor* create_functor;
+  AwDrawFn_ReleaseFunctor* release_functor;
+};
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_FN_H_
diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp
index 1ba559d..eab1340 100644
--- a/native/webview/plat_support/draw_vk_functor.cpp
+++ b/native/webview/plat_support/draw_vk_functor.cpp
@@ -20,6 +20,7 @@
 
 #define LOG_TAG "webviewchromium_plat_support"
 
+#include "draw_fn.h"
 #include "draw_vk.h"
 
 #include <jni.h>
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml
index 1d67286..a2a628d 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_pod.xml
@@ -25,13 +25,15 @@
     android:orientation="vertical"
     android:gravity="center">
 
-    <ImageView android:id="@+id/user_avatar"
+    <ImageView
+        android:id="@+id/user_avatar"
         android:layout_width="@dimen/car_user_switcher_image_avatar_size"
         android:layout_height="@dimen/car_user_switcher_image_avatar_size"
-        android:background="@drawable/car_button_ripple_background_light"
+        android:background="?android:attr/selectableItemBackground"
         android:gravity="center"/>
 
-    <TextView android:id="@+id/user_name"
+    <TextView
+        android:id="@+id/user_name"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/car_user_switcher_vertical_spacing_between_name_and_avatar"
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 6cd70d6..e8c5134cd 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -19,12 +19,10 @@
         android:fitsSystemWindows="true"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/car_user_switcher_background_color"
         android:visibility="gone">
 
     <LinearLayout
         android:id="@+id/container"
-        android:background="@color/car_user_switcher_background_color"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical">
@@ -38,7 +36,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_marginTop="@dimen/car_user_switcher_margin_top"
-            android:theme="@style/Theme.Car.Light.List"
+            android:theme="@style/PagedListTheme"
             app:verticallyCenterListContent="true"
             app:showPagedListViewDivider="false"
             app:gutter="both"
diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
index 141b28a..72ec8d8 100644
--- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
@@ -68,7 +68,7 @@
         android:orientation="vertical">
 
         <com.android.keyguard.AlphaOptimizedImageButton
-            android:id="@+id/notifications"
+            android:id="@+id/note"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:src="@drawable/car_ic_notification"
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index b67ce15..052566d 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -20,7 +20,7 @@
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@android:color/black"
+    android:background="@drawable/system_bar_background"
     android:orientation="vertical">
     <LinearLayout
         android:id="@id/nav_buttons"
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
index 46e60db..4fa877f 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -20,7 +20,7 @@
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@android:color/black"
+    android:background="@drawable/system_bar_background"
     android:orientation="vertical">
 
     <LinearLayout
diff --git a/packages/CarSystemUI/res/layout/car_qs_footer.xml b/packages/CarSystemUI/res/layout/car_qs_footer.xml
index 6f19cfc..bf96c00 100644
--- a/packages/CarSystemUI/res/layout/car_qs_footer.xml
+++ b/packages/CarSystemUI/res/layout/car_qs_footer.xml
@@ -35,7 +35,7 @@
         android:layout_centerVertical="true"
         android:layout_width="@dimen/car_qs_footer_icon_width"
         android:layout_height="@dimen/car_qs_footer_icon_height"
-        android:background="@drawable/ripple_drawable"
+        android:background="?android:attr/selectableItemBackground"
         android:focusable="true">
 
         <ImageView
diff --git a/packages/CarSystemUI/res/layout/car_qs_panel.xml b/packages/CarSystemUI/res/layout/car_qs_panel.xml
index dfa48c3..d923e0f 100644
--- a/packages/CarSystemUI/res/layout/car_qs_panel.xml
+++ b/packages/CarSystemUI/res/layout/car_qs_panel.xml
@@ -21,8 +21,7 @@
     android:layout_height="wrap_content"
     android:background="@color/car_qs_background_primary"
     android:orientation="vertical"
-    android:elevation="4dp"
-    android:theme="@android:style/Theme">
+    android:elevation="4dp">
 
     <include layout="@layout/car_status_bar_header"/>
     <include layout="@layout/car_qs_footer"/>
@@ -39,7 +38,7 @@
             android:id="@+id/user_grid"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:theme="@style/Theme.Car.Light.List"
+            android:theme="@style/PagedListTheme"
             app:showPagedListViewDivider="false"
             app:gutter="both"
             app:itemSpacing="@dimen/car_user_switcher_vertical_spacing_between_users"/>
diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
index 141b28a..72ec8d8 100644
--- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
@@ -68,7 +68,7 @@
         android:orientation="vertical">
 
         <com.android.keyguard.AlphaOptimizedImageButton
-            android:id="@+id/notifications"
+            android:id="@+id/note"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:src="@drawable/car_ic_notification"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index 7b3333e..1dca10a 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -21,7 +21,7 @@
     android:id="@+id/car_top_bar"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@android:color/black"
+    android:background="@drawable/system_bar_background"
     android:orientation="vertical">
 
     <RelativeLayout
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/packages/CarSystemUI/res/values/themes.xml
similarity index 70%
copy from core/java/android/service/contentcapture/InteractionContext.aidl
copy to packages/CarSystemUI/res/values/themes.xml
index 982e095..8a5961e 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/packages/CarSystemUI/res/values/themes.xml
@@ -1,3 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
 /**
  * Copyright (c) 2018, The Android Open Source Project
  *
@@ -13,7 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+-->
 
-package android.service.contentcapture;
-
-parcelable InteractionContext;
+<resources>
+    <!--This Theme contains attributes required for components from the car support lib -->
+    <style name="PagedListTheme" parent="Theme.CarSupportWrapper.NoActionBar">
+    </style>
+</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
index cb92c42..5e63b90 100644
--- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.notifications;
 
+import android.app.ActivityManager;
 import android.car.Car;
 import android.car.CarNotConnectedException;
 import android.car.drivingstate.CarUxRestrictionsManager;
@@ -24,6 +25,7 @@
 import android.content.ServiceConnection;
 import android.graphics.PixelFormat;
 import android.os.IBinder;
+import android.os.ServiceManager;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,8 +33,10 @@
 
 import com.android.car.notification.CarNotificationListener;
 import com.android.car.notification.CarUxRestrictionManagerWrapper;
+import com.android.car.notification.NotificationClickHandlerFactory;
 import com.android.car.notification.NotificationViewController;
 import com.android.car.notification.PreprocessingManager;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 
@@ -44,6 +48,7 @@
     private static final String TAG = "NotificationsUI";
     private CarNotificationListener mCarNotificationListener;
     private CarUxRestrictionsManager mCarUxRestrictionsManager;
+    private NotificationClickHandlerFactory mClickHandlerFactory;
     private Car mCar;
     private ViewGroup mCarNotificationWindow;
     private NotificationViewController mNotificationViewController;
@@ -60,7 +65,17 @@
         WindowManager windowManager =
                 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mCarNotificationListener = new CarNotificationListener();
-        mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper);
+        mClickHandlerFactory = new NotificationClickHandlerFactory(
+                IStatusBarService.Stub.asInterface(
+                        ServiceManager.getService(Context.STATUS_BAR_SERVICE)),
+                launchResult -> {
+                    if (launchResult == ActivityManager.START_TASK_TO_FRONT
+                            || launchResult == ActivityManager.START_SUCCESS) {
+                        closeCarNotifications();
+                    }
+                });
+        mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
+                mClickHandlerFactory);
         mCar = Car.createCar(mContext, mCarConnectionListener);
         mCar.connect();
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 1737b64..85dab57 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -107,36 +107,44 @@
     private CarAudioManager mCarAudioManager;
     private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
             new CarAudioManager.CarVolumeCallback() {
-        @Override
-        public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
-            // TODO: Include zoneId into consideration.
-            // For instance
-            // - single display + single-zone, ignore zoneId
-            // - multi-display + single-zone, zoneId is fixed, may show volume bar on all displays
-            // - single-display + multi-zone, may show volume bar on primary display only
-            // - multi-display + multi-zone, may show volume bar on display specified by zoneId
-            VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
-            int value = getSeekbarValue(mCarAudioManager, groupId);
-            // Do not update the progress if it is the same as before. When car audio manager sets
-            // its group volume caused by the seekbar progress changed, it also triggers this
-            // callback. Updating the seekbar at the same time could block the continuous seeking.
-            if (value != volumeItem.progress) {
-                volumeItem.listItem.setProgress(value);
-                volumeItem.progress = value;
-            }
-            if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
-                mHandler.obtainMessage(H.SHOW, Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
-            }
-        }
+                @Override
+                public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+                    // TODO: Include zoneId into consideration.
+                    // For instance
+                    // - single display + single-zone, ignore zoneId
+                    // - multi-display + single-zone, zoneId is fixed, may show volume bar on all
+                    // displays
+                    // - single-display + multi-zone, may show volume bar on primary display only
+                    // - multi-display + multi-zone, may show volume bar on display specified by
+                    // zoneId
+                    VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+                    int value = getSeekbarValue(mCarAudioManager, groupId);
+                    // Do not update the progress if it is the same as before. When car audio
+                    // manager sets
+                    // its group volume caused by the seekbar progress changed, it also triggers
+                    // this
+                    // callback. Updating the seekbar at the same time could block the continuous
+                    // seeking.
+                    if (value != volumeItem.progress) {
+                        volumeItem.listItem.setProgress(value);
+                        volumeItem.progress = value;
+                    }
+                    if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
+                        mHandler.obtainMessage(H.SHOW,
+                                Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
+                    }
+                }
 
-        @Override
-        public void onMasterMuteChanged(int zoneId, int flags) {
-            // ignored
-        }
-    };
+                @Override
+                public void onMasterMuteChanged(int zoneId, int flags) {
+                    // ignored
+                }
+            };
     private boolean mHovering;
     private boolean mShowing;
     private boolean mExpanded;
+    private View mExpandIcon;
+    private VolumeItem mDefaultVolumeItem;
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -151,10 +159,8 @@
                     mAvailableVolumeItems.add(volumeItem);
                     // The first one is the default item.
                     if (groupId == 0) {
-                        volumeItem.defaultItem = true;
-                        addSeekbarListItem(volumeItem, groupId,
-                                R.drawable.car_ic_keyboard_arrow_down,
-                                new ExpandIconListener());
+                        mDefaultVolumeItem = volumeItem;
+                        setupDefaultListItem();
                     }
                 }
 
@@ -178,6 +184,13 @@
         }
     };
 
+    private void setupDefaultListItem() {
+        mDefaultVolumeItem.defaultItem = true;
+        addSeekbarListItem(mDefaultVolumeItem, /* volumeGroupId = */0,
+                R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()
+        );
+    }
+
     public CarVolumeDialogImpl(Context context) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
@@ -294,7 +307,9 @@
             return;
         }
         mShowing = true;
-
+        if (mVolumeLineItems.isEmpty()) {
+            setupDefaultListItem();
+        }
         mDialog.show();
         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
     }
@@ -340,6 +355,13 @@
                     }
                     mDialog.dismiss();
                     mShowing = false;
+                    mShowing = false;
+                    // if mExpandIcon is null that means user never clicked on the expanded arrow
+                    // which implies that the dialog is still not expanded. In that case we do
+                    // not want to reset the state
+                    if (mExpandIcon != null && mExpanded) {
+                        toggleDialogExpansion(/* isClicked = */ false);
+                    }
                 }, DISMISS_DELAY_IN_MILLIS))
                 .start();
 
@@ -517,52 +539,62 @@
     }
 
     private final class ExpandIconListener implements View.OnClickListener {
-
         @Override
         public void onClick(final View v) {
-            mExpanded = !mExpanded;
-            Animator inAnimator;
-            if (mExpanded) {
-                for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
-                    // Adding the items which are not coming from the default item.
-                    VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
-                    if (volumeItem.defaultItem) {
-                        // Set progress here due to the progress of seekbar may not be updated.
-                        volumeItem.listItem.setProgress(volumeItem.progress);
-                    } else {
-                        addSeekbarListItem(volumeItem, groupId, 0, null);
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                        mContext, R.anim.car_arrow_fade_in_rotate_up);
-            } else {
-                // Only keeping the default stream if it is not expended.
-                Iterator itr = mVolumeLineItems.iterator();
-                while (itr.hasNext()) {
-                    SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
-                    VolumeItem volumeItem = findVolumeItem(seekbarListItem);
-                    if (!volumeItem.defaultItem) {
-                        itr.remove();
-                    } else {
-                        // Set progress here due to the progress of seekbar may not be updated.
-                        seekbarListItem.setProgress(volumeItem.progress);
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                        mContext, R.anim.car_arrow_fade_in_rotate_down);
-            }
-
-            Animator outAnimator = AnimatorInflater.loadAnimator(
-                    mContext, R.anim.car_arrow_fade_out);
-            inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
-            AnimatorSet animators = new AnimatorSet();
-            animators.playTogether(outAnimator, inAnimator);
-            animators.setTarget(v);
-            animators.start();
-            mPagedListAdapter.notifyDataSetChanged();
+            mExpandIcon = v;
+            toggleDialogExpansion(true);
         }
     }
 
+    private void toggleDialogExpansion(boolean isClicked) {
+        mExpanded = !mExpanded;
+        Animator inAnimator;
+        if (mExpanded) {
+            for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
+                // Adding the items which are not coming from the default item.
+                VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+                if (volumeItem.defaultItem) {
+                    // Set progress here due to the progress of seekbar may not be updated.
+                    volumeItem.listItem.setProgress(volumeItem.progress);
+                } else {
+                    addSeekbarListItem(volumeItem, groupId, 0, null);
+                }
+            }
+            inAnimator = AnimatorInflater.loadAnimator(
+                    mContext, R.anim.car_arrow_fade_in_rotate_up);
+
+        } else {
+            // Only keeping the default stream if it is not expended.
+            Iterator itr = mVolumeLineItems.iterator();
+            while (itr.hasNext()) {
+                SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
+                VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+                if (!volumeItem.defaultItem) {
+                    itr.remove();
+                } else {
+                    // Set progress here due to the progress of seekbar may not be updated.
+                    seekbarListItem.setProgress(volumeItem.progress);
+                }
+            }
+            inAnimator = AnimatorInflater.loadAnimator(
+                    mContext, R.anim.car_arrow_fade_in_rotate_down);
+        }
+
+        Animator outAnimator = AnimatorInflater.loadAnimator(
+                mContext, R.anim.car_arrow_fade_out);
+        inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
+        AnimatorSet animators = new AnimatorSet();
+        animators.playTogether(outAnimator, inAnimator);
+        if (!isClicked) {
+            // Do not animate when the state is called to reset the dialogs view and not clicked
+            // by user.
+            animators.setDuration(0);
+        }
+        animators.setTarget(mExpandIcon);
+        animators.start();
+        mPagedListAdapter.notifyDataSetChanged();
+    }
+
     private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
 
         private final int mVolumeGroupId;
@@ -601,4 +633,4 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 4d9aaec..f116546 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -31,7 +31,8 @@
     <application
         android:label="@string/app_name"
         android:directBootAware="true"
-        android:usesCleartextTraffic="true">
+        android:usesCleartextTraffic="true"
+        android:icon="@mipmap/ic_launcher_android">
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
             <intent-filter>
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
@@ -45,6 +46,7 @@
         <activity
             android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
             android:label="@string/action_bar_label"
+            android:exported="true"
             android:theme="@style/AppTheme"
             android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
diff --git a/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png b/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png
new file mode 100644
index 0000000..2e9b196
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/mipmap/ic_launcher_android.png
Binary files differ
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 4f67350..f36b4aa 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -195,19 +195,7 @@
         if (success) {
             // Trigger re-evaluation upon success http response code
             CarrierActionUtils.applyCarrierAction(
-                    CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(),
-                    getApplicationContext());
-            CarrierActionUtils.applyCarrierAction(
-                    CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(),
-                    getApplicationContext());
-            CarrierActionUtils.applyCarrierAction(
-                    CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
-                    getApplicationContext());
-            CarrierActionUtils.applyCarrierAction(
-                    CarrierActionUtils.CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER, getIntent(),
-                    getApplicationContext());
-            CarrierActionUtils.applyCarrierAction(
-                    CarrierActionUtils.CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL, getIntent(),
+                    CarrierActionUtils.CARRIER_ACTION_RESET_ALL, getIntent(),
                     getApplicationContext());
         }
         finishAndRemoveTask();
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 4518d79..3258d57 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -56,6 +56,7 @@
     public static final int CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER       = 8;
     public static final int CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL    = 9;
     public static final int CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL  = 10;
+    public static final int CARRIER_ACTION_RESET_ALL                         = 11;
 
     public static void applyCarrierAction(int actionIdx, Intent intent, Context context) {
         switch (actionIdx) {
@@ -92,6 +93,9 @@
             case CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL:
                 onDeregisterDefaultNetworkAvail(intent, context);
                 break;
+            case CARRIER_ACTION_RESET_ALL:
+                onResetAllCarrierActions(intent, context);
+                break;
             default:
                 loge("unsupported carrier action index: " + actionIdx);
         }
@@ -196,6 +200,14 @@
         context.getSystemService(NotificationManager.class).cancelAll();
     }
 
+    private static void onResetAllCarrierActions(Intent intent, Context context) {
+        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.getDefaultVoiceSubscriptionId());
+        logd("onResetAllCarrierActions subId: " + subId);
+        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+        telephonyMgr.carrierActionResetAll(subId);
+    }
+
     private static Notification getNotification(Context context, int titleId, int textId,
                                          PendingIntent pendingIntent) {
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 60d31fc..0352ebc 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -75,7 +75,7 @@
         mContext.getSystemService(TextClassificationManager.class)
                 .setTextClassifier(mTextClassifier);
         when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class)))
-                .thenReturn(new ConversationActions(Collections.emptyList()));
+                .thenReturn(new ConversationActions(Collections.emptyList(), null));
 
         when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification);
         // The notification is eligible to have smart suggestions.
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index e518e0b..cd3fb0c 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -1,5 +1,5 @@
 android_library {
-    name: "ActionButtonsPreference",
+    name: "SettingsLibActionButtonsPreference",
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 0126e7e5..042808a0 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -16,8 +16,8 @@
         "SettingsLibAppPreference",
         "SettingsLibSearchWidget",
         "SettingsLibSettingsSpinner",
-        "SettingsLayoutPreference",
-        "ActionButtonsPreference",
+        "SettingsLibLayoutPreference",
+        "SettingsLibActionButtonsPreference",
         "SettingsLibEntityHeaderWidgets",
     ],
 
diff --git a/packages/SettingsLib/SettingsLayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
similarity index 83%
rename from packages/SettingsLib/SettingsLayoutPreference/Android.bp
rename to packages/SettingsLib/LayoutPreference/Android.bp
index 489d360..a1f9a76 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -1,5 +1,5 @@
 android_library {
-    name: "SettingsLayoutPreference",
+    name: "SettingsLibLayoutPreference",
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml b/packages/SettingsLib/LayoutPreference/AndroidManifest.xml
similarity index 100%
rename from packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml
rename to packages/SettingsLib/LayoutPreference/AndroidManifest.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml b/packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml
similarity index 100%
rename from packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml
rename to packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
similarity index 98%
rename from packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
rename to packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index 01d9c00..e27ae7d 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
@@ -73,7 +73,7 @@
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_centerVertical="true"
         android:layout_alignParentEnd="true"
         android:orientation="vertical">
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
similarity index 100%
rename from packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml
rename to packages/SettingsLib/LayoutPreference/res/values/styles.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
similarity index 100%
rename from packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
rename to packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index f7b16f8..c8c05a0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -22,6 +22,7 @@
 import android.os.PowerManager;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -176,4 +177,22 @@
             setAutoBatterySaverTriggerLevel(context, level);
         }
     }
+
+    /**
+     * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
+     * is selected but no app is configured to actually provide the signal.
+     * @param context a valid context
+     */
+    public static void revertScheduleToNoneIfNeeded(Context context) {
+        ContentResolver resolver = context.getContentResolver();
+        final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE,
+                PowerManager.POWER_SAVER_MODE_PERCENTAGE);
+        boolean providerConfigured = !TextUtils.isEmpty(context.getString(
+                com.android.internal.R.string.config_batterySaverScheduleProvider));
+        if (currentMode == PowerManager.POWER_SAVER_MODE_DYNAMIC && !providerConfigured) {
+            Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+            Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE,
+                    PowerManager.POWER_SAVER_MODE_PERCENTAGE);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 5c126b1..120acd3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -263,6 +263,10 @@
             // The user canceled to enable a 3rd party IME.
             setCheckedInternal(false);
         });
+        builder.setOnCancelListener((dialog) -> {
+            // The user canceled to enable a 3rd party IME.
+            setCheckedInternal(false);
+        });
         mDialog = builder.create();
         mDialog.show();
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 3520918..f2b2719 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,7 +20,6 @@
 import android.annotation.SystemApi;
 import android.app.ActivityManager;
 import android.content.IContentProvider;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Process;
@@ -28,6 +27,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import java.io.FileDescriptor;
@@ -46,13 +46,6 @@
  */
 @SystemApi
 public final class DeviceConfigService extends Binder {
-    /**
-     * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
-     *     API.
-     */
-    private static final Uri CONFIG_CONTENT_URI =
-            Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
     final SettingsProvider mProvider;
 
     public DeviceConfigService(SettingsProvider provider) {
@@ -191,10 +184,10 @@
             final PrintWriter pout = getOutPrintWriter();
             switch (verb) {
                 case GET:
-                    pout.println(get(iprovider, namespace, key));
+                    pout.println(DeviceConfig.getProperty(namespace, key));
                     break;
                 case PUT:
-                    put(iprovider, namespace, key, value, makeDefault);
+                    DeviceConfig.setProperty(namespace, key, value, makeDefault);
                     break;
                 case DELETE:
                     pout.println(delete(iprovider, namespace, key)
@@ -207,7 +200,7 @@
                     }
                     break;
                 case RESET:
-                    reset(iprovider, resetMode, namespace);
+                    DeviceConfig.resetToDefaults(resetMode, namespace);
                     break;
                 default:
                     perr.println("Unspecified command");
@@ -241,43 +234,6 @@
                     + "flags are reset");
         }
 
-        private String get(IContentProvider provider, String namespace, String key) {
-            String compositeKey = namespace + "/" + key;
-            String result = null;
-            try {
-                Bundle args = new Bundle();
-                args.putInt(Settings.CALL_METHOD_USER_KEY,
-                        ActivityManager.getService().getCurrentUser().id);
-                Bundle b = provider.call(resolveCallingPackage(), Settings.CALL_METHOD_GET_CONFIG,
-                        compositeKey, args);
-                if (b != null) {
-                    result = b.getPairValue();
-                }
-            } catch (RemoteException e) {
-                throw new RuntimeException("Failed in IPC", e);
-            }
-            return result;
-        }
-
-        private void put(IContentProvider provider, String namespace, String key, String value,
-                boolean makeDefault) {
-            String compositeKey = namespace + "/" + key;
-
-            try {
-                Bundle args = new Bundle();
-                args.putString(Settings.NameValueTable.VALUE, value);
-                args.putInt(Settings.CALL_METHOD_USER_KEY,
-                        ActivityManager.getService().getCurrentUser().id);
-                if (makeDefault) {
-                    args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
-                }
-                provider.call(resolveCallingPackage(), Settings.CALL_METHOD_PUT_CONFIG,
-                        compositeKey, args);
-            } catch (RemoteException e) {
-                throw new RuntimeException("Failed in IPC", e);
-            }
-        }
-
         private boolean delete(IContentProvider provider, String namespace, String key) {
             String compositeKey = namespace + "/" + key;
             boolean success;
@@ -322,20 +278,6 @@
             return lines;
         }
 
-        private void reset(IContentProvider provider, int resetMode, @Nullable String namespace) {
-            try {
-                Bundle args = new Bundle();
-                args.putInt(Settings.CALL_METHOD_USER_KEY,
-                        ActivityManager.getService().getCurrentUser().id);
-                args.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode);
-                args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
-                provider.call(
-                        resolveCallingPackage(), Settings.CALL_METHOD_RESET_CONFIG, null, args);
-            } catch (RemoteException e) {
-                throw new RuntimeException("Failed in IPC", e);
-            }
-        }
-
         private static String resolveCallingPackage() {
             switch (Binder.getCallingUid()) {
                 case Process.ROOT_UID: {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b071355..ce529a0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -63,6 +63,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -195,13 +196,6 @@
     private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
     private static final Set<String> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS = new ArraySet<>();
 
-    /**
-     * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
-     *     API.
-     */
-    private static final Uri CONFIG_CONTENT_URI =
-            Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
     static {
         for (String name : Resources.getSystem().getStringArray(
                 com.android.internal.R.array.config_allowedGlobalInstantAppSettings)) {
@@ -3148,8 +3142,8 @@
 
         private Uri getNotificationUriFor(int key, String name) {
             if (isConfigSettingsKey(key)) {
-                return (name != null) ? Uri.withAppendedPath(CONFIG_CONTENT_URI, name)
-                        : CONFIG_CONTENT_URI;
+                return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
+                        : DeviceConfig.CONTENT_URI;
             } else if (isGlobalSettingsKey(key)) {
                 return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
                         : Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 9d0462e..5587cba 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -21,8 +21,8 @@
 import static junit.framework.Assert.assertNull;
 
 import android.content.ContentResolver;
-import android.net.Uri;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
@@ -43,12 +43,6 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class DeviceConfigServiceTest {
-    /**
-     * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
-     *     API.
-     */
-    private static final Uri CONFIG_CONTENT_URI =
-            Uri.parse("content://" + Settings.AUTHORITY + "/config");
     private static final String sNamespace = "namespace1";
     private static final String sKey = "key1";
     private static final String sValue = "value1";
@@ -152,7 +146,7 @@
         // make sValue the default value
         executeShellCommand(
                 "device_config put " + sNamespace + " " + sKey + " " + sValue + " default");
-        // make newValue the current value
+        // make newValue the current value (as set by a trusted package)
         executeShellCommand(
                 "device_config put " + sNamespace + " " + sKey + " " + newValue);
         String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
@@ -161,14 +155,14 @@
         // reset values that were set by untrusted packages
         executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
         result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // the default value has been restored
-        assertEquals(sValue, result);
+        // the current value was set by a trusted package, so it's not reset
+        assertEquals(newValue, result);
 
-        // clear values that were set by untrusted packages
+        // reset values that were set by untrusted or trusted packages
         executeShellCommand("device_config reset trusted_defaults " + sNamespace);
         result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // even the default value is gone now
-        assertNull(result);
+        // the default value has been restored
+        assertEquals(sValue, result);
     }
 
     private static void executeShellCommand(String command) throws IOException {
@@ -190,14 +184,15 @@
         if (makeDefault) {
             args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
         }
-        resolver.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+        resolver.call(
+                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
     }
 
     private static String getFromContentProvider(ContentResolver resolver, String namespace,
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
         assertNotNull(result);
         return result.getString(Settings.NameValueTable.VALUE);
     }
@@ -206,7 +201,7 @@
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
         assertNotNull(result);
         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
     }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 75492f3..c2e107a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -44,7 +44,6 @@
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SettingsLib",
-        "androidx.car_car",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
         "androidx.preference_preference",
@@ -67,8 +66,6 @@
 
     libs: [
         "telephony-common",
-        "android.car",
-        "android.car.userlib",
     ],
 
     aaptflags: [
@@ -98,7 +95,6 @@
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SettingsLib",
-        "androidx.car_car",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
         "androidx.preference_preference",
@@ -123,8 +119,6 @@
     libs: [
         "android.test.runner",
         "telephony-common",
-        "android.car",
-        "android.car.userlib",
         "android.test.base",
     ],
     aaptflags: [
@@ -149,8 +143,6 @@
 
     libs: [
         "telephony-common",
-        "android.car",
-        "android.car.userlib",
     ],
 
     dxflags: ["--multi-dex"],
@@ -158,6 +150,7 @@
         "--extra-packages",
         "com.android.keyguard",
     ],
+    required: ["privapp_whitelist_com.android.systemui"],
 
 }
 
@@ -186,8 +179,6 @@
     ],
     libs: [
         "telephony-common",
-        "android.car",
-        "android.car.userlib",
     ],
 
     srcs: [
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7d53c2f..1c1a140 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -77,6 +77,7 @@
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
     <uses-permission android:name="android.permission.MASTER_CLEAR" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" />
 
     <!-- ActivityManager -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
index 88b8dd8..fbd863d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
@@ -11,13 +11,12 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.plugins;
 
-import android.hardware.Sensor;
-import android.hardware.TriggerEventListener;
+import android.hardware.SensorListener;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
@@ -31,26 +30,30 @@
     int VERSION = 1;
 
     /**
-     * Registers for trigger events from the sensor. Trigger events are one-shot and need to
-     * re-registered in order for them to be fired again.
+     * Registers for sensor events. Events will be sent until the listener is unregistered.
      * @param sensor
      * @param listener
-     * @see android.hardware.SensorManager#requestTriggerSensor(
-     *     android.hardware.TriggerEventListener, android.hardware.Sensor)
+     * @see android.hardware.SensorManager#registerListener(SensorListener, int)
      */
-    void registerTriggerEvent(Sensor sensor, TriggerEventListener listener);
+    void registerListener(Sensor sensor, SensorEventListener listener);
 
     /**
-     * Unregisters trigger events from the sensor.
+     * Unregisters events from the sensor.
      * @param sensor
      * @param listener
      */
-    void unregisterTriggerEvent(Sensor sensor, TriggerEventListener listener);
+    void unregisterListener(Sensor sensor, SensorEventListener listener);
 
-    interface TriggerEventListener {
-        void onTrigger(TriggerEvent event);
+    /**
+     * Listener triggered whenever the Sensor has new data.
+     */
+    interface SensorEventListener {
+        void onSensorChanged(SensorEvent event);
     }
 
+    /**
+     * Sensor that can be defined in a plugin.
+     */
     class Sensor {
         public static final int TYPE_WAKE_LOCK_SCREEN = 1;
         public static final int TYPE_WAKE_DISPLAY = 2;
@@ -67,29 +70,32 @@
         }
     }
 
-    class TriggerEvent {
+    /**
+     * Event sent by a {@link Sensor}.
+     */
+    class SensorEvent {
         Sensor mSensor;
         int mVendorType;
         float[] mValues;
 
         /**
-         * Creates a trigger event
+         * Creates a sensor event.
          * @param sensor The type of sensor, e.g. TYPE_WAKE_LOCK_SCREEN
          * @param vendorType The vendor type, which should be unique for each type of sensor,
          *                   e.g. SINGLE_TAP = 1, DOUBLE_TAP = 2, etc.
          */
-        public TriggerEvent(Sensor sensor, int vendorType) {
+        public SensorEvent(Sensor sensor, int vendorType) {
             this(sensor, vendorType, null);
         }
 
         /**
-         * Creates a trigger event
+         * Creates a sensor event.
          * @param sensor The type of sensor, e.g. TYPE_WAKE_LOCK_SCREEN
          * @param vendorType The vendor type, which should be unique for each type of sensor,
          *                   e.g. SINGLE_TAP = 1, DOUBLE_TAP = 2, etc.
          * @param values Values captured by the sensor.
          */
-        public TriggerEvent(Sensor sensor, int vendorType, float[] values) {
+        public SensorEvent(Sensor sensor, int vendorType, float[] values) {
             mSensor = sensor;
             mVendorType = vendorType;
             mValues = values;
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 89b873e..367a9ae 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -32,7 +32,7 @@
         android:letterSpacing="0.03"
         android:textColor="?attr/wallpaperTextColor"
         android:singleLine="true"
-        style="@style/widget_big_thin"
+        style="@style/widget_big"
         android:format12Hour="@string/keyguard_widget_12_hours_format"
         android:format24Hour="@string/keyguard_widget_24_hours_format" />
 </com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 32a7147..67ecf6f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -44,7 +44,7 @@
             android:paddingLeft="@dimen/logout_button_padding_horizontal"
             android:paddingRight="@dimen/logout_button_padding_horizontal"
             android:background="@drawable/logout_button_background"
-            android:fontFamily="roboto-medium"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
             android:textAllCaps="true"
             android:textColor="?android:attr/textColorPrimary"
             android:textSize="13sp"
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 9baeaaa..ffc7b3c 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -50,7 +50,7 @@
     </style>
     <style name="Widget.TextView.NumPadKey.Klondike" parent="Widget.TextView.NumPadKey">
         <item name="android:textSize">12sp</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
         <item name="android:paddingBottom">0dp</item>
     </style>
@@ -59,10 +59,10 @@
     <style name="widget_label">
         <item name="android:textSize">@dimen/widget_label_font_size</item>
     </style>
-    <style name="widget_big_thin">
+    <style name="widget_big">
         <item name="android:textSize">@dimen/widget_big_font_size</item>
         <item name="android:paddingBottom">@dimen/bottom_text_spacing_digital</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:fontFeatureSettings">@*android:string/config_headlineFontFeatureSettings</item>
         <item name="android:ellipsize">none</item>
     </style>
@@ -93,7 +93,7 @@
         <item name="android:gravity">center</item>
         <item name="android:ellipsize">end</item>
         <item name="android:maxLines">2</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
     </style>
 
     <style name="TextAppearance.Keyguard.Secondary">
diff --git a/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml
new file mode 100644
index 0000000..2aa6e57f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml
@@ -0,0 +1,27 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="17dp"
+    android:viewportWidth="14"
+    android:viewportHeight="17">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M13.9,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07s-0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13s1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.23,0.79s0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45s-0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7S8.72,6.37 8.71,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M1.15,8.47l0.43,-4.96h4.33v1.17H2.6L2.37,7.39C2.78,7.1 3.22,6.96 3.69,6.96c0.77,0 1.38,0.3 1.83,0.9s0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43S4.32,13.6 3.48,13.6c-0.75,0 -1.36,-0.24 -1.83,-0.73s-0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59S3.88,8.09 3.4,8.09c-0.4,0 -0.72,0.1 -0.96,0.31L2.11,8.73L1.15,8.47z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..10bbcc7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,33 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
deleted file mode 100644
index a72e9b8..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-1.1">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#FFFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
deleted file mode 100644
index 53e4efc..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32.0dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-0.9">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#4DFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
deleted file mode 100644
index 8294183..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-0.9">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#4DFFFFFF"/>
-        <path
-            android:pathData="M12.82,21.6l5.11,-6.36A8.942,8.942 0,0 0,12 13c-2.28,0 -4.35,0.85 -5.94,2.25l5.1,6.35c0.43,0.53 1.23,0.53 1.66,0z"
-            android:fillColor="#FFFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
deleted file mode 100644
index 3d59cf2..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-0.9">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#4DFFFFFF"/>
-        <path
-            android:pathData="M12.82,21.6l6.99,-8.7C17.71,11.1 14.99,10 12,10c-2.99,0 -5.72,1.1 -7.82,2.91l6.98,8.7c0.43,0.52 1.23,0.52 1.66,-0.01z"
-            android:fillColor="#FFFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
deleted file mode 100644
index 21313b8..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-0.9">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#4DFFFFFF"/>
-        <path
-            android:pathData="M12.82,21.6l8.25,-10.26A13.961,13.961 0,0 0,12 8c-3.46,0 -6.63,1.26 -9.07,3.35l8.23,10.26c0.43,0.52 1.23,0.52 1.66,-0.01z"
-            android:fillColor="#FFFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
deleted file mode 100644
index fd763ff..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="29.5dp"
-    android:viewportWidth="25.6"
-    android:viewportHeight="23.6">
-    <group
-        android:translateX="0.8"
-        android:translateY="-0.9">
-        <path
-            android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
-            android:fillColor="#FFFFFFFF"/>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_sensors.xml b/packages/SystemUI/res/drawable/ic_signal_sensors.xml
new file mode 100644
index 0000000..faaddf6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_sensors.xml
@@ -0,0 +1,28 @@
+<!--
+     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.
+-->
+
+<vector android:height="48dp" android:viewportHeight="5"
+    android:viewportWidth="5" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#00000000"
+        android:pathData="m4.762,0.661 l-4.233,4.233"
+        android:strokeAlpha="1" android:strokeColor="#000000"
+        android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeWidth=".5"/>
+    <path android:fillColor="#00000000"
+        android:pathData="M0.265,2.778L1.058,2.778l0.529,-1.323 0.529,2.646 0.529,-3.175 0.529,2.646 0.529,-1.587 0.265,0.794h1.058"
+        android:strokeAlpha="1" android:strokeColor="#000000"
+        android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth=".33"/>
+</vector>
+
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 0b9a7b2..294bd50 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -24,7 +24,8 @@
     android:orientation="vertical"
     android:paddingBottom="8dp"
     android:visibility="invisible"
-    android:elevation="4dp" >
+    android:elevation="4dp"
+    android:importantForAccessibility="no" >
 
     <include
         android:id="@+id/qs_detail_header"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9e97cd8..61efbd5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -111,7 +111,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,sensorprivacy
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3851190..6037dfc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -24,7 +24,7 @@
     <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. -->
     <dimen name="navigation_bar_min_swipe_distance">48dp</dimen>
     <!-- The distance from a side of device of the navigation bar to start an edge swipe -->
-    <dimen name="navigation_bar_edge_swipe_threshold">60dp</dimen>
+    <dimen name="navigation_bar_edge_swipe_threshold">48dp</dimen>
 
     <!-- thickness (height) of the dead zone at the top of the navigation bar,
          reducing false presses on navbar buttons; approx 2mm -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9917257..07375ad 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -406,6 +406,12 @@
     <!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] -->
     <string name="data_connection_lte_plus">LTE+</string>
 
+    <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g" translate="false">5G</string>
+
+    <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translate="false">5G+</string>
+
     <!-- Content description of the data connection type CDMA. [CHAR LIMIT=NONE] -->
     <string name="data_connection_cdma">1X</string>
 
@@ -598,6 +604,10 @@
     <string name="accessibility_quick_settings_data_saver_changed_off">Data Saver turned off.</string>
     <!-- Announcement made when the Data Saver changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_data_saver_changed_on">Data Saver turned on.</string>
+    <!-- Announcement made when the Sensor Privacy changes to off (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_quick_settings_sensor_privacy_changed_off">Sensor Privacy turned off.</string>
+    <!-- Announcement made when the Sensor Privacy changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_quick_settings_sensor_privacy_changed_on">Sensor Privacy turned on.</string>
 
     <!-- Content description of the display brightness slider (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_brightness">Display brightness</string>
@@ -2318,4 +2328,6 @@
         <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
         <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other apps</item>
     </plurals>
+    <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] -->
+    <string name="sensor_privacy_mode">Sensors off</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fede934..8a5a69b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
 
     <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
         <item name="android:textSize">@dimen/status_bar_clock_size</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:textColor">@color/status_bar_clock_color</item>
     </style>
 
@@ -144,7 +144,7 @@
         <item name="android:textSize">@dimen/qs_time_expanded_size</item>
         <item name="android:textStyle">normal</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="TextAppearance.StatusBar.Expanded.AboveDateTime">
@@ -171,12 +171,12 @@
     <style name="TextAppearance.QS">
         <item name="android:textStyle">normal</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="TextAppearance.QS.DetailHeader">
         <item name="android:textSize">@dimen/qs_detail_header_text_size</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
     </style>
 
     <style name="TextAppearance.QS.DetailItemPrimary">
@@ -202,7 +202,7 @@
         <item name="android:textSize">@dimen/qs_detail_button_text_size</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textAllCaps">true</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:gravity">center</item>
     </style>
 
@@ -222,7 +222,7 @@
 
     <style name="TextAppearance.QS.SegmentedButton">
         <item name="android:textSize">16sp</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
     </style>
 
     <style name="TextAppearance.QS.DataUsage">
@@ -245,7 +245,7 @@
 
     <style name="TextAppearance.QS.TileLabel.Secondary">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="TextAppearance.QS.CarrierInfo">
@@ -262,7 +262,7 @@
 
     <style name="TextAppearance.AppOpsDialog.Item">
         <item name="android:textSize">@dimen/ongoing_appops_dialog_item_size</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
@@ -391,7 +391,7 @@
     <style name="TextAppearance.Volume">
         <item name="android:textStyle">normal</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="TextAppearance.Volume.Header">
@@ -435,7 +435,7 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo">
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">@color/notification_primary_text_color</item>
     </style>
 
@@ -463,7 +463,7 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo.Button">
-        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:background">@drawable/btn_borderless_rect</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index d3dded0..b21bcc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -137,7 +137,7 @@
         mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
         mDrawPaint.setTextAlign(Paint.Align.CENTER);
         mDrawPaint.setTypeface(Typeface.create(
-                context.getString(com.android.internal.R.string.config_headlineFontFamilyLight),
+                context.getString(com.android.internal.R.string.config_headlineFontFamily),
                 0));
         mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
                 Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 5e6d272..327ffcd 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -17,6 +17,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -191,6 +192,9 @@
                 new AsyncSensorManager(mContext.getSystemService(SensorManager.class),
                         getDependency(PluginManager.class)));
 
+        mProviders.put(SensorPrivacyManager.class, () ->
+                mContext.getSystemService(SensorPrivacyManager.class));
+
         mProviders.put(BluetoothController.class, () ->
                 new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index eda3c59..4fb1bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -44,8 +44,7 @@
     public static final int PULSE_REASON_SENSOR_PICKUP = 3;
     public static final int PULSE_REASON_SENSOR_DOUBLE_TAP = 4;
     public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
-    public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 6;
-    public static final int REASON_SENSOR_WAKE_UP = 7;
+    public static final int REASON_SENSOR_WAKE_UP = 6;
 
     private static boolean sRegisterKeyguardCallback = true;
 
@@ -212,7 +211,6 @@
             case PULSE_REASON_SENSOR_PICKUP: return "pickup";
             case PULSE_REASON_SENSOR_DOUBLE_TAP: return "doubletap";
             case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
-            case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakeLockScreen";
             case REASON_SENSOR_WAKE_UP: return "wakeup";
             default: throw new IllegalArgumentException("bad reason: " + pulseReason);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 7e77843..c2676d0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -17,7 +17,6 @@
 package com.android.systemui.doze;
 
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
-import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
 
 import android.annotation.AnyThread;
 import android.app.ActivityManager;
@@ -26,7 +25,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
@@ -114,14 +112,7 @@
                         DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
                         true /* reports touch coordinates */,
                         true /* touchscreen */),
-                new PluginTriggerSensor(
-                        new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
-                        Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
-                        true /* configured */,
-                        DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN,
-                        false /* reports touch coordinates */,
-                        false /* touchscreen */),
-                new PluginTriggerSensor(
+                new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
                         Settings.Secure.DOZE_WAKE_SCREEN_GESTURE,
                         true /* configured */,
@@ -272,7 +263,7 @@
         }
 
         @Override
-        public void onSensorChanged(SensorEvent event) {
+        public void onSensorChanged(android.hardware.SensorEvent event) {
             if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
 
             mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
@@ -417,7 +408,7 @@
 
         protected String triggerEventToString(TriggerEvent event) {
             if (event == null) return null;
-            final StringBuilder sb = new StringBuilder("TriggerEvent[")
+            final StringBuilder sb = new StringBuilder("SensorEvent[")
                     .append(event.timestamp).append(',')
                     .append(event.sensor.getName());
             if (event.values != null) {
@@ -432,23 +423,19 @@
     /**
      * A Sensor that is injected via plugin.
      */
-    private class PluginTriggerSensor extends TriggerSensor {
+    private class PluginSensor extends TriggerSensor {
 
         private final SensorManagerPlugin.Sensor mPluginSensor;
-        private final SensorManagerPlugin.TriggerEventListener mTriggerEventListener = (event) -> {
+        private final SensorManagerPlugin.SensorEventListener mTriggerEventListener = (event) -> {
             DozeLog.traceSensor(mContext, mPulseReason);
             mHandler.post(mWakeLock.wrap(() -> {
-                if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
-                mRegistered = false;
+                if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
                 mCallback.onSensorPulse(mPulseReason, true /* sensorPerformsProxCheck */, -1, -1,
                         event.getValues());
-                if (!mRegistered) {
-                    updateListener();  // reregister, this sensor only fires once
-                }
             }));
         };
 
-        PluginTriggerSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
+        PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
                 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
             super(null, setting, configured, pulseReason, reportsTouchCoordinates,
                     requiresTouchscreen);
@@ -460,13 +447,13 @@
             if (!mConfigured) return;
             AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager;
             if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
-                asyncSensorManager.requestPluginTriggerSensor(mPluginSensor, mTriggerEventListener);
+                asyncSensorManager.registerPluginListener(mPluginSensor, mTriggerEventListener);
                 mRegistered = true;
-                if (DEBUG) Log.d(TAG, "requestPluginTriggerSensor");
+                if (DEBUG) Log.d(TAG, "registerPluginListener");
             } else if (mRegistered) {
-                asyncSensorManager.cancelPluginTriggerSensor(mPluginSensor, mTriggerEventListener);
+                asyncSensorManager.unregisterPluginListener(mPluginSensor, mTriggerEventListener);
                 mRegistered = false;
-                if (DEBUG) Log.d(TAG, "cancelPluginTriggerSensor");
+                if (DEBUG) Log.d(TAG, "unregisterPluginListener");
             }
         }
 
@@ -479,7 +466,7 @@
                     .append(", mSensor=").append(mPluginSensor).append("}").toString();
         }
 
-        private String triggerEventToString(SensorManagerPlugin.TriggerEvent event) {
+        private String triggerEventToString(SensorManagerPlugin.SensorEvent event) {
             if (event == null) return null;
             final StringBuilder sb = new StringBuilder("PluginTriggerEvent[")
                     .append(event.getSensor()).append(',')
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index afe9a74..1da8976 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -53,6 +53,12 @@
     /** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */
     private static final String PULSE_ACTION = "com.android.systemui.doze.pulse";
 
+    /**
+     * Last value sent by the wake-display sensor.
+     * Assuming that the screen should start on.
+     */
+    private static boolean sWakeDisplaySensorState = true;
+
     private final Context mContext;
     private final DozeMachine mMachine;
     private final DozeSensors mDozeSensors;
@@ -128,7 +134,6 @@
         boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
         boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
         boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
-        boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
         boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
         boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0;
 
@@ -145,14 +150,6 @@
                 if (isDoubleTap) {
                     mDozeHost.onDoubleTap(screenX, screenY);
                     mMachine.wakeUp();
-                } else if (isWakeLockScreen) {
-                    if (wakeEvent) {
-                        mDozeHost.setPassiveInterrupt(true);
-                        mMachine.wakeUp();
-                        DozeLog.traceLockScreenWakeUp(wakeEvent);
-                    } else {
-                        if (DEBUG) Log.d(TAG, "Unpulsing");
-                    }
                 } else if (isPickup) {
                     mDozeHost.setPassiveInterrupt(true);
                     mMachine.wakeUp();
@@ -199,6 +196,7 @@
         DozeMachine.State state = mMachine.getState();
         boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
         boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
+        sWakeDisplaySensorState = wake;
 
         if (wake) {
             proximityCheckThenCall((result) -> {
@@ -234,6 +232,9 @@
                 }
                 mDozeSensors.setListening(true);
                 mDozeHost.setPassiveInterrupt(false);
+                if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
+                    onWakeScreen(false);
+                }
                 break;
             case DOZE_AOD_PAUSED:
             case DOZE_AOD_PAUSING:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/LockScreenWakeUpController.java b/packages/SystemUI/src/com/android/systemui/doze/LockScreenWakeUpController.java
new file mode 100644
index 0000000..ebfafce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/LockScreenWakeUpController.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.systemui.doze;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.util.AsyncSensorManager;
+
+/**
+ * Controller responsible for waking up or making the device sleep based on ambient sensors.
+ */
+public class LockScreenWakeUpController implements StatusBarStateController.StateListener,
+        SensorManagerPlugin.SensorEventListener {
+
+    private static final String TAG = LockScreenWakeUpController.class.getSimpleName();
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final AsyncSensorManager mAsyncSensorManager;
+    private final SensorManagerPlugin.Sensor mSensor;
+    private final AmbientDisplayConfiguration mAmbientConfiguration;
+    private final PowerManager mPowerManager;
+    private final DozeHost mDozeHost;
+    private final Handler mHandler;
+    private boolean mRegistered;
+    private boolean mDozing;
+
+    public LockScreenWakeUpController(Context context, DozeHost dozeHost) {
+        this(Dependency.get(AsyncSensorManager.class),
+                new SensorManagerPlugin.Sensor(SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN),
+                new AmbientDisplayConfiguration(context),
+                context.getSystemService(PowerManager.class),
+                dozeHost, Dependency.get(StatusBarStateController.class), new Handler());
+    }
+
+    @VisibleForTesting
+    public LockScreenWakeUpController(AsyncSensorManager asyncSensorManager,
+            SensorManagerPlugin.Sensor sensor, AmbientDisplayConfiguration ambientConfiguration,
+            PowerManager powerManager, DozeHost dozeHost,
+            StatusBarStateController statusBarStateController, Handler handler) {
+        mAsyncSensorManager = asyncSensorManager;
+        mSensor = sensor;
+        mAmbientConfiguration = ambientConfiguration;
+        mPowerManager = powerManager;
+        mDozeHost = dozeHost;
+        mHandler = handler;
+        statusBarStateController.addCallback(this);
+    }
+
+    @Override
+    public void onStateChanged(int newState) {
+        boolean isLockScreen = newState == StatusBarState.KEYGUARD
+                || newState == StatusBarState.SHADE_LOCKED;
+
+        if (!mAmbientConfiguration.wakeLockScreenGestureEnabled(UserHandle.USER_CURRENT)) {
+            if (mRegistered) {
+                mAsyncSensorManager.unregisterPluginListener(mSensor, this);
+                mRegistered = false;
+            }
+            return;
+        }
+
+        if (isLockScreen && !mRegistered) {
+            mAsyncSensorManager.registerPluginListener(mSensor, this);
+            mRegistered = true;
+        } else if (!isLockScreen && mRegistered) {
+            mAsyncSensorManager.unregisterPluginListener(mSensor, this);
+            mRegistered = false;
+        }
+    }
+
+    @Override
+    public void onDozingChanged(boolean isDozing) {
+        mDozing = isDozing;
+    }
+
+    @Override
+    public void onSensorChanged(SensorManagerPlugin.SensorEvent event) {
+        mHandler.post(()-> {
+            float[] rawValues = event.getValues();
+            boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0;
+
+            DozeLog.traceLockScreenWakeUp(wakeEvent);
+            if (wakeEvent && mDozing) {
+                mDozeHost.setPassiveInterrupt(true);
+                if (DEBUG) Log.d(TAG, "Wake up.");
+                mPowerManager.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE");
+            } else if (!wakeEvent && !mDozing) {
+                if (DEBUG) Log.d(TAG, "Nap time.");
+                mPowerManager.goToSleep(SystemClock.uptimeMillis(),
+                        PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
+            } else if (DEBUG) {
+                Log.d(TAG, "Skip sensor event. Wake? " + wakeEvent + " dozing: " + mDozing);
+            }
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9ccdf79..b133346 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -406,24 +406,6 @@
         }
 
         @Override
-        public void onPhoneStateChanged(int phoneState) {
-            synchronized (KeyguardViewMediator.this) {
-                if (TelephonyManager.CALL_STATE_IDLE == phoneState  // call ending
-                        && !mDeviceInteractive                           // screen off
-                        && mExternallyEnabled) {                // not disabled by any app
-
-                    // note: this is a way to gracefully reenable the keyguard when the call
-                    // ends and the screen is off without always reenabling the keyguard
-                    // each time the screen turns off while in call (and having an occasional ugly
-                    // flicker while turning back on the screen and disabling the keyguard again).
-                    if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the "
-                            + "keyguard is showing");
-                    doKeyguardLocked(null);
-                }
-            }
-        }
-
-        @Override
         public void onClockVisibilityChanged() {
             adjustStatusBarLocked();
         }
@@ -1316,15 +1298,7 @@
         if (!mExternallyEnabled) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
 
-            // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
-            // for an occasional ugly flicker in this situation:
-            // 1) receive a call with the screen on (no keyguard) or make a call
-            // 2) screen times out
-            // 3) user hits key to turn screen back on
-            // instead, we reenable the keyguard when we know the screen is off and the call
-            // ends (see the broadcast receiver below)
-            // TODO: clean this up when we have better support at the window manager level
-            // for apps that wish to be on top of the keyguard
+            mNeedToReshowWhenReenabled = true;
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 9aa21f8..69efbc8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -84,7 +84,7 @@
      * Processes a given touch event and updates the state.
      */
     public void onTouchEvent(MotionEvent ev) {
-        switch (ev.getAction()) {
+        switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 if (!mAllowTouches) {
                     return;
@@ -92,12 +92,13 @@
 
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
+                addMovement(ev);
 
                 mActivePointerId = ev.getPointerId(0);
                 if (DEBUG) {
                     Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
                 }
-                mLastTouch.set(ev.getX(), ev.getY());
+                mLastTouch.set(ev.getRawX(), ev.getRawY());
                 mDownTouch.set(mLastTouch);
                 mAllowDraggingOffscreen = true;
                 mIsUserInteracting = true;
@@ -118,15 +119,15 @@
                 }
 
                 // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
+                addMovement(ev);
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == -1) {
                     Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
                     break;
                 }
 
-                float x = ev.getX(pointerIndex);
-                float y = ev.getY(pointerIndex);
+                float x = ev.getRawX(pointerIndex);
+                float y = ev.getRawY(pointerIndex);
                 mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
                 mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
 
@@ -149,7 +150,7 @@
                 }
 
                 // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
+                addMovement(ev);
 
                 int pointerIndex = ev.getActionIndex();
                 int pointerId = ev.getPointerId(pointerIndex);
@@ -161,7 +162,7 @@
                         Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
                                 mActivePointerId);
                     }
-                    mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
+                    mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
                 }
                 break;
             }
@@ -172,7 +173,7 @@
                 }
 
                 // Update the velocity tracker
-                mVelocityTracker.addMovement(ev);
+                addMovement(ev);
                 mVelocityTracker.computeCurrentVelocity(1000,
                         mViewConfig.getScaledMaximumFlingVelocity());
                 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -184,7 +185,7 @@
                 }
 
                 mUpTouchTime = ev.getEventTime();
-                mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
                 mPreviouslyDragging = mIsDragging;
                 mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
                         (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
@@ -331,6 +332,16 @@
         }
     }
 
+    private void addMovement(MotionEvent event) {
+        // Add movement to velocity tracker using raw screen X and Y coordinates instead
+        // of window coordinates because the window frame may be moving at the same time.
+        float deltaX = event.getRawX() - event.getX();
+        float deltaY = event.getRawY() - event.getY();
+        event.offsetLocation(deltaX, deltaY);
+        mVelocityTracker.addMovement(event);
+        event.offsetLocation(-deltaX, -deltaY);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 7d52f0b..fd2c4e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.tiles.NfcTile;
 import com.android.systemui.qs.tiles.NightDisplayTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.SensorPrivacyTile;
 import com.android.systemui.qs.tiles.UserTile;
 import com.android.systemui.qs.tiles.WifiTile;
 import com.android.systemui.qs.tiles.WorkModeTile;
@@ -100,6 +101,8 @@
                 return new NightDisplayTile(mHost);
             case "nfc":
                 return new NfcTile(mHost);
+            case "sensorprivacy":
+                return new SensorPrivacyTile(mHost);
         }
 
         // Intent tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 32fd2dc..bfcf021 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
@@ -88,6 +89,7 @@
         float pathSize = AdaptiveIconDrawable.MASK_SIZE;
         PathShape p = new PathShape(path, pathSize, pathSize);
         ShapeDrawable d = new ShapeDrawable(p);
+        d.setTintList(ColorStateList.valueOf(Color.TRANSPARENT));
         int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
         d.setIntrinsicHeight(bgSize);
         d.setIntrinsicWidth(bgSize);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
new file mode 100644
index 0000000..ff368f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
@@ -0,0 +1,126 @@
+/*
+ * 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 com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.hardware.SensorPrivacyManager;
+import android.service.quicksettings.Tile;
+import android.widget.Switch;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+/** Quick settings tile: SensorPrivacy mode **/
+public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements
+        SensorPrivacyManager.OnSensorPrivacyChangedListener {
+    private static final String TAG = "SensorPrivacy";
+    private final Icon mIcon =
+            ResourceIcon.get(R.drawable.ic_signal_sensors);
+    private final KeyguardMonitor mKeyguard;
+    private final SensorPrivacyManager mSensorPrivacyManager;
+
+    public SensorPrivacyTile(QSHost host) {
+        super(host);
+
+        mSensorPrivacyManager = Dependency.get(SensorPrivacyManager.class);
+        mKeyguard = Dependency.get(KeyguardMonitor.class);
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    @Override
+    public void handleClick() {
+        final boolean wasEnabled = mState.value;
+        // Don't allow disabling from the lockscreen.
+        if (wasEnabled && mKeyguard.isSecure() && mKeyguard.isShowing()) {
+            Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+                MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+                setEnabled(!wasEnabled);
+            });
+            return;
+        }
+
+        MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+        setEnabled(!wasEnabled);
+    }
+
+    private void setEnabled(boolean enabled) {
+        mSensorPrivacyManager.setSensorPrivacy(enabled);
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.sensor_privacy_mode);
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        final boolean enabled = arg instanceof Boolean ? (Boolean) arg
+                : mSensorPrivacyManager.isSensorPrivacyEnabled();
+        state.value = enabled;
+        state.label = mContext.getString(R.string.sensor_privacy_mode);
+        state.icon = mIcon;
+        state.state = enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.contentDescription = state.label;
+        state.expandedAccessibilityClassName = Switch.class.getName();
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.QS_SENSOR_PRIVACY;
+    }
+
+    @Override
+    protected String composeChangeAnnouncement() {
+        if (mState.value) {
+            return mContext
+                    .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_on);
+        } else {
+            return mContext
+                    .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_off);
+        }
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+        if (listening) {
+            mSensorPrivacyManager.addSensorPrivacyListener(this);
+        } else {
+            mSensorPrivacyManager.removeSensorPrivacyListener(this);
+        }
+    }
+
+    @Override
+    public void onSensorPrivacyChanged(boolean enabled) {
+        refreshState(enabled);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index d8f7b71..6939ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.WifiIcons;
 
 import java.util.List;
 
@@ -190,7 +191,7 @@
         } else if (!state.value) {
             state.slash.isSlashed = true;
             state.state = Tile.STATE_INACTIVE;
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled);
+            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
             state.label = r.getString(R.string.quick_settings_wifi_label);
         } else if (wifiConnected) {
             state.icon = ResourceIcon.get(cb.wifiSignalIconId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6cec36a..91b34fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -916,6 +916,10 @@
         updateRelativeOffset();
     }
 
+    public void onUiModeChanged() {
+        updateBackgroundColors();
+    }
+
     private class ShelfState extends ExpandableViewState {
         private float openedAmount;
         private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index dc3a607..0702f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -437,7 +437,7 @@
             }
 
             row.showAppOpsIcons(entry.mActiveAppOps);
-            row.setAudiblyAlerted(entry.audiblyAlerted);
+            row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs);
         }
 
         Trace.beginSection("NotificationPresenter#onUpdateRowStates");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java
new file mode 100644
index 0000000..13e991b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.systemui.statusbar.notification;
+
+/**
+ * Listener interface for when NotificationEntryManager needs to tell
+ * NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose
+ * listening interface for the NotificationEntryManager.
+ */
+public interface AlertTransferListener {
+    /**
+     * Called when a new notification is posted. At this point, the notification is "pending": its
+     * views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
+     */
+    void onPendingEntryAdded(NotificationData.Entry entry);
+
+    /**
+     * Called when an existing notification's views are reinflated (usually due to an update being
+     * posted to that notification).
+     */
+    void onEntryReinflated(NotificationData.Entry entry);
+
+    /**
+     * Called when a notification has been removed (either because the user swiped it away or
+     * because the developer retracted it).
+     */
+    void onEntryRemoved(NotificationData.Entry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index f543b46..ae9f323 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -103,7 +103,7 @@
         public String key;
         public StatusBarNotification notification;
         public NotificationChannel channel;
-        public boolean audiblyAlerted;
+        public long lastAudiblyAlertedMs;
         public boolean noisy;
         public int importance;
         public StatusBarIconView icon;
@@ -172,7 +172,7 @@
 
         public void populateFromRanking(@NonNull Ranking ranking) {
             channel = ranking.getChannel();
-            audiblyAlerted = ranking.audiblyAlerted();
+            lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
             importance = ranking.getImportance();
             snoozeCriteria = ranking.getSnoozeCriteria();
             userSentiment = ranking.getUserSentiment();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 16a3849..aab3fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,6 +32,7 @@
 import android.database.ContentObserver;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -61,7 +62,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.bubbles.BubbleController;
@@ -83,7 +83,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -96,6 +95,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -110,6 +110,8 @@
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
+    public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+
     private final NotificationMessagingUtil mMessagingUtil;
     protected final Context mContext;
     protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
@@ -117,8 +119,6 @@
 
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
-    private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper =
-            Dependency.get(NotificationGroupAlertTransferHelper.class);
     private final NotificationGutsManager mGutsManager =
             Dependency.get(NotificationGutsManager.class);
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -139,6 +139,9 @@
     private NotificationListener mNotificationListener;
     private ShadeController mShadeController;
 
+    private final Handler mDeferredNotificationViewUpdateHandler;
+    private Runnable mUpdateNotificationViewsCallback;
+
     protected IDreamManager mDreamManager;
     protected IStatusBarService mBarService;
     private NotificationPresenter mPresenter;
@@ -157,6 +160,7 @@
             = new ArrayList<>();
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener;
+    @Nullable private AlertTransferListener mAlertTransferListener;
 
     private final class NotificationClicker implements View.OnClickListener {
 
@@ -258,12 +262,11 @@
         mMessagingUtil = new NotificationMessagingUtil(context);
         mBubbleController.setDismissListener(this /* bubbleEventListener */);
         mNotificationData = new NotificationData();
-        Dependency.get(InitController.class).addPostInitTask(this::onPostInit);
+        mDeferredNotificationViewUpdateHandler = new Handler();
     }
 
-    private void onPostInit() {
-        mGroupAlertTransferHelper.setPendingEntries(mPendingNotifications);
-        mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
+    public void setAlertTransferListener(AlertTransferListener listener) {
+        mAlertTransferListener = listener;
     }
 
     /**
@@ -301,6 +304,7 @@
             NotificationListContainer listContainer, Callback callback,
             HeadsUpManager headsUpManager) {
         mPresenter = presenter;
+        mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
         mCallback = callback;
         mHeadsUpManager = headsUpManager;
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
@@ -540,6 +544,17 @@
         tagForeground(shadeEntry.notification);
         updateNotifications();
         mCallback.onNotificationAdded(shadeEntry);
+
+        maybeScheduleUpdateNotificationViews(shadeEntry);
+    }
+
+    private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) {
+        long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
+                - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
+        if (audibleAlertTimeout > 0) {
+            mDeferredNotificationViewUpdateHandler.postDelayed(
+                    mUpdateNotificationViewsCallback, audibleAlertTimeout);
+        }
     }
 
     /**
@@ -587,7 +602,9 @@
                     mVisualStabilityManager.onLowPriorityUpdated(entry);
                     mPresenter.updateNotificationViews();
                 }
-                mGroupAlertTransferHelper.onInflationFinished(entry);
+                if (mAlertTransferListener != null) {
+                    mAlertTransferListener.onEntryReinflated(entry);
+                }
             }
         }
         entry.setLowPriorityStateUpdated(false);
@@ -600,8 +617,12 @@
 
     private void removeNotificationInternal(String key,
             @Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) {
+        final NotificationData.Entry entry = mNotificationData.get(key);
+
         abortExistingInflation(key);
-        mGroupAlertTransferHelper.cleanUpPendingAlertInfo(key);
+        if (mAlertTransferListener != null && entry != null) {
+            mAlertTransferListener.onEntryRemoved(entry);
+        }
 
         // Attempt to remove notifications from their alert managers (heads up, ambient pulse).
         // Though the remove itself may fail, it lets the manager know to remove as soon as
@@ -620,8 +641,6 @@
             mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
         }
 
-        NotificationData.Entry entry = mNotificationData.get(key);
-
         if (entry == null) {
             mCallback.onNotificationRemoved(key, null /* old */);
             return;
@@ -846,7 +865,9 @@
                 mNotificationData.getImportance(key));
 
         mPendingNotifications.put(key, shadeEntry);
-        mGroupAlertTransferHelper.onPendingEntryAdded(shadeEntry);
+        if (mAlertTransferListener != null) {
+            mAlertTransferListener.onPendingEntryAdded(shadeEntry);
+        }
     }
 
     @VisibleForTesting
@@ -937,6 +958,8 @@
         }
 
         mCallback.onNotificationUpdated(notification);
+
+        maybeScheduleUpdateNotificationViews(entry);
     }
 
     @Override
@@ -1231,6 +1254,15 @@
     }
 
     /**
+     * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
+     * notifications whose views have not yet been inflated. In general, the system pretends like
+     * these don't exist, although there are a couple exceptions.
+     */
+    public Iterable<NotificationData.Entry> getPendingNotificationsIterator() {
+        return mPendingNotifications.values();
+    }
+
+    /**
      * Callback for NotificationEntryManager.
      */
     public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7876b24..1d79152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -225,7 +225,7 @@
         initDimens();
     }
 
-    public void onUiModeChanged() {
+    protected void updateBackgroundColors() {
         updateColors();
         initBackground();
         updateBackgroundTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 8bed366..91d08ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -87,6 +87,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
@@ -121,6 +122,7 @@
     private static final int MENU_VIEW_INDEX = 0;
     private static final String TAG = "ExpandableNotifRow";
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
+    private boolean mUpdateBackgroundOnUpdate;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -587,6 +589,10 @@
         updateIconVisibilities();
         updateShelfIconColor();
         updateRippleAllowed();
+        if (mUpdateBackgroundOnUpdate) {
+            mUpdateBackgroundOnUpdate = false;
+            updateBackgroundColors();
+        }
     }
 
     /** Called when the notification's ranking was changed (but nothing else changed). */
@@ -1212,9 +1218,8 @@
         }
     }
 
-    @Override
     public void onUiModeChanged() {
-        super.onUiModeChanged();
+        mUpdateBackgroundOnUpdate = true;
         reInflateViews();
         if (mChildrenContainer != null) {
             for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
@@ -1685,14 +1690,17 @@
         mPublicLayout.showAppOpsIcons(activeOps);
     }
 
-    /** Sets whether the notification being displayed audibly alerted the user. */
-    public void setAudiblyAlerted(boolean audiblyAlerted) {
+    /** Sets the last time the notification being displayed audibly alerted the user. */
+    public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
         if (NotificationUtils.useNewInterruptionModel(mContext)) {
+            boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
+                    < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
             if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
-                mChildrenContainer.getHeaderView().setAudiblyAlerted(audiblyAlerted);
+                mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
+                        recentlyAudiblyAlerted);
             }
-            mPrivateLayout.setAudiblyAlerted(audiblyAlerted);
-            mPublicLayout.setAudiblyAlerted(audiblyAlerted);
+            mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
+            mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index edd54ca..6bc39c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1615,15 +1615,15 @@
     }
 
     /** Sets whether the notification being displayed audibly alerted the user. */
-    public void setAudiblyAlerted(boolean audiblyAlerted) {
+    public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
         if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
-            mContractedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+            mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
         }
         if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
-            mExpandedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+            mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
         }
         if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
-            mHeadsUpWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+            mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index eca1a14..dbe6e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -641,15 +641,7 @@
     public void onUiModeChanged() {
         mBgColor = mContext.getColor(R.color.notification_shade_background_color);
         updateBackgroundDimming();
-
-        // Re-inflate all notification views
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child instanceof ActivatableNotificationView) {
-                ((ActivatableNotificationView) child).onUiModeChanged();
-            }
-        }
+        mShelf.onUiModeChanged();
     }
 
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 80c4eb0..5906dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -26,6 +26,9 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 public class DarkIconDispatcherImpl implements DarkIconDispatcher {
 
     private final LightBarTransitionsController mTransitionsController;
@@ -74,7 +77,7 @@
     }
 
     /**
-     * Sets the dark area so {@link #setIconsDark} only affects the icons in the specified area.
+     * Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
      *
      * @param darkArea the area in which icons should change it's tint, in logical screen
      *                 coordinates
@@ -103,4 +106,12 @@
             mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
         }
     }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("DarkIconDispatcher: ");
+        pw.println("  mIconTint: 0x" + Integer.toHexString(mIconTint));
+        pw.println("  mDarkIntensity: " + mDarkIntensity + "f");
+        pw.println("  mTintArea: " + mTintArea);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index e0c5516..7c30e48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -76,11 +76,14 @@
     private final Rect mLastDockedBounds = new Rect();
     private boolean mQsCustomizing;
 
+    private final Context mContext;
+
     public LightBarController(Context ctx) {
         mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
         mStatusBarIconController = Dependency.get(DarkIconDispatcher.class);
         mBatteryController = Dependency.get(BatteryController.class);
         mBatteryController.addCallback(this);
+        mContext = ctx;
     }
 
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -217,8 +220,9 @@
 
     private void updateNavigation() {
         if (mNavigationBarController != null) {
-            mNavigationBarController.setIconsDark(
-                    mNavigationLight, animateChange());
+            if (!NavBarTintController.isEnabled(mContext)) {
+                mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
+            }
         }
     }
 
@@ -259,6 +263,10 @@
 
         pw.println();
 
+        if (mStatusBarIconController != null) {
+            mStatusBarIconController.dump(fd, pw, args);
+        }
+
         LightBarTransitionsController transitionsController =
                 mStatusBarIconController.getTransitionsController();
         if (transitionsController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 57cc7d6..7876aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -16,12 +16,16 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME;
+import static com.android.systemui.statusbar.phone.NavBarTintController.NAV_COLOR_TRANSITION_TIME_SETTING;
+
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.MathUtils;
+import android.provider.Settings;
 import android.util.TimeUtils;
 
 import com.android.systemui.Dependency;
@@ -42,13 +46,14 @@
 public class LightBarTransitionsController implements Dumpable, Callbacks,
         StatusBarStateController.StateListener {
 
-    public static final long DEFAULT_TINT_ANIMATION_DURATION = 120;
+    public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
     private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
 
     private final Handler mHandler;
     private final DarkIntensityApplier mApplier;
     private final KeyguardMonitor mKeyguardMonitor;
     private final StatusBarStateController mStatusBarStateController;
+    private NavBarTintController mColorAdaptionController;
 
     private boolean mTransitionDeferring;
     private long mTransitionDeferringStartTime;
@@ -67,6 +72,8 @@
         }
     };
 
+    private final Context mContext;
+
     public LightBarTransitionsController(Context context, DarkIntensityApplier applier) {
         mApplier = applier;
         mHandler = new Handler();
@@ -76,6 +83,7 @@
                 .addCallback(this);
         mStatusBarStateController.addCallback(this);
         mDozeAmount = mStatusBarStateController.getDozeAmount();
+        mContext = context;
     }
 
     public void destroy(Context context) {
@@ -106,7 +114,7 @@
     public void appTransitionCancelled() {
         if (mTransitionPending && mTintChangePending) {
             mTintChangePending = false;
-            animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+            animateIconTint(mPendingDarkIntensity, 0 /* delay */, getTintAnimationDuration());
         }
         mTransitionPending = false;
     }
@@ -146,10 +154,19 @@
                     Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
                     mTransitionDeferringDuration);
         } else {
-            animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION);
+            animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, getTintAnimationDuration());
         }
     }
 
+    public long getTintAnimationDuration() {
+        if (NavBarTintController.isEnabled(mContext)) {
+            return Math.max(Settings.Global.getInt(mContext.getContentResolver(),
+                    NAV_COLOR_TRANSITION_TIME_SETTING, DEFAULT_TINT_ANIMATION_DURATION),
+                    MIN_COLOR_ADAPT_TRANSITION_TIME);
+        }
+        return DEFAULT_TINT_ANIMATION_DURATION;
+    }
+
     public float getCurrentDarkIntensity() {
         return mDarkIntensity;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
new file mode 100644
index 0000000..9ecee18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
@@ -0,0 +1,208 @@
+/*
+ * 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 com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+
+public class NavBarTintController {
+    public static final String NAV_COLOR_TRANSITION_TIME_SETTING = "navbar_color_adapt_transition";
+    public static final int MIN_COLOR_ADAPT_TRANSITION_TIME = 400;
+
+    private final HandlerThread mColorAdaptHandlerThread = new HandlerThread("ColorExtractThread");
+    private Handler mColorAdaptionHandler;
+
+    // Poll time for each iteration to color sample
+    private static final int COLOR_ADAPTION_TIMEOUT = 300;
+
+    // Passing the threshold of this luminance value will make the button black otherwise white
+    private static final float LUMINANCE_THRESHOLD = 0.3f;
+
+    // The home button's icon is actually smaller than the button's size, the percentage will
+    // cut into the button's size to determine the icon size
+    private static final float PERCENTAGE_BUTTON_PADDING = 0.3f;
+
+    // The distance from the home button to color sample around
+    private static final int COLOR_SAMPLE_MARGIN = 20;
+
+    private boolean mRunning;
+
+    private final NavigationBarView mNavigationBarView;
+    private final LightBarTransitionsController mLightBarController;
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    public NavBarTintController(NavigationBarView navigationBarView,
+            LightBarTransitionsController lightBarController) {
+        mNavigationBarView = navigationBarView;
+        mLightBarController = lightBarController;
+    }
+
+    public void start() {
+        if (!isEnabled(mNavigationBarView.getContext())) {
+            return;
+        }
+        if (mColorAdaptionHandler == null) {
+            mColorAdaptHandlerThread.start();
+            mColorAdaptionHandler = new Handler(mColorAdaptHandlerThread.getLooper());
+        }
+        mColorAdaptionHandler.removeCallbacksAndMessages(null);
+        mColorAdaptionHandler.post(this::updateTint);
+        mRunning = true;
+    }
+
+    public void end() {
+        if (mColorAdaptionHandler != null) {
+            mColorAdaptionHandler.removeCallbacksAndMessages(null);
+        }
+        mRunning = false;
+    }
+
+    public void stop() {
+        end();
+        if (mColorAdaptionHandler != null) {
+            mColorAdaptHandlerThread.quitSafely();
+        }
+    }
+
+    private void updateTint() {
+        int[] navPos = new int[2];
+        int[] butPos = new int[2];
+        if (mNavigationBarView.getHomeButton().getCurrentView() == null) {
+            return;
+        }
+
+        // Determine the area of the home icon in the larger home button
+        mNavigationBarView.getHomeButton().getCurrentView().getLocationInSurface(butPos);
+        final int navWidth = mNavigationBarView.getHomeButton().getCurrentView().getWidth();
+        final int navHeight = mNavigationBarView.getHomeButton().getCurrentView().getHeight();
+        final int xPadding = (int) (PERCENTAGE_BUTTON_PADDING * navWidth);
+        final int yPadding = (int) (PERCENTAGE_BUTTON_PADDING * navHeight);
+        final Rect homeButtonRect = new Rect(butPos[0] + xPadding, butPos[1] + yPadding,
+                navWidth + butPos[0]  - xPadding, navHeight + butPos[1] - yPadding);
+        if (mNavigationBarView.getCurrentView() == null || homeButtonRect.isEmpty()) {
+            scheduleColorAdaption();
+            return;
+        }
+        mNavigationBarView.getCurrentView().getLocationOnScreen(navPos);
+        homeButtonRect.offset(navPos[0], navPos[1]);
+
+        // Apply a margin area around the button region to sample the colors, crop from screenshot
+        final Rect cropRect = new Rect(homeButtonRect);
+        cropRect.inset(-COLOR_SAMPLE_MARGIN, -COLOR_SAMPLE_MARGIN);
+        if (cropRect.isEmpty()) {
+            scheduleColorAdaption();
+            return;
+        }
+
+        // Determine the size of the home area
+        Rect homeArea = new Rect(COLOR_SAMPLE_MARGIN, COLOR_SAMPLE_MARGIN,
+                homeButtonRect.width() + COLOR_SAMPLE_MARGIN,
+                homeButtonRect.height() + COLOR_SAMPLE_MARGIN);
+
+        // Get the screenshot around the home button icon to determine the color
+        DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+        mNavigationBarView.getContext().getDisplay().getRealMetrics(mDisplayMetrics);
+        final Bitmap hardBitmap = SurfaceControl
+                .screenshot(new Rect(), mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+                        mNavigationBarView.getContext().getDisplay().getRotation());
+        if (hardBitmap != null && cropRect.bottom <= hardBitmap.getHeight()) {
+            final Bitmap cropBitmap = Bitmap.createBitmap(hardBitmap, cropRect.left, cropRect.top,
+                    cropRect.width(), cropRect.height());
+            final Bitmap softBitmap = cropBitmap.copy(Config.ARGB_8888, false);
+
+            // Get the luminance value to determine if the home button should be black or white
+            final int[] pixels = new int[softBitmap.getByteCount() / 4];
+            softBitmap.getPixels(pixels, 0, softBitmap.getWidth(), 0, 0, softBitmap.getWidth(),
+                    softBitmap.getHeight());
+            float r = 0, g = 0, blue = 0;
+
+            int width = cropRect.width();
+            int total = 0;
+            for (int i = 0; i < pixels.length; i += 4) {
+                int x = i % width;
+                int y = i / width;
+                if (!homeArea.contains(x, y)) {
+                    r += Color.red(pixels[i]);
+                    g += Color.green(pixels[i]);
+                    blue += Color.blue(pixels[i]);
+                    total++;
+                }
+            }
+
+            r /= total;
+            g /= total;
+            blue /= total;
+
+            r = Math.max(Math.min(r / 255f, 1), 0);
+            g = Math.max(Math.min(g / 255f, 1), 0);
+            blue = Math.max(Math.min(blue / 255f, 1), 0);
+
+            if (r <= 0.03928) {
+                r /= 12.92;
+            } else {
+                r = (float) Math.pow((r + 0.055) / 1.055, 2.4);
+            }
+            if (g <= 0.03928) {
+                g /= 12.92;
+            } else {
+                g = (float) Math.pow((g + 0.055) / 1.055, 2.4);
+            }
+            if (blue <= 0.03928) {
+                blue /= 12.92;
+            } else {
+                blue = (float) Math.pow((blue + 0.055) / 1.055, 2.4);
+            }
+
+            if (r * 0.2126 + g * 0.7152 + blue * 0.0722 > LUMINANCE_THRESHOLD) {
+                // Black
+                mMainHandler.post(
+                        () -> mLightBarController
+                                .setIconsDark(true /* dark */, true /* animate */));
+            } else {
+                // White
+                mMainHandler.post(
+                        () -> mLightBarController
+                                .setIconsDark(false /* dark */, true /* animate */));
+            }
+            cropBitmap.recycle();
+            hardBitmap.recycle();
+        }
+        scheduleColorAdaption();
+    }
+
+    private void scheduleColorAdaption() {
+        mColorAdaptionHandler.removeCallbacksAndMessages(null);
+        if (!mRunning || !isEnabled(mNavigationBarView.getContext())) {
+            return;
+        }
+        mColorAdaptionHandler.postDelayed(this::updateTint, COLOR_ADAPTION_TIMEOUT);
+    }
+
+    public static boolean isEnabled(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                NavigationPrototypeController.NAV_COLOR_ADAPT_ENABLE_SETTING, 0) == 1;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index ae0a14529..55655d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -851,6 +851,16 @@
             if (Intent.ACTION_SCREEN_OFF.equals(action)
                     || Intent.ACTION_SCREEN_ON.equals(action)) {
                 notifyNavigationBarScreenOn();
+
+                if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                    // Enabled and screen is on, start it again if enabled
+                    if (NavBarTintController.isEnabled(getContext())) {
+                        mNavigationBarView.getColorAdaptionController().start();
+                    }
+                } else {
+                    // Screen off disable it
+                    mNavigationBarView.getColorAdaptionController().end();
+                }
             }
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 // The accessibility settings may be different for the new user
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 30e8409..6a7983a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -149,6 +149,7 @@
     private RecentsOnboarding mRecentsOnboarding;
     private NotificationPanelView mPanelView;
 
+    private NavBarTintController mColorAdaptionController;
     private NavigationPrototypeController mPrototypeController;
     private NavigationGestureAction[] mDefaultGestureMap;
     private QuickScrubAction mQuickScrubAction;
@@ -277,6 +278,15 @@
         public void onBackButtonVisibilityChanged(boolean visible) {
             getBackButton().setVisibility(visible ? VISIBLE : GONE);
         }
+
+        @Override
+        public void onColorAdaptChanged(boolean enabled) {
+            if (enabled) {
+                mColorAdaptionController.start();
+            } else {
+                mColorAdaptionController.end();
+            }
+        }
     };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
@@ -334,6 +344,11 @@
         mPrototypeController = new NavigationPrototypeController(mHandler, mContext);
         mPrototypeController.register();
         mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener);
+        mColorAdaptionController = new NavBarTintController(this, getLightTransitionsController());
+    }
+
+    public NavBarTintController getColorAdaptionController() {
+        return mColorAdaptionController;
     }
 
     public BarTransitions getBarTransitions() {
@@ -1097,6 +1112,7 @@
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
+        mColorAdaptionController.start();
     }
 
     @Override
@@ -1107,6 +1123,7 @@
             mGestureHelper.destroy();
         }
         mPrototypeController.unregister();
+        mColorAdaptionController.stop();
         setUpSwipeUpOnboarding(false);
         for (int i = 0; i < mButtonDispatchers.size(); ++i) {
             mButtonDispatchers.valueAt(i).onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index b11b6d4..40ac793 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -37,6 +37,7 @@
 
     static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
     private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
+    public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK})
@@ -73,6 +74,7 @@
     public void register() {
         registerObserver(HIDE_BACK_BUTTON_SETTING);
         registerObserver(GESTURE_MATCH_SETTING);
+        registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING);
     }
 
     /**
@@ -96,6 +98,9 @@
                 } else if (path.endsWith(HIDE_BACK_BUTTON_SETTING)) {
                     mListener.onBackButtonVisibilityChanged(
                             !getGlobalBool(HIDE_BACK_BUTTON_SETTING));
+                } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
+                    mListener.onColorAdaptChanged(
+                            NavBarTintController.isEnabled(mContext));
                 }
             } catch (SettingNotFoundException e) {
                 e.printStackTrace();
@@ -138,5 +143,6 @@
     public interface OnPrototypeChangedListener {
         void onGestureRemap(@GestureAction int[] actions);
         void onBackButtonVisibilityChanged(boolean visible);
+        void onColorAdaptChanged(boolean enabled);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 2a68fa5..dd81c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -29,7 +29,9 @@
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.AlertTransferListener;
 import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
@@ -38,8 +40,6 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.Objects;
 
 /**
@@ -47,8 +47,8 @@
  * {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping
  * the correct notification in a group alerting based off the group suppression.
  */
-public class NotificationGroupAlertTransferHelper implements OnGroupChangeListener,
-        OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener {
+public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
+        OnAmbientChangedListener, StateListener {
 
     private static final long ALERT_TRANSFER_TIMEOUT = 300;
 
@@ -69,15 +69,7 @@
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
 
-    // TODO(b/119637830): It would be good if GroupManager already had all pending notifications as
-    // normal children (i.e. add notifications to GroupManager before inflation) so that we don't
-    // have to have this dependency. We'd also have to worry less about the suppression not being up
-    // to date.
-    /**
-     * Notifications that are currently inflating for the first time. Used to remove an incorrectly
-     * alerting notification faster.
-     */
-    private HashMap<String, Entry> mPendingNotifications;
+    private NotificationEntryManager mEntryManager;
 
     private boolean mIsDozing;
 
@@ -85,6 +77,23 @@
         Dependency.get(StatusBarStateController.class).addCallback(this);
     }
 
+    /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
+    public void bind(NotificationEntryManager entryManager,
+            NotificationGroupManager groupManager) {
+        if (mEntryManager != null) {
+            throw new IllegalStateException("Already bound.");
+        }
+
+        // TODO(b/119637830): It would be good if GroupManager already had all pending notifications
+        // as normal children (i.e. add notifications to GroupManager before inflation) so that we
+        // don't have to have this dependency. We'd also have to worry less about the suppression
+        // not being up to date.
+        mEntryManager = entryManager;
+
+        mEntryManager.setAlertTransferListener(mAlertTransferListener);
+        groupManager.addOnGroupChangeListener(mOnGroupChangeListener);
+    }
+
     /**
      * Whether or not a notification has transferred its alert state to the notification and
      * the notification should alert after inflating.
@@ -97,25 +106,10 @@
         return alertInfo != null && alertInfo.isStillValid();
     }
 
-    /**
-     * Removes any alerts pending on this entry. Note that this will not stop any inflation tasks
-     * started by a transfer, so this should only be used as clean-up for when inflation is stopped
-     * and the pending alert no longer needs to happen.
-     *
-     * @param key notification key that may have info that needs to be cleaned up
-     */
-    public void cleanUpPendingAlertInfo(@NonNull String key) {
-        mPendingAlerts.remove(key);
-    }
-
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
 
-    public void setPendingEntries(HashMap<String, Entry> pendingNotifications) {
-        mPendingNotifications = pendingNotifications;
-    }
-
     @Override
     public void onStateChanged(int newState) {}
 
@@ -130,43 +124,45 @@
         mIsDozing = isDozing;
     }
 
-    @Override
-    public void onGroupCreated(NotificationGroup group, String groupKey) {
-        mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
-    }
+    private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
+        @Override
+        public void onGroupCreated(NotificationGroup group, String groupKey) {
+            mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
+        }
 
-    @Override
-    public void onGroupRemoved(NotificationGroup group, String groupKey) {
-        mGroupAlertEntries.remove(groupKey);
-    }
+        @Override
+        public void onGroupRemoved(NotificationGroup group, String groupKey) {
+            mGroupAlertEntries.remove(groupKey);
+        }
 
-    @Override
-    public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
-        AlertingNotificationManager alertManager = getActiveAlertManager();
-        if (suppressed) {
-            if (alertManager.isAlerting(group.summary.key)) {
-                handleSuppressedSummaryAlerted(group.summary, alertManager);
-            }
-        } else {
-            // Group summary can be null if we are no longer suppressed because the summary was
-            // removed. In that case, we don't need to alert the summary.
-            if (group.summary == null) {
-                return;
-            }
-            GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
-                    group.summary.notification));
-            // Group is no longer suppressed. We should check if we need to transfer the alert
-            // back to the summary now that it's no longer suppressed.
-            if (groupAlertEntry.mAlertSummaryOnNextAddition) {
-                if (!alertManager.isAlerting(group.summary.key)) {
-                    alertNotificationWhenPossible(group.summary, alertManager);
+        @Override
+        public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
+            AlertingNotificationManager alertManager = getActiveAlertManager();
+            if (suppressed) {
+                if (alertManager.isAlerting(group.summary.key)) {
+                    handleSuppressedSummaryAlerted(group.summary, alertManager);
                 }
-                groupAlertEntry.mAlertSummaryOnNextAddition = false;
             } else {
-                checkShouldTransferBack(groupAlertEntry);
+                // Group summary can be null if we are no longer suppressed because the summary was
+                // removed. In that case, we don't need to alert the summary.
+                if (group.summary == null) {
+                    return;
+                }
+                GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
+                        group.summary.notification));
+                // Group is no longer suppressed. We should check if we need to transfer the alert
+                // back to the summary now that it's no longer suppressed.
+                if (groupAlertEntry.mAlertSummaryOnNextAddition) {
+                    if (!alertManager.isAlerting(group.summary.key)) {
+                        alertNotificationWhenPossible(group.summary, alertManager);
+                    }
+                    groupAlertEntry.mAlertSummaryOnNextAddition = false;
+                } else {
+                    checkShouldTransferBack(groupAlertEntry);
+                }
             }
         }
-    }
+    };
 
     @Override
     public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
@@ -185,37 +181,42 @@
         }
     }
 
-    /**
-     * Called when the entry's reinflation has finished. If there is an alert pending, we then
-     * show the alert.
-     *
-     * @param entry entry whose inflation has finished
-     */
-    public void onInflationFinished(@NonNull Entry entry) {
-        PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
-        if (alertInfo != null) {
-            if (alertInfo.isStillValid()) {
-                alertNotificationWhenPossible(entry, getActiveAlertManager());
-            } else {
-                // The transfer is no longer valid. Free the content.
-                entry.getRow().freeContentViewWhenSafe(alertInfo.mAlertManager.getContentFlag());
+    private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() {
+        // Called when a new notification has been posted but is not inflated yet. We use this to
+        // see as early as we can if we need to abort a transfer.
+        @Override
+        public void onPendingEntryAdded(Entry entry) {
+            String groupKey = mGroupManager.getGroupKey(entry.notification);
+            GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
+            if (groupAlertEntry != null) {
+                checkShouldTransferBack(groupAlertEntry);
             }
         }
-    }
 
-    /**
-     * Called when a new notification has been posted but is not inflated yet. We use this to see
-     * as early as we can if we need to abort a transfer.
-     *
-     * @param entry entry that has been added
-     */
-    public void onPendingEntryAdded(@NonNull Entry entry) {
-        String groupKey = mGroupManager.getGroupKey(entry.notification);
-        GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
-        if (groupAlertEntry != null) {
-            checkShouldTransferBack(groupAlertEntry);
+        // Called when the entry's reinflation has finished. If there is an alert pending, we
+        // then show the alert.
+        @Override
+        public void onEntryReinflated(Entry entry) {
+            PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
+            if (alertInfo != null) {
+                if (alertInfo.isStillValid()) {
+                    alertNotificationWhenPossible(entry, getActiveAlertManager());
+                } else {
+                    // The transfer is no longer valid. Free the content.
+                    entry.getRow().freeContentViewWhenSafe(
+                            alertInfo.mAlertManager.getContentFlag());
+                }
+            }
         }
-    }
+
+        @Override
+        public void onEntryRemoved(Entry entry) {
+            // Removes any alerts pending on this entry. Note that this will not stop any inflation
+            // tasks started by a transfer, so this should only be used as clean-up for when
+            // inflation is stopped and the pending alert no longer needs to happen.
+            mPendingAlerts.remove(entry.key);
+        }
+    };
 
     /**
      * Gets the number of new notifications pending inflation that will be added to the group
@@ -225,11 +226,11 @@
      * @return the number of new notifications that will be added to the group
      */
     private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
-        if (mPendingNotifications == null) {
+        if (mEntryManager == null) {
             return 0;
         }
         int number = 0;
-        Collection<Entry> values = mPendingNotifications.values();
+        Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
         for (Entry entry : values) {
             if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
                 number++;
@@ -245,10 +246,10 @@
      * @return true if a pending notification will add to this group
      */
     private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
-        if (mPendingNotifications == null) {
+        if (mEntryManager == null) {
             return false;
         }
-        Collection<Entry> values = mPendingNotifications.values();
+        Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
         for (Entry entry : values) {
             if (isPendingNotificationInGroup(entry, group)) {
                 return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1d6a1e8..75e5cba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -148,6 +148,7 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.doze.LockScreenWakeUpController;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -581,6 +582,7 @@
     protected NotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private boolean mPulsing;
+    private LockScreenWakeUpController mLockScreenWakeUpController;
 
     @Override
     public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -628,6 +630,8 @@
         mBubbleController = Dependency.get(BubbleController.class);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
+        mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
+
         mColorExtractor.addOnColorsChangedListener(this);
         mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR);
 
@@ -982,6 +986,7 @@
         for (int i = 0; i < pattern.length; i++) {
             mCameraLaunchGestureVibePattern[i] = pattern[i];
         }
+        mLockScreenWakeUpController = new LockScreenWakeUpController(mContext, mDozeServiceHost);
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -2225,6 +2230,11 @@
             mNavigationBar.getBarTransitions().setAutoDim(false);
         }
         mHandler.removeCallbacks(mAutoDim);
+
+        // Do not dim the navigation buttons if the its tint is controlled by the bar's background
+        if (NavBarTintController.isEnabled(mContext)) {
+            return;
+        }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             mHandler.postDelayed(mAutoDim, AUTOHIDE_TIMEOUT_MS);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index acdd5e9..261f117 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -67,6 +67,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
+import java.util.ArrayList;
+
 public class StatusBarNotificationPresenter implements NotificationPresenter,
         ConfigurationController.ConfigurationListener {
 
@@ -105,6 +107,7 @@
     private final int mMaxAllowedKeyguardNotifications;
     private final IStatusBarService mBarService;
     private boolean mReinflateNotificationsOnUserSwitched;
+    private boolean mDispatchUiModeChangeOnUserSwitched;
     private final UnlockMethodCache mUnlockMethodCache;
     private TextView mNotificationPanelDebugText;
 
@@ -187,6 +190,27 @@
     }
 
     @Override
+    public void onUiModeChanged() {
+        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+            updateNotificationOnUiModeChanged();
+        } else {
+            mDispatchUiModeChangeOnUserSwitched = true;
+        }
+    }
+
+    private void updateNotificationOnUiModeChanged() {
+        ArrayList<Entry> userNotifications
+                = mEntryManager.getNotificationData().getNotificationsForCurrentUser();
+        for (int i = 0; i < userNotifications.size(); i++) {
+            Entry entry = userNotifications.get(i);
+            ExpandableNotificationRow row = entry.getRow();
+            if (row != null) {
+                row.onUiModeChanged();
+            }
+        }
+    }
+
+    @Override
     public boolean isCollapsing() {
         return mNotificationPanel.isCollapsing()
                 || mActivityLaunchAnimator.isAnimationPending()
@@ -301,6 +325,10 @@
             mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
             mReinflateNotificationsOnUserSwitched = false;
         }
+        if (mDispatchUiModeChangeOnUserSwitched) {
+            updateNotificationOnUiModeChanged();
+            mDispatchUiModeChangeOnUserSwitched = false;
+        }
         updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         mShadeController.setLockscreenUser(newUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index 6ee6cb2..53d0228 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -28,7 +28,6 @@
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.WifiTracker;
 import com.android.settingslib.wifi.WifiTracker.WifiListener;
-import com.android.systemui.R;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -43,13 +42,7 @@
     // network credentials.  This is used by quick settings for secured networks.
     private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
 
-    private static final int[] ICONS = {
-        R.drawable.ic_qs_wifi_full_0,
-        R.drawable.ic_qs_wifi_full_1,
-        R.drawable.ic_qs_wifi_full_2,
-        R.drawable.ic_qs_wifi_full_3,
-        R.drawable.ic_qs_wifi_full_4,
-    };
+    private static final int[] ICONS = WifiIcons.WIFI_FULL_ICONS;
 
     private final Context mContext;
     private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
index 945ed76..0823db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
@@ -19,9 +19,17 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
-public interface DarkIconDispatcher {
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark
+ * intensity
+ */
+public interface DarkIconDispatcher extends Dumpable {
 
     void setIconsDarkArea(Rect r);
     LightBarTransitionsController getTransitionsController();
@@ -37,6 +45,11 @@
     // addDarkReceiver.
     void applyDark(DarkReceiver object);
 
+    /**
+     * Dumpable interface
+     */
+    default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
+
     int DEFAULT_ICON_TINT = Color.WHITE;
     Rect sTmpRect = new Rect();
     int[] sTmpInt2 = new int[2];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index e943261..3deede0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -22,6 +22,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationState;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -437,7 +438,13 @@
                 mCurrentState.level = mSignalStrength.getLevel();
             }
         }
-        if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
+
+        // When the device is camped on a 5G Non-Standalone network, the data network type is still
+        // LTE. In this case, we first check which 5G icon should be shown.
+        MobileIconGroup nr5GIconGroup = getNr5GIconGroup();
+        if (nr5GIconGroup != null) {
+            mCurrentState.iconGroup = nr5GIconGroup;
+        } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
             mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
         } else {
             mCurrentState.iconGroup = mDefaultIcons;
@@ -464,6 +471,36 @@
         notifyListenersIfNecessary();
     }
 
+    private MobileIconGroup getNr5GIconGroup() {
+        if (mServiceState == null) return null;
+
+        int nrStatus = mServiceState.getNrStatus();
+        if (nrStatus == NetworkRegistrationState.NR_STATUS_CONNECTED) {
+            // Check if the NR 5G is using millimeter wave and the icon is config.
+            if (mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE) {
+                if (mConfig.nr5GIconMap.containsKey(Config.NR_CONNECTED_MMWAVE)) {
+                    return mConfig.nr5GIconMap.get(Config.NR_CONNECTED_MMWAVE);
+                }
+            }
+
+            // If NR 5G is not using millimeter wave or there is no icon for millimeter wave, we
+            // check the normal 5G icon.
+            if (mConfig.nr5GIconMap.containsKey(Config.NR_CONNECTED)) {
+                return mConfig.nr5GIconMap.get(Config.NR_CONNECTED);
+            }
+        } else if (nrStatus == NetworkRegistrationState.NR_STATUS_NOT_RESTRICTED) {
+            if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED)) {
+                return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED);
+            }
+        } else if (nrStatus == NetworkRegistrationState.NR_STATUS_RESTRICTED) {
+            if (mConfig.nr5GIconMap.containsKey(Config.NR_RESTRICTED)) {
+                return mConfig.nr5GIconMap.get(Config.NR_RESTRICTED);
+            }
+        }
+
+        return null;
+    }
+
     private boolean isDataDisabled() {
         return !mPhone.getDataEnabled(mSubscriptionInfo.getSubscriptionId());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 70a3589..bc43120 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -60,15 +60,19 @@
 import com.android.systemui.R;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 /** Platform implementation of the network controller. **/
 public class NetworkControllerImpl extends BroadcastReceiver
@@ -1029,6 +1033,13 @@
 
     @VisibleForTesting
     static class Config {
+        static final int NR_CONNECTED_MMWAVE = 1;
+        static final int NR_CONNECTED = 2;
+        static final int NR_NOT_RESTRICTED = 3;
+        static final int NR_RESTRICTED = 4;
+
+        Map<Integer, MobileIconGroup> nr5GIconMap = new HashMap<>();
+
         boolean showAtLeast3G = false;
         boolean alwaysShowCdmaRssi = false;
         boolean show4gForLte = false;
@@ -1037,6 +1048,19 @@
         boolean inflateSignalStrengths = false;
         boolean alwaysShowDataRatIcon = false;
 
+        /**
+         * Mapping from NR 5G status string to an integer. The NR 5G status string should match
+         * those in carrier config.
+         */
+        private static final Map<String, Integer> NR_STATUS_STRING_TO_INDEX;
+        static {
+            NR_STATUS_STRING_TO_INDEX = new HashMap<>(4);
+            NR_STATUS_STRING_TO_INDEX.put("connected_mmwave", NR_CONNECTED_MMWAVE);
+            NR_STATUS_STRING_TO_INDEX.put("connected", NR_CONNECTED);
+            NR_STATUS_STRING_TO_INDEX.put("not_restricted", NR_NOT_RESTRICTED);
+            NR_STATUS_STRING_TO_INDEX.put("restricted", NR_RESTRICTED);
+        }
+
         static Config readConfig(Context context) {
             Config config = new Config();
             Resources res = context.getResources();
@@ -1061,8 +1085,46 @@
                         CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
                 config.hideLtePlus = b.getBoolean(
                         CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
+                String nr5GIconConfiguration =
+                        b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+                if (!TextUtils.isEmpty(nr5GIconConfiguration)) {
+                    String[] nr5GIconConfigPairs = nr5GIconConfiguration.trim().split(",");
+                    for (String pair : nr5GIconConfigPairs) {
+                        add5GIconMapping(pair, config);
+                    }
+                }
             }
+
             return config;
         }
+
+        /**
+         * Add a mapping from NR 5G status to the 5G icon. All the icon resources come from
+         * {@link TelephonyIcons}.
+         *
+         * @param keyValuePair the NR 5G status and icon name separated by a colon.
+         * @param config container that used to store the parsed configs.
+         */
+        @VisibleForTesting
+        static void add5GIconMapping(String keyValuePair, Config config) {
+            String[] kv = (keyValuePair.trim().toLowerCase()).split(":");
+
+            if (kv.length != 2) {
+                if (DEBUG) Log.e(TAG, "Invalid 5G icon configuration, config = " + keyValuePair);
+                return;
+            }
+
+            String key = kv[0], value = kv[1];
+
+            // There is no icon config for the specific 5G status.
+            if (value.equals("none")) return;
+
+            if (NR_STATUS_STRING_TO_INDEX.containsKey(key)
+                    && TelephonyIcons.ICON_NAME_TO_ICON.containsKey(value)) {
+                config.nr5GIconMap.put(
+                        NR_STATUS_STRING_TO_INDEX.get(key),
+                        TelephonyIcons.ICON_NAME_TO_ICON.get(value));
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index bd76820..7347f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -19,6 +19,9 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
 
+import java.util.HashMap;
+import java.util.Map;
+
 class TelephonyIcons {
     //***** Data connection icons
     static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
@@ -33,6 +36,8 @@
     static final int ICON_4G = R.drawable.ic_4g_mobiledata;
     static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
     static final int ICON_1X = R.drawable.ic_1x_mobiledata;
+    static final int ICON_5G = R.drawable.ic_5g_mobiledata;
+    static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
 
     static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
             "CARRIER_NETWORK_CHANGE",
@@ -199,6 +204,34 @@
             TelephonyIcons.ICON_LTE_PLUS,
             true);
 
+    static final MobileIconGroup NR_5G = new MobileIconGroup(
+            "5G",
+            null,
+            null,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+            0,
+            0,
+            0,
+            0,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+            R.string.data_connection_5g,
+            TelephonyIcons.ICON_5G,
+            true);
+
+    static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
+            "5G_PLUS",
+            null,
+            null,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+            0,
+            0,
+            0,
+            0,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+            R.string.data_connection_5g_plus,
+            TelephonyIcons.ICON_5G_PLUS,
+            true);
+
     static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
             "DataDisabled",
             null,
@@ -211,5 +244,27 @@
             R.string.cell_data_off_content_description,
             0,
             false);
+
+    /** Mapping icon name(lower case) to the icon object. */
+    static final Map<String, MobileIconGroup> ICON_NAME_TO_ICON;
+    static {
+        ICON_NAME_TO_ICON = new HashMap<>();
+        ICON_NAME_TO_ICON.put("carrier_network_change", CARRIER_NETWORK_CHANGE);
+        ICON_NAME_TO_ICON.put("3g", THREE_G);
+        ICON_NAME_TO_ICON.put("wfc", WFC);
+        ICON_NAME_TO_ICON.put("unknown", UNKNOWN);
+        ICON_NAME_TO_ICON.put("e", E);
+        ICON_NAME_TO_ICON.put("1x", ONE_X);
+        ICON_NAME_TO_ICON.put("g", G);
+        ICON_NAME_TO_ICON.put("h", H);
+        ICON_NAME_TO_ICON.put("h+", H_PLUS);
+        ICON_NAME_TO_ICON.put("4g", FOUR_G);
+        ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS);
+        ICON_NAME_TO_ICON.put("lte", LTE);
+        ICON_NAME_TO_ICON.put("lte+", LTE_PLUS);
+        ICON_NAME_TO_ICON.put("5g", NR_5G);
+        ICON_NAME_TO_ICON.put("5g_plus", NR_5G_PLUS);
+        ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
index 374408d..f629863 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
@@ -32,19 +32,24 @@
               R.drawable.stat_sys_wifi_signal_4_fully }
         };
 
+    static final int[] WIFI_FULL_ICONS = {
+            com.android.internal.R.drawable.ic_wifi_signal_0,
+            com.android.internal.R.drawable.ic_wifi_signal_1,
+            com.android.internal.R.drawable.ic_wifi_signal_2,
+            com.android.internal.R.drawable.ic_wifi_signal_3,
+            com.android.internal.R.drawable.ic_wifi_signal_4
+    };
+
     public static final int[][] QS_WIFI_SIGNAL_STRENGTH = {
             { R.drawable.ic_qs_wifi_0,
               R.drawable.ic_qs_wifi_1,
               R.drawable.ic_qs_wifi_2,
               R.drawable.ic_qs_wifi_3,
               R.drawable.ic_qs_wifi_4 },
-            { R.drawable.ic_qs_wifi_full_0,
-              R.drawable.ic_qs_wifi_full_1,
-              R.drawable.ic_qs_wifi_full_2,
-              R.drawable.ic_qs_wifi_full_3,
-              R.drawable.ic_qs_wifi_full_4 }
+            WIFI_FULL_ICONS
         };
 
+    public static final int QS_WIFI_DISABLED = com.android.internal.R.drawable.ic_wifi_signal_0;
     static final int QS_WIFI_NO_NETWORK = R.drawable.ic_qs_wifi_no_network;
     static final int WIFI_NO_NETWORK = R.drawable.stat_sys_wifi_signal_null;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
index 0dd8937..88cbbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
@@ -76,8 +76,9 @@
     }
 
     @Override
-    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs,
-            Handler handler, int maxReportLatencyUs, int reservedFlags) {
+    protected boolean registerListenerImpl(SensorEventListener listener,
+            Sensor sensor, int delayUs, Handler handler, int maxReportLatencyUs,
+            int reservedFlags) {
         mHandler.post(() -> {
             if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
                 Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
@@ -146,23 +147,28 @@
      * @param sensor
      * @param listener
      */
-    public void requestPluginTriggerSensor(SensorManagerPlugin.Sensor sensor,
-            SensorManagerPlugin.TriggerEventListener listener) {
+    public void registerPluginListener(SensorManagerPlugin.Sensor sensor,
+            SensorManagerPlugin.SensorEventListener listener) {
         if (mPlugins.isEmpty()) {
             Log.w(TAG, "No plugins registered");
         }
         mHandler.post(() -> {
             for (int i = 0; i < mPlugins.size(); i++) {
-                mPlugins.get(i).registerTriggerEvent(sensor, listener);
+                mPlugins.get(i).registerListener(sensor, listener);
             }
         });
     }
 
-    public void cancelPluginTriggerSensor(SensorManagerPlugin.Sensor sensor,
-            SensorManagerPlugin.TriggerEventListener listener) {
+    /**
+     * Unregisters all sensors that match the give type for all plugins.
+     * @param sensor
+     * @param listener
+     */
+    public void unregisterPluginListener(SensorManagerPlugin.Sensor sensor,
+            SensorManagerPlugin.SensorEventListener listener) {
         mHandler.post(() -> {
             for (int i = 0; i < mPlugins.size(); i++) {
-                mPlugins.get(i).unregisterTriggerEvent(sensor, listener);
+                mPlugins.get(i).unregisterListener(sensor, listener);
             }
         });
     }
@@ -185,7 +191,8 @@
     }
 
     @Override
-    protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+    protected void unregisterListenerImpl(SensorEventListener listener,
+            Sensor sensor) {
         mHandler.post(() -> {
             if (sensor == null) {
                 mInner.unregisterListener(listener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/LockScreenWakeUpControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/LockScreenWakeUpControllerTest.java
new file mode 100644
index 0000000..8963b59
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/LockScreenWakeUpControllerTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.android.systemui.doze;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.PowerManager;
+import android.support.test.filters.SmallTest;
+
+import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.util.AsyncSensorManager;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class LockScreenWakeUpControllerTest extends SysuiTestCase {
+
+    @Mock
+    private AsyncSensorManager mAsyncSensorManager;
+    @Mock
+    private SensorManagerPlugin.Sensor mSensor;
+    @Mock
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private DozeHost mDozeHost;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private Handler mHandler;
+
+    private LockScreenWakeUpController mLockScreenWakeUpController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mHandler).post(any());
+
+        mLockScreenWakeUpController = new LockScreenWakeUpController(mAsyncSensorManager, mSensor,
+                mAmbientDisplayConfiguration, mPowerManager, mDozeHost, mStatusBarStateController,
+                mHandler);
+    }
+
+    @Test
+    public void testOnStateChanged_registersUnregistersListener() {
+        when(mAmbientDisplayConfiguration.wakeLockScreenGestureEnabled(anyInt())).thenReturn(true);
+        mLockScreenWakeUpController.onStateChanged(StatusBarState.KEYGUARD);
+        mLockScreenWakeUpController.onStateChanged(StatusBarState.SHADE);
+
+        verify(mAsyncSensorManager, times(1)).registerPluginListener(eq(mSensor),
+                eq(mLockScreenWakeUpController));
+
+        mLockScreenWakeUpController.onStateChanged(StatusBarState.SHADE);
+        verify(mAsyncSensorManager).unregisterPluginListener(eq(mSensor),
+                eq(mLockScreenWakeUpController));
+    }
+
+    @Test
+    public void testOnStateChanged_disabledSensor() {
+        when(mAmbientDisplayConfiguration.wakeLockScreenGestureEnabled(anyInt()))
+                .thenReturn(false);
+        mLockScreenWakeUpController.onStateChanged(StatusBarState.KEYGUARD);
+        mLockScreenWakeUpController.onStateChanged(StatusBarState.SHADE);
+
+        verify(mAsyncSensorManager, never()).registerPluginListener(eq(mSensor),
+                eq(mLockScreenWakeUpController));
+    }
+
+    @Test
+    public void testOnSensorChanged_postsToMainThread() {
+        SensorManagerPlugin.SensorEvent event = new SensorManagerPlugin.SensorEvent(mSensor, 0);
+        mLockScreenWakeUpController.onSensorChanged(event);
+
+        verify(mHandler).post(any());
+    }
+
+    @Test
+    public void testOnSensorChanged_wakeUpWhenDozing() {
+        SensorManagerPlugin.SensorEvent event =
+                new SensorManagerPlugin.SensorEvent(mSensor, 0, new float[] {1});
+        mLockScreenWakeUpController.onSensorChanged(event);
+        verify(mPowerManager, never()).wakeUp(anyLong(), any());
+
+        mLockScreenWakeUpController.onDozingChanged(true);
+        mLockScreenWakeUpController.onSensorChanged(event);
+        verify(mPowerManager).wakeUp(anyLong(), any());
+    }
+
+    @Test
+    public void testOnSensorChanged_sleepsWhenAwake() {
+        boolean[] goToSleep = new boolean[] {false};
+        doAnswer(invocation -> goToSleep[0] = true)
+                .when(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
+        SensorManagerPlugin.SensorEvent event =
+                new SensorManagerPlugin.SensorEvent(mSensor, 0, new float[] {0});
+        mLockScreenWakeUpController.onDozingChanged(true);
+        mLockScreenWakeUpController.onSensorChanged(event);
+        Assert.assertFalse("goToSleep should have never been called.", goToSleep[0]);
+
+        mLockScreenWakeUpController.onDozingChanged(false);
+        mLockScreenWakeUpController.onSensorChanged(event);
+        Assert.assertTrue("goToSleep should have been called.", goToSleep[0]);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
new file mode 100644
index 0000000..90792e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.systemui.qs.tiles;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.SensorPrivacyManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SensorPrivacyTileTest extends SysuiTestCase {
+
+    @Mock
+    private KeyguardMonitor mKeyguard;
+    @Mock
+    private QSTileHost mHost;
+    @Mock
+    SensorPrivacyManager mSensorPrivacyManager;
+
+    private TestableLooper mTestableLooper;
+
+    private SensorPrivacyTile mSensorPrivacyTile;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTestableLooper = TestableLooper.get(this);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+        mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class);
+
+        mSensorPrivacyManager = mDependency.injectMockDependency(SensorPrivacyManager.class);
+
+        when(mHost.getContext()).thenReturn(mContext);
+
+        mSensorPrivacyTile = new SensorPrivacyTile(mHost);
+    }
+
+    @Test
+    public void testSensorPrivacyListenerAdded_handleListeningTrue() {
+        // To prevent access to privacy related features from apps with WRITE_SECURE_SETTINGS the
+        // sensor privacy state is not stored in Settings; to receive notification apps must add
+        // themselves as a listener with the SensorPrivacyManager. This test verifies when
+        // setListening is called with a value of true the tile adds itself as a listener.
+        mSensorPrivacyTile.handleSetListening(true);
+        mTestableLooper.processAllMessages();
+        verify(mSensorPrivacyManager).addSensorPrivacyListener(mSensorPrivacyTile);
+    }
+
+    @Test
+    public void testSensorPrivacyListenerRemoved_handleListeningFalse() {
+        // Similar to the test above verifies that the tile removes itself as a listener when
+        // setListening is called with a value of false.
+        mSensorPrivacyTile.handleSetListening(false);
+        mTestableLooper.processAllMessages();
+        verify(mSensorPrivacyManager).removeSensorPrivacyListener((mSensorPrivacyTile));
+    }
+
+    @Test
+    public void testSensorPrivacyEnabled_handleClick() {
+        // Verifies when the SensorPrivacy tile is clicked it invokes the SensorPrivacyManager to
+        // set sensor privacy.
+        mSensorPrivacyTile.getState().value = false;
+        mSensorPrivacyTile.handleClick();
+        mTestableLooper.processAllMessages();
+        verify(mSensorPrivacyManager).setSensorPrivacy(true);
+
+        mSensorPrivacyTile.getState().value = true;
+        mSensorPrivacyTile.handleClick();
+        mTestableLooper.processAllMessages();
+        verify(mSensorPrivacyManager).setSensorPrivacy(false);
+    }
+
+    @Test
+    public void testSensorPrivacyNotDisabled_keyguard() {
+        // Verifies when the device is locked that sensor privacy cannot be disabled
+        when(mKeyguard.isSecure()).thenReturn(true);
+        when(mKeyguard.isShowing()).thenReturn(true);
+        mSensorPrivacyTile.getState().value = true;
+        mSensorPrivacyTile.handleClick();
+        mTestableLooper.processAllMessages();
+        verify(mSensorPrivacyManager, never()).setSensorPrivacy(false);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
index 8e88ed0..def7513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
@@ -475,14 +475,14 @@
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
                         outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
                         outRanking.canShowBadge(), outRanking.getUserSentiment(), true,
-                        false, false, null, null);
+                        -1, false, null, null);
             } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
                 outRanking.populate(key, outRanking.getRank(),
                         outRanking.matchesInterruptionFilter(),
                         outRanking.getVisibilityOverride(), 255,
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
                         outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
-                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true, false,
+                        outRanking.canShowBadge(), outRanking.getUserSentiment(), true, -1,
                         false, null, null);
             } else {
                 outRanking.populate(key, outRanking.getRank(),
@@ -490,7 +490,7 @@
                         outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
                         outRanking.getImportance(), outRanking.getImportanceExplanation(),
                         outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null,
-                        outRanking.canShowBadge(), outRanking.getUserSentiment(), false, false,
+                        outRanking.canShowBadge(), outRanking.getUserSentiment(), false, -1,
                         false, null, null);
             }
             return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 8706e21..904e5b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -167,7 +167,7 @@
                     0,
                     NotificationManager.IMPORTANCE_DEFAULT,
                     null, null,
-                    null, null, null, true, sentiment, false, false, false, null, null);
+                    null, null, null, true, sentiment, false, -1, false, null, null);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
     }
@@ -185,7 +185,7 @@
                     NotificationManager.IMPORTANCE_DEFAULT,
                     null, null,
                     null, null, null, true,
-                    NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, false,
+                    NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
                     false, smartActions, null);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index c3bc511..ee39e10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -32,14 +32,19 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.AmbientPulseManager;
+import com.android.systemui.statusbar.notification.AlertTransferListener;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -49,13 +54,15 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
     private NotificationGroupManager mGroupManager;
     private AmbientPulseManager mAmbientPulseManager;
     private HeadsUpManager mHeadsUpManager;
+    @Mock private NotificationEntryManager mNotificationEntryManager;
+    @Captor private ArgumentCaptor<AlertTransferListener> mListenerCaptor;
+    private AlertTransferListener mAlertTransferListener;
     private final HashMap<String, Entry> mPendingEntries = new HashMap<>();
     private final NotificationGroupTestHelper mGroupTestHelper =
             new NotificationGroupTestHelper(mContext);
@@ -67,15 +74,19 @@
         mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager);
         mHeadsUpManager = new HeadsUpManager(mContext) {};
 
+        when(mNotificationEntryManager.getPendingNotificationsIterator())
+                .thenReturn(mPendingEntries.values());
+
         mGroupManager = new NotificationGroupManager();
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
         mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper();
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
-        mGroupAlertTransferHelper.setPendingEntries(mPendingEntries);
 
-        mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
+        mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
+        verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture());
+        mAlertTransferListener = mListenerCaptor.getValue();
         mHeadsUpManager.addListener(mGroupAlertTransferHelper);
         mAmbientPulseManager.addListener(mGroupAlertTransferHelper);
     }
@@ -110,7 +121,7 @@
 
         // Add second child notification so that summary is no longer suppressed.
         mPendingEntries.put(childEntry2.key, childEntry2);
-        mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+        mAlertTransferListener.onPendingEntryAdded(childEntry2);
         mGroupManager.onEntryAdded(childEntry2);
 
         // The alert state should transfer back to the summary as there is now more than one
@@ -137,7 +148,7 @@
 
         // Add second child notification so that summary is no longer suppressed.
         mPendingEntries.put(childEntry2.key, childEntry2);
-        mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+        mAlertTransferListener.onPendingEntryAdded(childEntry2);
         mGroupManager.onEntryAdded(childEntry2);
 
         // Dozing changed so no reason to re-alert summary.
@@ -175,7 +186,7 @@
 
         when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
             .thenReturn(true);
-        mGroupAlertTransferHelper.onInflationFinished(childEntry);
+        mAlertTransferListener.onEntryReinflated(childEntry);
 
         // Alert is immediately removed from summary, and we show child as its content is inflated.
         assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
@@ -199,13 +210,13 @@
 
         // Add second child notification so that summary is no longer suppressed.
         mPendingEntries.put(childEntry2.key, childEntry2);
-        mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+        mAlertTransferListener.onPendingEntryAdded(childEntry2);
         mGroupManager.onEntryAdded(childEntry2);
 
         // Child entry finishes its inflation.
         when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
             .thenReturn(true);
-        mGroupAlertTransferHelper.onInflationFinished(childEntry);
+        mAlertTransferListener.onEntryReinflated(childEntry);
 
         verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
             .getContentFlag());
@@ -225,7 +236,7 @@
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
 
-        mGroupAlertTransferHelper.cleanUpPendingAlertInfo(childEntry.key);
+        mAlertTransferListener.onEntryRemoved(childEntry);
 
         assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 35f0dba..fdbf090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -212,6 +212,11 @@
             NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
     }
 
+    public void setupDefaultNr5GIconConfiguration() {
+        NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig);
+        NetworkControllerImpl.Config.add5GIconMapping("connected:5g", mConfig);
+    }
+
     public void setConnectivityViaBroadcast(
         int networkType, boolean validated, boolean isConnected) {
         setConnectivityCommon(networkType, validated, isConnected);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index d42940a..2baea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,11 +1,14 @@
 package com.android.systemui.statusbar.policy;
 
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.net.NetworkCapabilities;
 import android.os.Looper;
+import android.telephony.NetworkRegistrationState;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -16,6 +19,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -141,6 +145,47 @@
     }
 
     @Test
+    public void testNr5GIcon_NrConnectedWithoutMMWave_show5GIcon() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationState.NR_STATUS_CONNECTED).when(ss).getNrStatus();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+    }
+
+    @Test
+    public void testNr5GIcon_NrConnectedWithMMWave_show5GPlusIcon() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationState.NR_STATUS_CONNECTED).when(ss).getNrStatus();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G_PLUS);
+    }
+
+    @Test
+    public void testNr5GIcon_NrRestricted_showLteIcon() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultSignal();
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationState.NR_STATUS_RESTRICTED).when(ss).getNrStatus();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        verifyDataIndicators(TelephonyIcons.ICON_LTE);
+    }
+
+    @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
         when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
@@ -222,5 +267,4 @@
                 true, DEFAULT_QS_SIGNAL_STRENGTH, dataIcon, false,
                 false);
     }
-
 }
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 2d30640..66d64b1 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6637,6 +6637,18 @@
     // OS: Q
     SETTINGS_WIFI_DPP_ENROLLEE = 1596;
 
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Financial Apps Sms Access
+    // CATEGORY: SETTINGS
+    // OS: Q
+    SETTINGS_FINANCIAL_APPS_SMS_ACCESS = 1597;
+
+    // OPEN: QS Sensor Privacy Mode tile shown
+    // ACTION: QS Sensor Privacy Mode tile tapped
+    // SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: QUICK_SETTINGS
+    // OS: Q
+    QS_SENSOR_PRIVACY = 1598;
+
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index a917ced..2cbab49 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -477,6 +477,7 @@
      * requires on-screen confirmation by the user.
      */
     public void adbBackup(
+            @UserIdInt int userId,
             ParcelFileDescriptor fd,
             boolean includeApks,
             boolean includeObbs,
@@ -487,6 +488,8 @@
             boolean doCompress,
             boolean doKeyValue,
             String[] packageNames) {
+        enforceCallingPermissionOnUserId(userId, "adbBackup");
+
         mUserBackupManagerService.adbBackup(
                 fd,
                 includeApks,
@@ -505,7 +508,9 @@
      * is synchronous and does not return to the caller until the restore has been completed. It
      * requires on-screen confirmation by the user.
      */
-    public void adbRestore(ParcelFileDescriptor fd) {
+    public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
+        enforceCallingPermissionOnUserId(userId, "setBackupEnabled");
+
         mUserBackupManagerService.adbRestore(fd);
     }
 
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index ed6ff9b..4acd5c4 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -389,14 +389,13 @@
         backupNowForUser(binderGetCallingUserId());
     }
 
-    @Override
-    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
-            boolean includeShared, boolean doWidgets, boolean allApps,
-            boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
-                    throws RemoteException {
+    public void adbBackup(@UserIdInt int userId, ParcelFileDescriptor fd,
+            boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+            boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue,
+            String[] packageNames) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+            svc.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
                     allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
         }
     }
@@ -410,10 +409,10 @@
     }
 
     @Override
-    public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
+    public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.adbRestore(fd);
+            svc.adbRestore(userId, fd);
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 5220a59..796ef40 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -37,6 +37,7 @@
 import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
@@ -2423,9 +2424,9 @@
      * return to the caller until the backup has been completed. It requires on-screen confirmation
      * by the user.
      */
-    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
-            boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
-            boolean compress, boolean doKeyValue, String[] pkgList) {
+    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks,
+            boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps,
+            boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 872fe42..10e713d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -33,6 +33,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.IContentCaptureManager;
 
@@ -165,7 +166,8 @@
         @Override
         public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken,
                 @NonNull ComponentName componentName, @NonNull String sessionId,
-                int flags, @NonNull IResultReceiver result) {
+                @Nullable ContentCaptureContext clientContext, int flags,
+                @NonNull IResultReceiver result) {
             Preconditions.checkNotNull(activityToken);
             Preconditions.checkNotNull(componentName);
             Preconditions.checkNotNull(sessionId);
@@ -180,7 +182,7 @@
             synchronized (mLock) {
                 final ContentCapturePerUserService service = getServiceForUserLocked(userId);
                 service.startSessionLocked(activityToken, componentName, taskId, displayId,
-                        sessionId, flags, mAllowInstantService, result);
+                        sessionId, clientContext, flags, mAllowInstantService, result);
             }
         }
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index aa171f4..8130912 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -37,8 +37,9 @@
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
-import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -59,7 +60,7 @@
     private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
 
     @GuardedBy("mLock")
-    private final ArrayMap<String, ContentCaptureSession> mSessions =
+    private final ArrayMap<String, ContentCaptureServerSession> mSessions =
             new ArrayMap<>();
 
     // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
@@ -113,10 +114,10 @@
     @GuardedBy("mLock")
     public void startSessionLocked(@NonNull IBinder activityToken,
             @NonNull ComponentName componentName, int taskId, int displayId,
-            @NonNull String sessionId, int flags, boolean bindInstantServiceAllowed,
-            @NonNull IResultReceiver resultReceiver) {
+            @NonNull String sessionId, @Nullable ContentCaptureContext clientContext,
+            int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver resultReceiver) {
         if (!isEnabledLocked()) {
-            sendToClient(resultReceiver, ContentCaptureManager.STATE_DISABLED);
+            sendToClient(resultReceiver, ContentCaptureSession.STATE_DISABLED);
             return;
         }
         final ComponentName serviceComponentName = getServiceComponentName();
@@ -130,7 +131,7 @@
             return;
         }
 
-        ContentCaptureSession session = mSessions.get(sessionId);
+        ContentCaptureServerSession session = mSessions.get(sessionId);
         if (session != null) {
             if (mMaster.debug) {
                 Slog.d(TAG, "startSession(): reusing session " + sessionId + " for "
@@ -139,20 +140,20 @@
             // TODO(b/111276913): check if local ids match and decide what to do if they don't
             // TODO(b/111276913): should we call session.notifySessionStartedLocked() again??
             // if not, move notifySessionStartedLocked() into session constructor
-            sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE);
+            sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE);
             return;
         }
 
-        session = new ContentCaptureSession(getContext(), mUserId, mLock, activityToken,
-                this, serviceComponentName, componentName, taskId, displayId, sessionId, flags,
-                bindInstantServiceAllowed, mMaster.verbose);
+        session = new ContentCaptureServerSession(getContext(), mUserId, mLock, activityToken,
+                this, serviceComponentName, componentName, taskId, displayId, sessionId,
+                clientContext, flags, bindInstantServiceAllowed, mMaster.verbose);
         if (mMaster.verbose) {
             Slog.v(TAG, "startSession(): new session for " + componentName + " and id "
                     + sessionId);
         }
         mSessions.put(sessionId, session);
         session.notifySessionStartedLocked();
-        sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE);
+        sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE);
     }
 
     // TODO(b/111276913): log metrics
@@ -163,7 +164,7 @@
             return;
         }
 
-        final ContentCaptureSession session = mSessions.get(sessionId);
+        final ContentCaptureServerSession session = mSessions.get(sessionId);
         if (session == null) {
             if (mMaster.debug) {
                 Slog.d(TAG, "finishSession(): no session with id" + sessionId);
@@ -194,7 +195,7 @@
         if (!isEnabledLocked()) {
             return;
         }
-        final ContentCaptureSession session = mSessions.get(sessionId);
+        final ContentCaptureServerSession session = mSessions.get(sessionId);
         if (session == null) {
             if (mMaster.verbose) {
                 Slog.v(TAG, "sendEvents(): no session for " + sessionId);
@@ -212,7 +213,7 @@
             @NonNull Bundle data) {
         final String id = getSessionId(activityToken);
         if (id != null) {
-            final ContentCaptureSession session = mSessions.get(id);
+            final ContentCaptureServerSession session = mSessions.get(id);
             final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
             final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
             final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
@@ -237,9 +238,9 @@
     }
 
     @GuardedBy("mLock")
-    private ContentCaptureSession getSession(@NonNull IBinder activityToken) {
+    private ContentCaptureServerSession getSession(@NonNull IBinder activityToken) {
         for (int i = 0; i < mSessions.size(); i++) {
-            final ContentCaptureSession session = mSessions.valueAt(i);
+            final ContentCaptureServerSession session = mSessions.valueAt(i);
             if (session.mActivityToken.equals(activityToken)) {
                 return session;
             }
@@ -262,7 +263,7 @@
     void destroySessionsLocked() {
         final int numSessions = mSessions.size();
         for (int i = 0; i < numSessions; i++) {
-            final ContentCaptureSession session = mSessions.valueAt(i);
+            final ContentCaptureServerSession session = mSessions.valueAt(i);
             session.destroyLocked(true);
         }
         mSessions.clear();
@@ -272,7 +273,7 @@
     void listSessionsLocked(ArrayList<String> output) {
         final int numSessions = mSessions.size();
         for (int i = 0; i < numSessions; i++) {
-            final ContentCaptureSession session = mSessions.valueAt(i);
+            final ContentCaptureServerSession session = mSessions.valueAt(i);
             output.add(session.toShortString());
         }
     }
@@ -288,7 +289,7 @@
             final String prefix2 = prefix + "  ";
             for (int i = 0; i < size; i++) {
                 pw.print(prefix); pw.print("session@"); pw.println(i);
-                final ContentCaptureSession session = mSessions.valueAt(i);
+                final ContentCaptureServerSession session = mSessions.valueAt(i);
                 session.dumpLocked(prefix2, pw);
             }
         }
@@ -300,7 +301,7 @@
     @GuardedBy("mLock")
     private String getSessionId(@NonNull IBinder activityToken) {
         for (int i = 0; i < mSessions.size(); i++) {
-            ContentCaptureSession session = mSessions.valueAt(i);
+            ContentCaptureServerSession session = mSessions.valueAt(i);
             if (session.isActivitySession(activityToken)) {
                 return mSessions.keyAt(i);
             }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
similarity index 82%
rename from services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
rename to services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index a4012d5..181a2da 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -16,15 +16,16 @@
 package com.android.server.contentcapture;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.IBinder;
 import android.service.contentcapture.ContentCaptureService;
-import android.service.contentcapture.InteractionContext;
-import android.service.contentcapture.InteractionSessionId;
 import android.service.contentcapture.SnapshotData;
 import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -33,21 +34,22 @@
 import java.io.PrintWriter;
 import java.util.List;
 
-final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
+final class ContentCaptureServerSession implements ContentCaptureServiceCallbacks {
 
-    private static final String TAG = "ContentCaptureSession";
+    private static final String TAG = ContentCaptureServerSession.class.getSimpleName();
 
     private final Object mLock;
     final IBinder mActivityToken;
     private final ContentCapturePerUserService mService;
     private final RemoteContentCaptureService mRemoteService;
-    private final InteractionContext mInterationContext;
+    private final ContentCaptureContext mContentCaptureContext;
     private final String mId;
 
-    ContentCaptureSession(@NonNull Context context, int userId, @NonNull Object lock,
+    ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock,
             @NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service,
             @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName,
-            int taskId, int displayId, @NonNull String sessionId, int flags,
+            int taskId, int displayId, @NonNull String sessionId,
+            @Nullable ContentCaptureContext clientContext, int flags,
             boolean bindInstantServiceAllowed, boolean verbose) {
         mLock = lock;
         mActivityToken = activityToken;
@@ -56,7 +58,8 @@
         mRemoteService = new RemoteContentCaptureService(context,
                 ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this,
                 bindInstantServiceAllowed, verbose);
-        mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags);
+        mContentCaptureContext = new ContentCaptureContext(clientContext, appComponentName, taskId,
+                displayId, flags);
     }
 
     /**
@@ -71,7 +74,7 @@
      */
     @GuardedBy("mLock")
     public void notifySessionStartedLocked() {
-        mRemoteService.onSessionLifecycleRequest(mInterationContext, mId);
+        mRemoteService.onSessionLifecycleRequest(mContentCaptureContext, mId);
     }
 
     /**
@@ -93,7 +96,7 @@
      * Cleans up the session and removes it from the service.
      *
      * @param notifyRemoteService whether it should trigger a {@link
-     * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)}
+     * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
      * request.
      */
     @GuardedBy("mLock")
@@ -109,7 +112,7 @@
      * Cleans up the session, but not removes it from the service.
      *
      * @param notifyRemoteService whether it should trigger a {@link
-     * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)}
+     * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
      * request.
      */
     @GuardedBy("mLock")
@@ -137,7 +140,7 @@
     @GuardedBy("mLock")
     public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
         pw.print(prefix); pw.print("id: ");  pw.print(mId); pw.println();
-        pw.print(prefix); pw.print("context: ");  mInterationContext.dump(pw); pw.println();
+        pw.print(prefix); pw.print("context: ");  mContentCaptureContext.dump(pw); pw.println();
         pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
         pw.print(prefix); pw.print("has autofill callback: ");
     }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 33b6c8d..b4edf7e 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -22,9 +22,9 @@
 import android.os.IBinder;
 import android.service.contentcapture.ContentCaptureEventsRequest;
 import android.service.contentcapture.IContentCaptureService;
-import android.service.contentcapture.InteractionContext;
 import android.service.contentcapture.SnapshotData;
 import android.text.format.DateUtils;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 
 import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService;
@@ -66,17 +66,17 @@
     }
 
     /**
-     * Called by {@link ContentCaptureSession} to generate a call to the
+     * Called by {@link ContentCaptureServerSession} to generate a call to the
      * {@link RemoteContentCaptureService} to indicate the session was created (when {@code context}
      * is not {@code null} or destroyed (when {@code context} is {@code null}).
      */
-    public void onSessionLifecycleRequest(@Nullable InteractionContext context,
+    public void onSessionLifecycleRequest(@Nullable ContentCaptureContext context,
             @NonNull String sessionId) {
         scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId));
     }
 
     /**
-     * Called by {@link ContentCaptureSession} to send a batch of events to the service.
+     * Called by {@link ContentCaptureServerSession} to send a batch of events to the service.
      */
     public void onContentCaptureEventsRequest(@NonNull String sessionId,
             @NonNull List<ContentCaptureEvent> events) {
@@ -85,7 +85,7 @@
     }
 
     /**
-     * Called by {@link ContentCaptureSession} to send snapshot data to the service.
+     * Called by {@link ContentCaptureServerSession} to send snapshot data to the service.
      */
     public void onActivitySnapshotRequest(@NonNull String sessionId,
             @NonNull SnapshotData snapshotData) {
@@ -94,8 +94,8 @@
 
     public interface ContentCaptureServiceCallbacks
             extends VultureCallback<RemoteContentCaptureService> {
-        // NOTE: so far we don't need to notify the callback implementation (an inner class on
-        // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
+        // NOTE: so far we don't need to notify the callback implementation
+        // (ContentCaptureServerSession) of the request results (success, timeouts, etc..), so this
         // callback interface is empty.
     }
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 08034f7..0fa996e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1123,8 +1123,6 @@
         rescheduleKernelAlarmsLocked();
         updateNextAlarmClockLocked();
 
-        // And send a TIME_TICK right now, since it is important to get the UI updated.
-        mHandler.post(() ->  getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL));
     }
 
     static final class InFlight {
@@ -1298,7 +1296,7 @@
         mInjector.init();
 
         synchronized (mLock) {
-            mHandler = new AlarmHandler(Looper.myLooper());
+            mHandler = new AlarmHandler();
             mConstants = new Constants(mHandler);
 
             mNextWakeup = mNextNonWakeup = 0;
@@ -3050,6 +3048,9 @@
                         mNonInteractiveTime = dur;
                     }
                 }
+                // And send a TIME_TICK right now, since it is important to get the UI updated.
+                mHandler.post(() ->
+                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL));
             } else {
                 mNonInteractiveStartTime = nowELAPSED;
             }
@@ -3838,7 +3839,8 @@
         mWakeLock.setWorkSource(null);
     }
 
-    private class AlarmHandler extends Handler {
+    @VisibleForTesting
+    class AlarmHandler extends Handler {
         public static final int ALARM_EVENT = 1;
         public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
         public static final int LISTENER_TIMEOUT = 3;
@@ -3847,8 +3849,8 @@
         public static final int APP_STANDBY_PAROLE_CHANGED = 6;
         public static final int REMOVE_FOR_STOPPED = 7;
 
-        AlarmHandler(Looper looper) {
-            super(looper);
+        AlarmHandler() {
+            super(Looper.myLooper());
         }
 
         public void postRemoveForStopped(int uid) {
@@ -3961,8 +3963,8 @@
 
             final WorkSource workSource = null; // Let system take blame for time tick events.
             setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
-                    0, null, mTimeTickTrigger, null, AlarmManager.FLAG_STANDALONE, workSource,
-                    null, Process.myUid(), "android");
+                    0, null, mTimeTickTrigger, "TIME_TICK", AlarmManager.FLAG_STANDALONE,
+                    workSource, null, Process.myUid(), "android");
 
             // Finally, remember when we set the tick alarm
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index eda9fe1..89194e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1968,6 +1968,7 @@
     void systemReady() {
         mProxyTracker.loadGlobalProxy();
         registerNetdEventCallback();
+        mTethering.systemReady();
 
         synchronized (this) {
             mSystemReady = true;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8b992eb..4e8ef54 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -22,6 +22,7 @@
 
 import static com.android.internal.util.Preconditions.checkState;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -189,6 +190,8 @@
     private LocationBlacklist mBlacklist;
     private GnssMeasurementsProvider mGnssMeasurementsProvider;
     private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+    private String mLocationControllerExtraPackage;
+    private boolean mLocationControllerExtraPackageEnabled;
     private IGpsGeofenceHardware mGpsGeofenceProxy;
 
     // --- fields below are protected by mLock ---
@@ -2717,6 +2720,39 @@
         return null;
     }
 
+    @Override
+    public void setLocationControllerExtraPackage(String packageName) {
+        mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+                Manifest.permission.LOCATION_HARDWARE + " permission required");
+        synchronized (mLock) {
+            mLocationControllerExtraPackage = packageName;
+        }
+    }
+
+    @Override
+    public String getLocationControllerExtraPackage() {
+        synchronized (mLock) {
+            return mLocationControllerExtraPackage;
+        }
+    }
+
+    @Override
+    public void setLocationControllerExtraPackageEnabled(boolean enabled) {
+        mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+                Manifest.permission.LOCATION_HARDWARE + " permission required");
+        synchronized (mLock) {
+            mLocationControllerExtraPackageEnabled = enabled;
+        }
+    }
+
+    @Override
+    public boolean isLocationControllerExtraPackageEnabled() {
+        synchronized (mLock) {
+            return mLocationControllerExtraPackageEnabled
+                    && (mLocationControllerExtraPackage != null);
+        }
+    }
+
     /**
      * Returns the current location enabled/disabled status for a user
      *
@@ -2763,7 +2799,7 @@
      */
     @Override
     public void setLocationEnabledForUser(boolean enabled, int userId) {
-        mContext.enforceCallingPermission(
+        mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.WRITE_SECURE_SETTINGS,
                 "Requires WRITE_SECURE_SETTINGS permission");
 
@@ -3492,6 +3528,11 @@
                 }
             }
 
+            if (mLocationControllerExtraPackage != null) {
+                pw.println(" Location controller extra package: " + mLocationControllerExtraPackage
+                        + " enabled: " + mLocationControllerExtraPackageEnabled);
+            }
+
             if (!mBackgroundThrottlePackageWhitelist.isEmpty()) {
                 pw.println("  Throttling Whitelisted Packages:");
                 for (String packageName : mBackgroundThrottlePackageWhitelist) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 4678fec..8869af4 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -164,8 +164,6 @@
 
     private static final int MAX_UID_RANGES_PER_COMMAND = 10;
 
-    private static final  String[] EMPTY_STRING_ARRAY = new String[0];
-
     /**
      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
@@ -959,8 +957,7 @@
     public String[] listInterfaces() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.interfaceGetList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.interfaceGetList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1252,8 +1249,7 @@
     public String[] listTetheredInterfaces() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.tetherInterfaceList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.tetherInterfaceList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1276,8 +1272,7 @@
     public String[] getDnsForwarders() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.tetherDnsList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.tetherDnsList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
new file mode 100644
index 0000000..1cbcbe5
--- /dev/null
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -0,0 +1,426 @@
+/*
+ * 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 com.android.server;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.hardware.ISensorPrivacyListener;
+import android.hardware.ISensorPrivacyManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.NoSuchElementException;
+
+/** @hide */
+public final class SensorPrivacyService extends SystemService {
+
+    private static final String TAG = "SensorPrivacyService";
+
+    private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
+    private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
+    private static final String XML_ATTRIBUTE_ENABLED = "enabled";
+
+    private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
+
+    public SensorPrivacyService(Context context) {
+        super(context);
+        mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl);
+    }
+
+    class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub {
+
+        private final SensorPrivacyHandler mHandler;
+        private final Context mContext;
+        private final Object mLock = new Object();
+        @GuardedBy("mLock")
+        private final AtomicFile mAtomicFile;
+        @GuardedBy("mLock")
+        private boolean mEnabled;
+
+        SensorPrivacyServiceImpl(Context context) {
+            mContext = context;
+            mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
+            File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
+                    SENSOR_PRIVACY_XML_FILE);
+            mAtomicFile = new AtomicFile(sensorPrivacyFile);
+            synchronized (mLock) {
+                mEnabled = readPersistedSensorPrivacyEnabledLocked();
+            }
+        }
+
+        /**
+         * Sets the sensor privacy to the provided state and notifies all listeners of the new
+         * state.
+         */
+        @Override
+        public void setSensorPrivacy(boolean enable) {
+            enforceSensorPrivacyPermission();
+            synchronized (mLock) {
+                mEnabled = enable;
+                FileOutputStream outputStream = null;
+                try {
+                    XmlSerializer serializer = new FastXmlSerializer();
+                    outputStream = mAtomicFile.startWrite();
+                    serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+                    serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(enable));
+                    serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+                    serializer.endDocument();
+                    mAtomicFile.finishWrite(outputStream);
+                } catch (IOException e) {
+                    Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
+                    mAtomicFile.failWrite(outputStream);
+                }
+            }
+            mHandler.onSensorPrivacyChanged(enable);
+        }
+
+        /**
+         * Enforces the caller contains the necessary permission to change the state of sensor
+         * privacy.
+         */
+        private void enforceSensorPrivacyPermission() {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
+                return;
+            }
+            throw new SecurityException(
+                    "Changing sensor privacy requires the following permission: "
+                            + android.Manifest.permission.MANAGE_SENSOR_PRIVACY);
+        }
+
+        /**
+         * Returns whether sensor privacy is enabled.
+         */
+        @Override
+        public boolean isSensorPrivacyEnabled() {
+            synchronized (mLock) {
+                return mEnabled;
+            }
+        }
+
+        /**
+         * Returns the state of sensor privacy from persistent storage.
+         */
+        private boolean readPersistedSensorPrivacyEnabledLocked() {
+            // if the file does not exist then sensor privacy has not yet been enabled on
+            // the device.
+            if (!mAtomicFile.exists()) {
+                return false;
+            }
+            boolean enabled;
+            try (FileInputStream inputStream = mAtomicFile.openRead()) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+                XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+                parser.next();
+                String tagName = parser.getName();
+                enabled = Boolean.valueOf(parser.getAttributeValue(null, XML_ATTRIBUTE_ENABLED));
+            } catch (IOException | XmlPullParserException e) {
+                Log.e(TAG, "Caught an exception reading the state from storage: ", e);
+                // Delete the file to prevent the same error on subsequent calls and assume sensor
+                // privacy is not enabled.
+                mAtomicFile.delete();
+                enabled = false;
+            }
+            return enabled;
+        }
+
+        /**
+         * Persists the state of sensor privacy.
+         */
+        private void persistSensorPrivacyState() {
+            synchronized (mLock) {
+                FileOutputStream outputStream = null;
+                try {
+                    XmlSerializer serializer = new FastXmlSerializer();
+                    outputStream = mAtomicFile.startWrite();
+                    serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+                    serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(mEnabled));
+                    serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+                    serializer.endDocument();
+                    mAtomicFile.finishWrite(outputStream);
+                } catch (IOException e) {
+                    Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
+                    mAtomicFile.failWrite(outputStream);
+                }
+            }
+        }
+
+        /**
+         * Registers a listener to be notified when the sensor privacy state changes.
+         */
+        @Override
+        public void addSensorPrivacyListener(ISensorPrivacyListener listener) {
+            if (listener == null) {
+                throw new NullPointerException("listener cannot be null");
+            }
+            mHandler.addListener(listener);
+        }
+
+        /**
+         * Unregisters a listener from sensor privacy state change notifications.
+         */
+        @Override
+        public void removeSensorPrivacyListener(ISensorPrivacyListener listener) {
+            if (listener == null) {
+                throw new NullPointerException("listener cannot be null");
+            }
+            mHandler.removeListener(listener);
+        }
+    }
+
+    /**
+     * Handles sensor privacy state changes and notifying listeners of the change.
+     */
+    private final class SensorPrivacyHandler extends Handler {
+        private static final int MESSAGE_SENSOR_PRIVACY_CHANGED = 1;
+
+        private final Object mListenerLock = new Object();
+
+        @GuardedBy("mListenerLock")
+        private final RemoteCallbackList<ISensorPrivacyListener> mListeners =
+                new RemoteCallbackList<>();
+        private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients;
+        private final Context mContext;
+
+        SensorPrivacyHandler(Looper looper, Context context) {
+            super(looper);
+            mDeathRecipients = new ArrayMap<>();
+            mContext = context;
+        }
+
+        public void onSensorPrivacyChanged(boolean enabled) {
+            sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
+                    this, enabled));
+            sendMessage(
+                    PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
+                            mSensorPrivacyServiceImpl));
+        }
+
+        public void addListener(ISensorPrivacyListener listener) {
+            synchronized (mListenerLock) {
+                DeathRecipient deathRecipient = new DeathRecipient(listener);
+                mDeathRecipients.put(listener, deathRecipient);
+                mListeners.register(listener);
+            }
+        }
+
+        public void removeListener(ISensorPrivacyListener listener) {
+            synchronized (mListenerLock) {
+                DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
+                if (deathRecipient != null) {
+                    deathRecipient.destroy();
+                }
+                mListeners.unregister(listener);
+            }
+        }
+
+        public void handleSensorPrivacyChanged(boolean enabled) {
+            final int count = mListeners.beginBroadcast();
+            for (int i = 0; i < count; i++) {
+                ISensorPrivacyListener listener = mListeners.getBroadcastItem(i);
+                try {
+                    listener.onSensorPrivacyChanged(enabled);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+                }
+            }
+            mListeners.finishBroadcast();
+            // Handle the state of all sensors managed by this service.
+            SensorState.handleSensorPrivacyToggled(mContext, enabled);
+        }
+    }
+
+    private final class DeathRecipient implements IBinder.DeathRecipient {
+
+        private ISensorPrivacyListener mListener;
+
+        DeathRecipient(ISensorPrivacyListener listener) {
+            mListener = listener;
+            try {
+                mListener.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener);
+        }
+
+        public void destroy() {
+            try {
+                mListener.asBinder().unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+            }
+        }
+    }
+
+    /**
+     * Maintains the state of the sensors when sensor privacy is enabled to return them to their
+     * original state when sensor privacy is disabled.
+     */
+    private static final class SensorState {
+
+        private static Object sLock = new Object();
+        @GuardedBy("sLock")
+        private static SensorState sPreviousState;
+
+        private boolean mAirplaneEnabled;
+        private boolean mLocationEnabled;
+
+        SensorState(boolean airplaneEnabled, boolean locationEnabled) {
+            mAirplaneEnabled = airplaneEnabled;
+            mLocationEnabled = locationEnabled;
+        }
+
+        public static void handleSensorPrivacyToggled(Context context, boolean enabled) {
+            synchronized (sLock) {
+                SensorState state;
+                if (enabled) {
+                    // if sensor privacy is being enabled then obtain the current state of the
+                    // sensors to be persisted and restored when sensor privacy is disabled.
+                    state = getCurrentSensorState(context);
+                } else {
+                    // else obtain the previous sensor state to be restored, first from the saved
+                    // state if available, otherwise attempt to read it from Settings.
+                    if (sPreviousState != null) {
+                        state = sPreviousState;
+                    } else {
+                        state = getPersistedSensorState(context);
+                    }
+                    // if the previous state is not available then return without attempting to
+                    // modify the sensor state.
+                    if (state == null) {
+                        return;
+                    }
+                }
+                // The SensorState represents the state of the sensor before sensor privacy was
+                // enabled; if airplane mode was not enabled then the state of airplane mode should
+                // be the same as the state of sensor privacy.
+                if (!state.mAirplaneEnabled) {
+                    setAirplaneMode(context, enabled);
+                }
+                // Similar to airplane mode the state of location should be the opposite of sensor
+                // privacy mode, if it was enabled when sensor privacy was enabled then it should be
+                // disabled. If location is disabled when sensor privacy is enabled then it will be
+                // left disabled when sensor privacy is disabled.
+                if (state.mLocationEnabled) {
+                    setLocationEnabled(context, !enabled);
+                }
+
+                // if sensor privacy is being enabled then persist the current state.
+                if (enabled) {
+                    sPreviousState = state;
+                    persistState(context, sPreviousState);
+                }
+            }
+        }
+
+        public static SensorState getCurrentSensorState(Context context) {
+            LocationManager locationManager = (LocationManager) context.getSystemService(
+                    Context.LOCATION_SERVICE);
+            boolean airplaneEnabled = Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+            boolean locationEnabled = locationManager.isLocationEnabled();
+            return new SensorState(airplaneEnabled, locationEnabled);
+        }
+
+        public static void persistState(Context context, SensorState state) {
+            StringBuilder stateValue = new StringBuilder();
+            stateValue.append(state.mAirplaneEnabled
+                    ? Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED
+                    : Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED);
+            stateValue.append(",");
+            stateValue.append(
+                    state.mLocationEnabled ? Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED
+                            : Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED);
+            Settings.Secure.putString(context.getContentResolver(),
+                    Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE, stateValue.toString());
+        }
+
+        public static SensorState getPersistedSensorState(Context context) {
+            String persistedState = Settings.Secure.getString(context.getContentResolver(),
+                    Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE);
+            if (persistedState == null) {
+                Log.e(TAG, "The persisted sensor state could not be obtained from Settings");
+                return null;
+            }
+            String[] sensorStates = persistedState.split(",");
+            if (sensorStates.length < 2) {
+                Log.e(TAG, "The persisted sensor state does not contain the expected values: "
+                        + persistedState);
+                return null;
+            }
+            boolean airplaneEnabled = sensorStates[0].equals(
+                    Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED);
+            boolean locationEnabled = sensorStates[1].equals(
+                    Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED);
+            return new SensorState(airplaneEnabled, locationEnabled);
+        }
+
+        private static void setAirplaneMode(Context context, boolean enable) {
+            ConnectivityManager connectivityManager =
+                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            connectivityManager.setAirplaneMode(enable);
+        }
+
+        private static void setLocationEnabled(Context context, boolean enable) {
+            LocationManager locationManager = (LocationManager) context.getSystemService(
+                    Context.LOCATION_SERVICE);
+            locationManager.setLocationEnabledForUser(enable,
+                    UserHandle.of(ActivityManager.getCurrentUser()));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5901ece..6c62725 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -19,9 +19,12 @@
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
@@ -32,9 +35,11 @@
 import static android.os.storage.OnObbStateChangeListener.MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
 
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
 import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -52,12 +57,14 @@
 import android.app.admin.SecurityLog;
 import android.app.usage.StorageStatsManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ProviderInfo;
@@ -129,6 +136,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.HexDump;
@@ -211,7 +219,9 @@
 
         @Override
         public void onBootPhase(int phase) {
-            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+                mStorageManagerService.servicesReady();
+            } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                 mStorageManagerService.systemReady();
             } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
                 mStorageManagerService.bootCompleted();
@@ -268,6 +278,7 @@
     private static final String TAG_VOLUMES = "volumes";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid";
+    private static final String ATTR_ISOLATED_STORAGE = "isolatedStorage";
     private static final String TAG_VOLUME = "volume";
     private static final String ATTR_TYPE = "type";
     private static final String ATTR_FS_UUID = "fsUuid";
@@ -318,6 +329,10 @@
     @GuardedBy("mLock")
     private String mPrimaryStorageUuid;
 
+    /** Flag indicating isolated storage state of last boot */
+    @GuardedBy("mLock")
+    private boolean mLastIsolatedStorage = false;
+
     /** Map from disk ID to latches */
     @GuardedBy("mLock")
     private ArrayMap<String, CountDownLatch> mDiskScanLatches = new ArrayMap<>();
@@ -763,6 +778,18 @@
                 }
             });
         refreshZramSettings();
+
+        // Toggle isolated-enable system property in response to settings
+        mContext.getContentResolver().registerContentObserver(
+            Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE),
+            false /*notifyForDescendants*/,
+            new ContentObserver(null /* current thread */) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    refreshIsolatedStorageSettings();
+                }
+            });
+        refreshIsolatedStorageSettings();
     }
 
     /**
@@ -788,6 +815,32 @@
         }
     }
 
+    private void refreshIsolatedStorageSettings() {
+        final int local = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
+        final int remote = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.ISOLATED_STORAGE_REMOTE, 0);
+
+        // Walk down precedence chain; we prefer local settings first, then
+        // remote settings, before finally falling back to hard-coded default.
+        final boolean res;
+        if (local == -1) {
+            res = false;
+        } else if (local == 1) {
+            res = true;
+        } else if (remote == -1) {
+            res = false;
+        } else if (remote == 1) {
+            res = true;
+        } else {
+            res = false;
+        }
+
+        Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag "
+                + remote + " resolved to " + res);
+        SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res));
+    }
+
     /**
      * MediaProvider has a ton of code that makes assumptions about storage
      * paths never changing, so we outright kill them to pick up new state.
@@ -1575,6 +1628,74 @@
         }
     }
 
+    private void servicesReady() {
+        synchronized (mLock) {
+            final boolean thisIsolatedStorage = StorageManager.hasIsolatedStorage();
+            if (mLastIsolatedStorage == thisIsolatedStorage) {
+                // Nothing changed since last boot; keep rolling forward
+                return;
+            } else if (thisIsolatedStorage) {
+                // This boot enables isolated storage; apply legacy behavior
+                applyLegacyStorage();
+            }
+
+            // Always remember the new state we just booted with
+            writeSettingsLocked();
+        }
+    }
+
+    /**
+     * If we're enabling isolated storage, we need to remember which existing
+     * apps have already been using shared storage, and grant them legacy access
+     * to keep them running smoothly.
+     */
+    private void applyLegacyStorage() {
+        final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+        final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+        for (int userId : um.getUserIds()) {
+            final PackageManager pm;
+            try {
+                pm = mContext.createPackageContextAsUser(mContext.getPackageName(),
+                        0, UserHandle.of(userId)).getPackageManager();
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+
+            final List<PackageInfo> pkgs = pm.getPackagesHoldingPermissions(new String[] {
+                    android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+            }, MATCH_UNINSTALLED_PACKAGES | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
+            for (PackageInfo pkg : pkgs) {
+                final int uid = pkg.applicationInfo.uid;
+                final String packageName = pkg.applicationInfo.packageName;
+
+                final long lastAccess = getLastAccessTime(appOps, uid, packageName, new int[] {
+                        AppOpsManager.OP_READ_EXTERNAL_STORAGE,
+                        AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+                });
+
+                Log.d(TAG, "Found " + uid + " " + packageName
+                        + " with granted storage access, last accessed " + lastAccess);
+                if (lastAccess > 0) {
+                    appOps.setMode(AppOpsManager.OP_LEGACY_STORAGE,
+                            uid, packageName, AppOpsManager.MODE_ALLOWED);
+                }
+            }
+        }
+    }
+
+    private static long getLastAccessTime(AppOpsManager manager,
+            int uid, String packageName, int[] ops) {
+        long maxTime = 0;
+        final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
+        for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) {
+            for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) {
+                maxTime = Math.max(maxTime, op.getLastAccessTime());
+            }
+        }
+        return maxTime;
+    }
+
     private void systemReady() {
         LocalServices.getService(ActivityTaskManagerInternal.class)
                 .registerScreenObserver(this);
@@ -1603,6 +1724,7 @@
     private void readSettingsLocked() {
         mRecords.clear();
         mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+        mLastIsolatedStorage = false;
 
         FileInputStream fis = null;
         try {
@@ -1624,6 +1746,8 @@
                             mPrimaryStorageUuid = readStringAttribute(in,
                                     ATTR_PRIMARY_STORAGE_UUID);
                         }
+                        mLastIsolatedStorage = readBooleanAttribute(in,
+                                ATTR_ISOLATED_STORAGE, false);
 
                     } else if (TAG_VOLUME.equals(tag)) {
                         final VolumeRecord rec = readVolumeRecord(in);
@@ -1654,6 +1778,7 @@
             out.startTag(null, TAG_VOLUMES);
             writeIntAttribute(out, ATTR_VERSION, VERSION_FIX_PRIMARY);
             writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid);
+            writeBooleanAttribute(out, ATTR_ISOLATED_STORAGE, StorageManager.hasIsolatedStorage());
             final int size = mRecords.size();
             for (int i = 0; i < size; i++) {
                 final VolumeRecord rec = mRecords.valueAt(i);
@@ -2122,18 +2247,22 @@
             }
         }
 
-        if ((mask & StorageManager.DEBUG_ISOLATED_STORAGE) != 0) {
-            final boolean enabled = (flags & StorageManager.DEBUG_ISOLATED_STORAGE) != 0;
+        if ((mask & (StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
+                | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF)) != 0) {
+            final int value;
+            if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON) != 0) {
+                value = 1;
+            } else if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF) != 0) {
+                value = -1;
+            } else {
+                value = 0;
+            }
 
             final long token = Binder.clearCallingIdentity();
             try {
-                SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE,
-                        Boolean.toString(enabled));
-
-                // Some of the storage related permissions get fiddled with during
-                // package scanning. So, delete the package cache to force PackageManagerService
-                // to do package scanning.
-                FileUtils.deleteContents(Environment.getPackageCacheDirectory());
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.ISOLATED_STORAGE_LOCAL, value);
+                refreshIsolatedStorageSettings();
 
                 // Perform hard reboot to kick policy into place
                 mContext.getSystemService(PowerManager.class).reboot(null);
@@ -2789,11 +2918,7 @@
         Slog.v(TAG, "mountProxyFileDescriptor");
 
         // We only support a narrow set of incoming mode flags
-        if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
-            mode = MODE_READ_WRITE;
-        } else {
-            mode = MODE_READ_ONLY;
-        }
+        mode &= MODE_READ_WRITE;
 
         try {
             synchronized (mAppFuseLock) {
@@ -2920,7 +3045,7 @@
         }
 
         if (!foundPrimary) {
-            Log.w(TAG, "No primary storage defined yet; hacking together a stub");
+            Slog.w(TAG, "No primary storage defined yet; hacking together a stub");
 
             final boolean primaryPhysical = SystemProperties.getBoolean(
                     StorageManager.PROP_PRIMARY_PHYSICAL, false);
@@ -3505,6 +3630,10 @@
             if (mIPackageManager.checkUidPermission(WRITE_MEDIA_STORAGE, uid)
                     == PERMISSION_GRANTED) {
                 return Zygote.MOUNT_EXTERNAL_FULL;
+            } else if (mIAppOpsService.checkOperation(OP_LEGACY_STORAGE, uid,
+                    packageName) == MODE_ALLOWED) {
+                // TODO: define a specific "legacy" mount mode
+                return Zygote.MOUNT_EXTERNAL_FULL;
             } else if (mIPackageManager.checkUidPermission(INSTALL_PACKAGES, uid)
                     == PERMISSION_GRANTED || mIAppOpsService.checkOperation(
                             OP_REQUEST_INSTALL_PACKAGES, uid, packageName) == MODE_ALLOWED) {
@@ -3672,6 +3801,8 @@
 
             pw.println();
             pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
+
+            pw.println();
             final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize();
             if (pair == null) {
                 pw.println("Internal storage total size: N/A");
@@ -3684,8 +3815,18 @@
                 pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
                 pw.println(" MiB)");
             }
+
+            pw.println();
             pw.println("Local unlocked users: " + Arrays.toString(mLocalUnlockedUsers));
             pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
+
+            final ContentResolver cr = mContext.getContentResolver();
+            pw.println();
+            pw.println("Isolated storage, local feature flag: "
+                    + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_LOCAL, 0));
+            pw.println("Isolated storage, remote feature flag: "
+                    + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0));
+            pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage());
         }
 
         synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b04ae17..1ef3e94 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1684,7 +1684,9 @@
         }
 
         synchronized (mRecords) {
-            mEmergencyNumberList = TelephonyManager.getDefault().getCurrentEmergencyNumberList();
+            TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            mEmergencyNumberList = tm.getCurrentEmergencyNumberList();
 
             for (Record r : mRecords) {
                 if (r.matchPhoneStateListenerEvent(
@@ -1996,6 +1998,13 @@
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
         }
 
+        if ((events & PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE) != 0) {
+            // It can have either READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE.
+            TelephonyPermissions.checkReadPhoneState(mContext,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
+                    Binder.getCallingUid(), callingPackage, "listen to "
+                            + "LISTEN_PREFERRED_DATA_SUBID_CHANGE");
+        }
 
         return true;
     }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f7acf7e..5afb90d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1964,7 +1964,8 @@
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
         userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
-                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", null);
+                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+                callingPackage);
 
         ServiceMap smap = getServiceMapLocked(userId);
         final ComponentName comp;
@@ -2019,6 +2020,13 @@
                 ComponentName className = new ComponentName(
                         sInfo.applicationInfo.packageName, sInfo.name);
                 ComponentName name = comp != null ? comp : className;
+                if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
+                        name.getPackageName(), sInfo.applicationInfo.uid)) {
+                    String msg = "association not allowed between packages "
+                            + callingPackage + " and " + r.packageName;
+                    Slog.w(TAG, "Service lookup failed: " + msg);
+                    return new ServiceLookupResult(null, msg);
+                }
                 if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
                     if (isBindExternal) {
                         if (!sInfo.exported) {
@@ -2099,6 +2107,17 @@
             }
         }
         if (r != null) {
+            if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, r.packageName,
+                    r.appInfo.uid)) {
+                String msg = "association not allowed between packages "
+                        + callingPackage + " and " + r.packageName;
+                Slog.w(TAG, "Service lookup failed: " + msg);
+                return new ServiceLookupResult(null, msg);
+            }
+            if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
+                    resolvedType, r.appInfo)) {
+                return new ServiceLookupResult(null, "blocked by firewall");
+            }
             if (mAm.checkComponentPermission(r.permission,
                     callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) {
                 if (!r.exported) {
@@ -2125,11 +2144,6 @@
                     return null;
                 }
             }
-
-            if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
-                    resolvedType, r.appInfo)) {
-                return null;
-            }
             return new ServiceLookupResult(r, null);
         }
         return null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8571ae6..1c04a94 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -69,6 +69,7 @@
     static final String KEY_PROCESS_START_ASYNC = "process_start_async";
     static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
     static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
+    static final String KEY_USE_COMPACTION = "use_compaction";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -99,6 +100,7 @@
     private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
     private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
     private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
+    private static final boolean DEFAULT_USE_COMPACTION = false;
 
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -218,6 +220,9 @@
     // this long.
     public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
 
+    // Use compaction for background apps.
+    public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
+
     // Indicates whether the activity starts logging is enabled.
     // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
     volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -375,6 +380,7 @@
                     DEFAULT_MEMORY_INFO_THROTTLE_TIME);
             TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
                     DEFAULT_TOP_TO_FGS_GRACE_DURATION);
+            USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
 
             updateMaxCachedProcesses();
         }
@@ -465,6 +471,8 @@
         pw.println(MEMORY_INFO_THROTTLE_TIME);
         pw.print("  "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
         pw.println(TOP_TO_FGS_GRACE_DURATION);
+        pw.print("  "); pw.print(KEY_USE_COMPACTION); pw.print("=");
+        pw.println(USE_COMPACTION);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8ce37a5..739dbbc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -658,6 +658,12 @@
     ArraySet<String> mBackgroundLaunchBroadcasts;
 
     /**
+     * When an app has restrictions on the other apps that can have associations with it,
+     * it appears here with a set of the allowed apps.
+     */
+    ArrayMap<String, ArraySet<String>> mAllowedAssociations;
+
+    /**
      * All of the processes we currently have running organized by pid.
      * The keys are the pid running the application.
      *
@@ -789,6 +795,11 @@
      */
     final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>();
 
+    /**
+     * Processes to compact.
+     */
+    final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
+
     private boolean mBinderTransactionTrackingEnabled = false;
 
     /**
@@ -1446,6 +1457,7 @@
     final Handler mUiHandler;
     final ServiceThread mProcStartHandlerThread;
     final Handler mProcStartHandler;
+    final ServiceThread mCompactionThread;
 
     final ActivityManagerConstants mConstants;
 
@@ -1783,6 +1795,11 @@
         }
     };
 
+    static final int COMPACT_PROCESS_SOME = 1;
+    static final int COMPACT_PROCESS_FULL = 2;
+    static final int COMPACT_PROCESS_MSG = 1;
+    final Handler mCompactionHandler;
+
     static final int COLLECT_PSS_BG_MSG = 1;
 
     final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
@@ -2218,6 +2235,8 @@
                 ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null;
         mProcStartHandlerThread = null;
         mProcStartHandler = null;
+        mCompactionThread = null;
+        mCompactionHandler = null;
         mHiddenApiBlacklist = null;
         mFactoryTest = FACTORY_TEST_OFF;
     }
@@ -2246,6 +2265,88 @@
         mProcStartHandlerThread.start();
         mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());
 
+        mCompactionThread = new ServiceThread("CompactionThread",
+                THREAD_PRIORITY_FOREGROUND, true);
+        mCompactionThread.start();
+        mCompactionHandler = new Handler(mCompactionThread.getLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case COMPACT_PROCESS_MSG: {
+                long start = SystemClock.uptimeMillis();
+                ProcessRecord proc;
+                int pid;
+                String action;
+                final String name;
+                int pendingAction, lastCompactAction;
+                long lastCompactTime;
+                synchronized(ActivityManagerService.this) {
+                    proc = mPendingCompactionProcesses.remove(0);
+
+                    // don't compact if the process has returned to perceptible
+                    if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        return;
+                    }
+
+                    pid = proc.pid;
+                    name = proc.processName;
+                    pendingAction = proc.reqCompactAction;
+                    lastCompactAction = proc.lastCompactAction;
+                    lastCompactTime = proc.lastCompactTime;
+                }
+                if (pid == 0) {
+                    // not a real process, either one being launched or one being killed
+                    return;
+                }
+
+                // basic throttling
+                if (pendingAction == COMPACT_PROCESS_SOME) {
+                    // if we're compacting some, then compact if >10s after last full
+                    // or >5s after last some
+                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 5000)) ||
+                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000)))
+                        return;
+                } else {
+                    // if we're compacting full, then compact if >10s after last full
+                    // or >.5s after last some
+                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 500)) ||
+                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000)))
+                        return;
+                }
+
+                try {
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " +
+                                     ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") +
+                                     ": " + name);
+                    long[] rssBefore = Process.getRss(pid);
+                    FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+                    if (pendingAction == COMPACT_PROCESS_SOME) {
+                        action = "file";
+                    } else {
+                        action = "all";
+                    }
+                    fos.write(action.getBytes());
+                    fos.close();
+                    long[] rssAfter = Process.getRss(pid);
+                    long end = SystemClock.uptimeMillis();
+                    EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+                            rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+                            rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], end-start);
+                    synchronized(ActivityManagerService.this) {
+                        proc.lastCompactTime = end;
+                        proc.lastCompactAction = pendingAction;
+                    }
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                } catch (Exception e) {
+                    // nothing to do, presumably the process died
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                }
+            }
+            }
+        }
+        };
+
+
         mConstants = new ActivityManagerConstants(this, mHandler);
 
         mProcessList.init(this);
@@ -2339,13 +2440,15 @@
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
 
-        // bind background thread to little cores
+        // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
         updateOomAdjLocked();
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
-                    Process.THREAD_GROUP_BG_NONINTERACTIVE);
+                    Process.THREAD_GROUP_SYSTEM);
+            Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+                    Process.THREAD_GROUP_SYSTEM);
         } catch (Exception e) {
             Slog.w(TAG, "Setting background thread cpuset failed");
         }
@@ -2396,6 +2499,34 @@
         return mBackgroundLaunchBroadcasts;
     }
 
+    boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
+        if (mAllowedAssociations == null) {
+            mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations();
+        }
+        // Interactions with the system uid are always allowed, since that is the core system
+        // that everyone needs to be able to interact with.
+        if (UserHandle.getAppId(uid1) == SYSTEM_UID) {
+            return true;
+        }
+        if (UserHandle.getAppId(uid2) == SYSTEM_UID) {
+            return true;
+        }
+        // We won't allow this association if either pkg1 or pkg2 has a limit on the
+        // associations that are allowed with it, and the other package is not explicitly
+        // specified as one of those associations.
+        ArraySet<String> pkgs = mAllowedAssociations.get(pkg1);
+        if (pkgs != null) {
+            if (!pkgs.contains(pkg2)) {
+                return false;
+            }
+        }
+        pkgs = mAllowedAssociations.get(pkg2);
+        if (pkgs != null) {
+            return pkgs.contains(pkg1);
+        }
+        return true;
+    }
+
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -6264,6 +6395,21 @@
         return state != 'Z' && state != 'X' && state != 'x' && state != 'K';
     }
 
+    private String checkContentProviderAssociation(ProcessRecord callingApp, int callingUid,
+            ProviderInfo cpi) {
+        if (callingApp == null) {
+            return validateAssociationAllowedLocked(cpi.packageName, cpi.applicationInfo.uid,
+                    null, callingUid) ? null : "<null>";
+        }
+        for (int i = callingApp.pkgList.size() - 1; i >= 0; i--) {
+            if (!validateAssociationAllowedLocked(callingApp.pkgList.keyAt(i), callingApp.uid,
+                    cpi.packageName, cpi.applicationInfo.uid)) {
+                return cpi.packageName;
+            }
+        }
+        return null;
+    }
+
     private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
             String name, IBinder token, int callingUid, String callingTag, boolean stable,
             int userId) {
@@ -6335,6 +6481,11 @@
                 String msg;
 
                 if (r != null && cpr.canRunHere(r)) {
+                    if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+                        throw new SecurityException("Content provider lookup "
+                                + cpr.name.flattenToShortString()
+                                + " failed: association not allowed with package " + msg);
+                    }
                     checkTime(startTime,
                             "getContentProviderImpl: before checkContentProviderPermission");
                     if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
@@ -6364,6 +6515,11 @@
                 } catch (RemoteException e) {
                 }
 
+                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+                    throw new SecurityException("Content provider lookup "
+                            + cpr.name.flattenToShortString()
+                            + " failed: association not allowed with package " + msg);
+                }
                 checkTime(startTime,
                         "getContentProviderImpl: before checkContentProviderPermission");
                 if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
@@ -6461,6 +6617,11 @@
                 checkTime(startTime, "getContentProviderImpl: got app info for user");
 
                 String msg;
+                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
+                    throw new SecurityException("Content provider lookup "
+                            + cpr.name.flattenToShortString()
+                            + " failed: association not allowed with package " + msg);
+                }
                 checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                 if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
                         != null) {
@@ -9207,6 +9368,12 @@
                 pw.println("-------------------------------------------------------------------------------");
 
             }
+            dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+
+            }
             mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
@@ -9474,6 +9641,14 @@
                     System.gc();
                     pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
                 }
+            } else if ("allowed-associations".equals(cmd)) {
+                if (opti < args.length) {
+                    dumpPackage = args[opti];
+                    opti++;
+                }
+                synchronized (this) {
+                    dumpAllowedAssociationsLocked(fd, pw, args, opti, true, dumpPackage);
+                }
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
@@ -10824,6 +10999,44 @@
         proto.end(handlerToken);
     }
 
+    void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean needSep = false;
+        boolean printedAnything = false;
+
+        pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+        boolean printed = false;
+        if (mAllowedAssociations != null) {
+            for (int i = 0; i < mAllowedAssociations.size(); i++) {
+                final String pkg = mAllowedAssociations.keyAt(i);
+                final ArraySet<String> asc = mAllowedAssociations.valueAt(i);
+                boolean printedHeader = false;
+                for (int j = 0; j < asc.size(); j++) {
+                    if (dumpPackage == null || pkg.equals(dumpPackage)
+                            || asc.valueAt(j).equals(dumpPackage)) {
+                        if (!printed) {
+                            pw.println("  Allowed associations (by restricted package):");
+                            printed = true;
+                            needSep = true;
+                            printedAnything = true;
+                        }
+                        if (!printedHeader) {
+                            pw.print("  * ");
+                            pw.print(pkg);
+                            pw.println(":");
+                            printedHeader = true;
+                        }
+                        pw.print("      Allow: ");
+                        pw.println(asc.valueAt(j));
+                    }
+                }
+            }
+        }
+        if (!printed) {
+            pw.println("  (No association restrictions)");
+        }
+    }
+
     void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -16805,6 +17018,24 @@
         int changes = 0;
 
         if (app.curAdj != app.setAdj) {
+            // don't compact during bootup
+            if (mConstants.USE_COMPACTION && mBooted) {
+                // Perform a minor compaction when a perceptible app becomes the prev/home app
+                // Perform a major compaction when any app enters cached
+                // reminder: here, setAdj is previous state, curAdj is upcoming state
+                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
+                    (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
+                     app.curAdj == ProcessList.HOME_APP_ADJ)) {
+                    app.reqCompactAction = COMPACT_PROCESS_SOME;
+                    mPendingCompactionProcesses.add(app);
+                    mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG);
+                } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
+                           app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                    app.reqCompactAction = COMPACT_PROCESS_FULL;
+                    mPendingCompactionProcesses.add(app);
+                    mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG);
+                }
+            }
             ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
                 String msg = "Set " + app.pid + " " + app.processName + " adj "
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 67a4d14..740c0da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
+import static android.app.WaitResult.launchStateToString;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
@@ -491,6 +492,7 @@
             final long endTime = SystemClock.uptimeMillis();
             PrintWriter out = mWaitOption ? pw : getErrPrintWriter();
             boolean launched = false;
+            boolean hotLaunch = false;
             switch (res) {
                 case ActivityManager.START_SUCCESS:
                     launched = true;
@@ -516,6 +518,8 @@
                     break;
                 case ActivityManager.START_TASK_TO_FRONT:
                     launched = true;
+                    //TODO(b/120981435) remove special case
+                    hotLaunch = true;
                     out.println(
                             "Warning: Activity not started, its current "
                                     + "task has been brought to the front");
@@ -563,6 +567,9 @@
                     result.who = intent.getComponent();
                 }
                 pw.println("Status: " + (result.timeout ? "timeout" : "ok"));
+                final @WaitResult.LaunchState int launchState =
+                        hotLaunch ? WaitResult.LAUNCH_STATE_HOT : result.launchState;
+                pw.println("LaunchState: " + launchStateToString(launchState));
                 if (result.who != null) {
                     pw.println("Activity: " + result.who.flattenToShortString());
                 }
@@ -2852,6 +2859,7 @@
             pw.println("    prov[iders] [COMP_SPEC ...]: content provider state");
             pw.println("    provider [COMP_SPEC]: provider client-side state");
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
+            pw.println("    allowed-associations: current package association restrictions");
             pw.println("    as[sociations]: tracked app associations");
             pw.println("    lmk: stats on low memory killer");
             pw.println("    lru: raw LRU process list");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index dd3f3b5..1c1daff 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -27,7 +28,6 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.AppOpsManager;
 import android.app.ApplicationErrorReport;
 import android.app.Dialog;
 import android.content.ActivityNotFoundException;
@@ -835,15 +835,6 @@
                 return;
             }
 
-            Intent intent = new Intent("android.intent.action.ANR");
-            if (!mService.mProcessesReady) {
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND);
-            }
-            mService.broadcastIntentLocked(null, null, intent,
-                    null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                    null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
-
             boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
             if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ab9ba08..a376e7a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -724,6 +724,8 @@
         synchronized (mStats) {
             mStats.noteWifiOnLocked();
         }
+        StatsLog.write(StatsLog.WIFI_ENABLED_STATE_CHANGED,
+                StatsLog.WIFI_ENABLED_STATE_CHANGED__STATE__ON);
     }
 
     public void noteWifiOff() {
@@ -731,6 +733,8 @@
         synchronized (mStats) {
             mStats.noteWifiOffLocked();
         }
+        StatsLog.write(StatsLog.WIFI_ENABLED_STATE_CHANGED,
+                StatsLog.WIFI_ENABLED_STATE_CHANGED__STATE__OFF);
     }
 
     public void noteStartAudio(int uid) {
@@ -865,6 +869,9 @@
         synchronized (mStats) {
             mStats.noteWifiRunningLocked(ws);
         }
+        // TODO: Log WIFI_RUNNING_STATE_CHANGED in a better spot to include Hotspot too.
+        StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+                ws, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__ON);
     }
 
     public void noteWifiRunningChanged(WorkSource oldWs, WorkSource newWs) {
@@ -872,6 +879,10 @@
         synchronized (mStats) {
             mStats.noteWifiRunningChangedLocked(oldWs, newWs);
         }
+        StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+                newWs, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__ON);
+        StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+                oldWs, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__OFF);
     }
 
     public void noteWifiStopped(WorkSource ws) {
@@ -879,6 +890,8 @@
         synchronized (mStats) {
             mStats.noteWifiStoppedLocked(ws);
         }
+        StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+                ws, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__OFF);
     }
 
     public void noteWifiState(int wifiState, String accessPoint) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 3a0899d..c290fbe 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -527,6 +527,24 @@
     private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
             BroadcastFilter filter, boolean ordered, int index) {
         boolean skip = false;
+        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                filter.packageName, filter.owningUid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            skip = true;
+        }
+        if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
+            Slog.w(TAG, "Firewall blocked: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            skip = true;
+        }
         if (filter.requiredPermission != null) {
             int perm = mService.checkComponentPermission(filter.requiredPermission,
                     r.callingPid, r.callingUid, -1, true);
@@ -619,11 +637,6 @@
             skip = true;
         }
 
-        if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
-            skip = true;
-        }
-
         if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
                 || filter.receiverList.app.isCrashing())) {
             Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
@@ -1082,6 +1095,24 @@
                         > brOptions.getMaxManifestReceiverApiLevel())) {
             skip = true;
         }
+        if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+            skip = true;
+        }
+        if (!skip) {
+            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                    r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
+            if (skip) {
+                Slog.w(TAG, "Firewall blocked: broadcasting "
+                        + r.intent.toString()
+                        + " from " + r.callerPackage + " (pid=" + r.callingPid
+                        + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+            }
+        }
         int perm = mService.checkComponentPermission(info.activityInfo.permission,
                 r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
                 info.activityInfo.exported);
@@ -1170,10 +1201,6 @@
                     + " (uid " + r.callingUid + ")");
             skip = true;
         }
-        if (!skip) {
-            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                    r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
-        }
         boolean isSingleton = false;
         try {
             isSingleton = mService.isSingleton(info.activityInfo.processName,
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 09064f2..fa7a4c5 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -135,4 +135,7 @@
 30062 am_on_activity_result_called (User|1|5),(Component Name|3),(Reason|3)
 
 # The task is being removed from its parent stack
-30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
\ No newline at end of file
+30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
+
+# The task is being compacted
+30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3)
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index 90fe30c..a584914 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -123,9 +123,8 @@
      * if the file is not available.
      */
     public static String readCmdlineFromProcfs(int pid) {
-        String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
-        String cmdline = readFileContents(path);
-        return cmdline != null ? cmdline : "";
+        final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
+        return parseCmdlineFromProcfs(readFileContents(path));
     }
 
     private static String readFileContents(String path) {
@@ -210,6 +209,24 @@
         return m.find() ? Long.parseLong(m.group(1)) * BYTES_IN_KILOBYTE : 0;
     }
 
+
+    /**
+     * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
+     *
+     * Parsing is required to strip anything after first null byte.
+     */
+    @VisibleForTesting
+    static String parseCmdlineFromProcfs(String cmdline) {
+        if (cmdline == null) {
+            return "";
+        }
+        int firstNullByte = cmdline.indexOf("\0");
+        if (firstNullByte == -1) {
+            return cmdline;
+        }
+        return cmdline.substring(0, firstNullByte);
+    }
+
     /**
      * Returns whether per-app memcg is available on device.
      */
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 4826f48..c4b7150 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -145,6 +145,9 @@
     int curAdj;                 // Current OOM adjustment for this process
     int setAdj;                 // Last set OOM adjustment for this process
     int verifiedAdj;            // The last adjustment that was verified as actually being set
+    long lastCompactTime;       // The last time that this process was compacted
+    int reqCompactAction;       // The most recent compaction action requested for this app.
+    int lastCompactAction;      // The most recent compaction action performed for this app.
     private int mCurSchedGroup; // Currently desired scheduling class
     int setSchedGroup;          // Last set to background scheduling class
     int trimMemoryLevel;        // Last selected memory trimming level
@@ -382,6 +385,8 @@
                 pw.print(" setRaw="); pw.print(setRawAdj);
                 pw.print(" cur="); pw.print(curAdj);
                 pw.print(" set="); pw.println(setAdj);
+        pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
+                pw.print(" lastCompactAction="); pw.print(lastCompactAction);
         pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
                 pw.print(" setSchedGroup="); pw.print(setSchedGroup);
                 pw.print(" systemNoUi="); pw.print(systemNoUi);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index d75601b..9dfdddb 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1382,7 +1382,7 @@
                     return;
                 }
 
-                mUpstreamNetworkMonitor.start(mDeps.getDefaultNetworkRequest());
+                mUpstreamNetworkMonitor.startObserveAllNetworks();
 
                 // TODO: De-duplicate with updateUpstreamWanted() below.
                 if (upstreamWanted()) {
@@ -1658,6 +1658,10 @@
         }
     }
 
+    public void systemReady() {
+        mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest());
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         // Binder.java closes the resource for us.
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 3e5d5aa..3ac311b 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -55,10 +55,13 @@
  * A class to centralize all the network and link properties information
  * pertaining to the current and any potential upstream network.
  *
- * Calling #start() registers two callbacks: one to track the system default
- * network and a second to observe all networks.  The latter is necessary
- * while the expression of preferred upstreams remains a list of legacy
- * connectivity types.  In future, this can be revisited.
+ * The owner of UNM gets it to register network callbacks by calling the
+ * following methods :
+ * Calling #startTrackDefaultNetwork() to track the system default network.
+ * Calling #startObserveAllNetworks() to observe all networks. Listening all
+ * networks is necessary while the expression of preferred upstreams remains
+ * a list of legacy connectivity types.  In future, this can be revisited.
+ * Calling #registerMobileNetworkRequest() to bring up mobile DUN/HIPRI network.
  *
  * The methods and data members of this class are only to be accessed and
  * modified from the tethering master state machine thread. Any other
@@ -119,33 +122,31 @@
         mCM = cm;
     }
 
-    public void start(NetworkRequest defaultNetworkRequest) {
-        stop();
-
-        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
-                .clearCapabilities().build();
-        mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
-        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
-
-        if (defaultNetworkRequest != null) {
-            // This is not really a "request", just a way of tracking the system default network.
-            // It's guaranteed not to actually bring up any networks because it's the same request
-            // as the ConnectivityService default request, and thus shares fate with it. We can't
-            // use registerDefaultNetworkCallback because it will not track the system default
-            // network if there is a VPN that applies to our UID.
+    public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) {
+        // This is not really a "request", just a way of tracking the system default network.
+        // It's guaranteed not to actually bring up any networks because it's the same request
+        // as the ConnectivityService default request, and thus shares fate with it. We can't
+        // use registerDefaultNetworkCallback because it will not track the system default
+        // network if there is a VPN that applies to our UID.
+        if (mDefaultNetworkCallback == null) {
             final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
             mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
             cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
         }
     }
 
+    public void startObserveAllNetworks() {
+        stop();
+
+        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
+                .clearCapabilities().build();
+        mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
+        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
+    }
+
     public void stop() {
         releaseMobileNetworkRequest();
 
-        releaseCallback(mDefaultNetworkCallback);
-        mDefaultNetworkCallback = null;
-        mDefaultInternetNetwork = null;
-
         releaseCallback(mListenAllCallback);
         mListenAllCallback = null;
 
@@ -264,9 +265,7 @@
         mNetworkMap.put(network, new NetworkState(null, null, null, network, null, null));
     }
 
-    private void handleNetCap(int callbackType, Network network, NetworkCapabilities newNc) {
-        if (callbackType == CALLBACK_DEFAULT_INTERNET) mDefaultInternetNetwork = network;
-
+    private void handleNetCap(Network network, NetworkCapabilities newNc) {
         final NetworkState prev = mNetworkMap.get(network);
         if (prev == null || newNc.equals(prev.networkCapabilities)) {
             // Ignore notifications about networks for which we have not yet
@@ -315,31 +314,25 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleSuspended(int callbackType, Network network) {
-        if (callbackType != CALLBACK_LISTEN_ALL) return;
+    private void handleSuspended(Network network) {
         if (!network.equals(mTetheringUpstreamNetwork)) return;
         mLog.log("SUSPENDED current upstream: " + network);
     }
 
-    private void handleResumed(int callbackType, Network network) {
-        if (callbackType != CALLBACK_LISTEN_ALL) return;
+    private void handleResumed(Network network) {
         if (!network.equals(mTetheringUpstreamNetwork)) return;
         mLog.log("RESUMED current upstream: " + network);
     }
 
-    private void handleLost(int callbackType, Network network) {
-        if (network.equals(mDefaultInternetNetwork)) {
-            mDefaultInternetNetwork = null;
-            // There are few TODOs within ConnectivityService's rematching code
-            // pertaining to spurious onLost() notifications.
-            //
-            // TODO: simplify this, probably if favor of code that:
-            //     - selects a new upstream if mTetheringUpstreamNetwork has
-            //       been lost (by any callback)
-            //     - deletes the entry from the map only when the LISTEN_ALL
-            //       callback gets  notified.
-            if (callbackType == CALLBACK_DEFAULT_INTERNET) return;
-        }
+    private void handleLost(Network network) {
+        // There are few TODOs within ConnectivityService's rematching code
+        // pertaining to spurious onLost() notifications.
+        //
+        // TODO: simplify this, probably if favor of code that:
+        //     - selects a new upstream if mTetheringUpstreamNetwork has
+        //       been lost (by any callback)
+        //     - deletes the entry from the map only when the LISTEN_ALL
+        //       callback gets notified.
 
         if (!mNetworkMap.containsKey(network)) {
             // Ignore loss of networks about which we had not previously
@@ -393,11 +386,17 @@
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
-            handleNetCap(mCallbackType, network, newNc);
+            if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
+                mDefaultInternetNetwork = network;
+                return;
+            }
+            handleNetCap(network, newNc);
         }
 
         @Override
         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
+            if (mCallbackType == CALLBACK_DEFAULT_INTERNET) return;
+
             handleLinkProp(network, newLp);
             // Any non-LISTEN_ALL callback will necessarily concern a network that will
             // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback.
@@ -409,17 +408,25 @@
 
         @Override
         public void onNetworkSuspended(Network network) {
-            handleSuspended(mCallbackType, network);
+            if (mCallbackType == CALLBACK_LISTEN_ALL) {
+                handleSuspended(network);
+            }
         }
 
         @Override
         public void onNetworkResumed(Network network) {
-            handleResumed(mCallbackType, network);
+            if (mCallbackType == CALLBACK_LISTEN_ALL) {
+                handleResumed(network);
+            }
         }
 
         @Override
         public void onLost(Network network) {
-            handleLost(mCallbackType, network);
+            if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
+                mDefaultInternetNetwork = null;
+                return;
+            }
+            handleLost(network);
             // Any non-LISTEN_ALL callback will necessarily concern a network that will
             // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback.
             // So it's not useful to do this work for non-LISTEN_ALL callbacks.
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b97e904..78b3c15 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,30 +16,26 @@
 
 package com.android.server.display;
 
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.text.format.DateUtils;
 import android.util.EventLog;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TimeUtils;
 
+import com.android.server.EventLogTags;
+
 import java.io.PrintWriter;
 
 class AutomaticBrightnessController {
@@ -127,7 +123,8 @@
     private final int mWeightingIntercept;
 
     // Configuration object for determining thresholds to change brightness dynamically
-    private final HysteresisLevels mHysteresisLevels;
+    private final HysteresisLevels mAmbientBrightnessThresholds;
+    private final HysteresisLevels mScreenBrightnessThresholds;
 
     // Amount of time to delay auto-brightness after screen on while waiting for
     // the light sensor to warm-up in milliseconds.
@@ -147,8 +144,12 @@
     private boolean mAmbientLuxValid;
 
     // The ambient light level threshold at which to brighten or darken the screen.
-    private float mBrighteningLuxThreshold;
-    private float mDarkeningLuxThreshold;
+    private float mAmbientBrighteningThreshold;
+    private float mAmbientDarkeningThreshold;
+
+    // The screen brightness threshold at which to brighten or darken the screen.
+    private float mScreenBrighteningThreshold;
+    private float mScreenDarkeningThreshold;
 
     // The most recent light sample.
     private float mLastObservedLux;
@@ -196,7 +197,8 @@
             int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
             int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
             long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
-            HysteresisLevels hysteresisLevels) {
+            HysteresisLevels ambientBrightnessThresholds,
+            HysteresisLevels screenBrightnessThresholds) {
         mCallbacks = callbacks;
         mSensorManager = sensorManager;
         mBrightnessMapper = mapper;
@@ -212,7 +214,8 @@
         mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
         mAmbientLightHorizon = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
         mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
-        mHysteresisLevels = hysteresisLevels;
+        mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+        mScreenBrightnessThresholds = screenBrightnessThresholds;
         mShortTermModelValid = true;
         mShortTermModelAnchor = -1;
 
@@ -364,8 +367,10 @@
         pw.println("  mCurrentLightSensorRate=" + mCurrentLightSensorRate);
         pw.println("  mAmbientLux=" + mAmbientLux);
         pw.println("  mAmbientLuxValid=" + mAmbientLuxValid);
-        pw.println("  mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
-        pw.println("  mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
+        pw.println("  mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
+        pw.println("  mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
+        pw.println("  mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
+        pw.println("  mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
         pw.println("  mLastObservedLux=" + mLastObservedLux);
         pw.println("  mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
         pw.println("  mRecentLightSamples=" + mRecentLightSamples);
@@ -384,7 +389,8 @@
         mBrightnessMapper.dump(pw);
 
         pw.println();
-        mHysteresisLevels.dump(pw);
+        mAmbientBrightnessThresholds.dump(pw);
+        mScreenBrightnessThresholds.dump(pw);
     }
 
     private boolean setLightSensorEnabled(boolean enable) {
@@ -460,8 +466,8 @@
             lux = 0;
         }
         mAmbientLux = lux;
-        mBrighteningLuxThreshold = mHysteresisLevels.getBrighteningThreshold(lux);
-        mDarkeningLuxThreshold = mHysteresisLevels.getDarkeningThreshold(lux);
+        mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+        mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
         if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
@@ -552,7 +558,7 @@
         final int N = mAmbientLightRingBuffer.size();
         long earliestValidTime = time;
         for (int i = N - 1; i >= 0; i--) {
-            if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
+            if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
                 break;
             }
             earliestValidTime = mAmbientLightRingBuffer.getTime(i);
@@ -564,7 +570,7 @@
         final int N = mAmbientLightRingBuffer.size();
         long earliestValidTime = time;
         for (int i = N - 1; i >= 0; i--) {
-            if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
+            if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
                 break;
             }
             earliestValidTime = mAmbientLightRingBuffer.getTime(i);
@@ -617,20 +623,19 @@
         float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS);
         float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS);
 
-        if ((slowAmbientLux >= mBrighteningLuxThreshold &&
-             fastAmbientLux >= mBrighteningLuxThreshold &&
-             nextBrightenTransition <= time)
-             ||
-            (slowAmbientLux <= mDarkeningLuxThreshold &&
-             fastAmbientLux <= mDarkeningLuxThreshold &&
-             nextDarkenTransition <= time)) {
+        if ((slowAmbientLux >= mAmbientBrighteningThreshold
+                && fastAmbientLux >= mAmbientBrighteningThreshold
+                && nextBrightenTransition <= time)
+                || (slowAmbientLux <= mAmbientDarkeningThreshold
+                        && fastAmbientLux <= mAmbientDarkeningThreshold
+                        && nextDarkenTransition <= time)) {
             setAmbientLux(fastAmbientLux);
             if (DEBUG) {
-                Slog.d(TAG, "updateAmbientLux: " +
-                        ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " +
-                        "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", " +
-                        "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
-                        "mAmbientLux=" + mAmbientLux);
+                Slog.d(TAG, "updateAmbientLux: "
+                        + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+                        + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
+                        + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+                        + "mAmbientLux=" + mAmbientLux);
             }
             updateAutoBrightness(true);
             nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
@@ -661,7 +666,22 @@
 
         int newScreenAutoBrightness =
                 clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
+
+        // If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
+        // in which case we ignore the new screen brightness if it doesn't differ enough from the
+        // previous one.
+        if (mScreenAutoBrightness != -1
+                && newScreenAutoBrightness > mScreenDarkeningThreshold
+                && newScreenAutoBrightness < mScreenBrighteningThreshold) {
+            if (DEBUG) {
+                Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold
+                        + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold);
+            }
+            return;
+        }
+
         if (mScreenAutoBrightness != newScreenAutoBrightness) {
+
             if (DEBUG) {
                 Slog.d(TAG, "updateAutoBrightness: " +
                         "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
@@ -669,6 +689,11 @@
             }
 
             mScreenAutoBrightness = newScreenAutoBrightness;
+            mScreenBrighteningThreshold =
+                    mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness);
+            mScreenDarkeningThreshold =
+                    mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness);
+
             if (sendUpdate) {
                 mCallbacks.updateBrightness();
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e2c8ef9..249270b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -16,16 +16,11 @@
 
 package com.android.server.display;
 
-import android.app.ActivityManager;
-import com.android.internal.app.IBatteryStats;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.policy.WindowManagerPolicy;
-
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
@@ -54,6 +49,11 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
+import com.android.internal.app.IBatteryStats;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.policy.WindowManagerPolicy;
+
 import java.io.PrintWriter;
 
 /**
@@ -422,14 +422,24 @@
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
-            int[] brightLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_dynamicHysteresisBrightLevels);
-            int[] darkLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_dynamicHysteresisDarkLevels);
-            int[] luxHysteresisLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_dynamicHysteresisLuxLevels);
-            HysteresisLevels hysteresisLevels = new HysteresisLevels(
-                    brightLevels, darkLevels, luxHysteresisLevels);
+            int[] ambientBrighteningThresholds = resources.getIntArray(
+                    com.android.internal.R.array.config_ambientBrighteningThresholds);
+            int[] ambientDarkeningThresholds = resources.getIntArray(
+                    com.android.internal.R.array.config_ambientDarkeningThresholds);
+            int[] ambientThresholdLevels = resources.getIntArray(
+                    com.android.internal.R.array.config_ambientThresholdLevels);
+            HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+                    ambientBrighteningThresholds, ambientDarkeningThresholds,
+                    ambientThresholdLevels);
+
+            int[] screenBrighteningThresholds = resources.getIntArray(
+                    com.android.internal.R.array.config_screenBrighteningThresholds);
+            int[] screenDarkeningThresholds = resources.getIntArray(
+                    com.android.internal.R.array.config_screenDarkeningThresholds);
+            int[] screenThresholdLevels = resources.getIntArray(
+                    com.android.internal.R.array.config_screenThresholdLevels);
+            HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels);
 
             long brighteningLightDebounce = resources.getInteger(
                     com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
@@ -459,7 +469,8 @@
                         lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum,
                         mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
                         initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
-                        autoBrightnessResetAmbientLuxAfterWarmUp, hysteresisLevels);
+                        autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+                        screenBrightnessThresholds);
             } else {
                 mUseSoftwareAutoBrightnessConfig = false;
             }
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 1c02dd1..2db1d03 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -28,67 +28,67 @@
     private static final String TAG = "HysteresisLevels";
 
     // Default hysteresis constraints for brightening or darkening.
-    // The recent lux must have changed by at least this fraction relative to the
-    // current ambient lux before a change will be considered.
+    // The recent value must have changed by at least this fraction relative to the
+    // current value before a change will be considered.
     private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
     private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
 
     private static final boolean DEBUG = false;
 
-    private final float[] mBrightLevels;
-    private final float[] mDarkLevels;
-    private final float[] mLuxLevels;
+    private final float[] mBrighteningThresholds;
+    private final float[] mDarkeningThresholds;
+    private final float[] mThresholdLevels;
 
-  /**
-   * Creates a {@code HysteresisLevels} object with the given equal-length
-   * integer arrays.
-   * @param brightLevels an array of brightening hysteresis constraint constants
-   * @param darkLevels an array of darkening hysteresis constraint constants
-   * @param luxLevels a monotonically increasing array of illuminance
-   *                  thresholds in units of lux
-   */
-    public HysteresisLevels(int[] brightLevels, int[] darkLevels, int[] luxLevels) {
-        if (brightLevels.length != darkLevels.length || darkLevels.length != luxLevels.length + 1) {
+    /**
+     * Creates a {@code HysteresisLevels} object with the given equal-length
+     * integer arrays.
+     * @param brighteningThresholds an array of brightening hysteresis constraint constants.
+     * @param darkeningThresholds an array of darkening hysteresis constraint constants.
+     * @param thresholdLevels a monotonically increasing array of threshold levels.
+    */
+    HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
+            int[] thresholdLevels) {
+        if (brighteningThresholds.length != darkeningThresholds.length
+                || darkeningThresholds.length != thresholdLevels.length + 1) {
             throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
         }
-        mBrightLevels = setArrayFormat(brightLevels, 1000.0f);
-        mDarkLevels = setArrayFormat(darkLevels, 1000.0f);
-        mLuxLevels = setArrayFormat(luxLevels, 1.0f);
+        mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
+        mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
+        mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
     }
 
     /**
-     * Return the brightening hysteresis threshold for the given lux level.
+     * Return the brightening hysteresis threshold for the given value level.
      */
-    public float getBrighteningThreshold(float lux) {
-        float brightConstant = getReferenceLevel(lux, mBrightLevels);
-        float brightThreshold = lux * (1.0f + brightConstant);
+    float getBrighteningThreshold(float value) {
+        float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+        float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
-            Slog.d(TAG, "bright hysteresis constant=: " + brightConstant + ", threshold="
-                + brightThreshold + ", lux=" + lux);
+            Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+                    + brightThreshold + ", value=" + value);
         }
         return brightThreshold;
     }
 
     /**
-     * Return the darkening hysteresis threshold for the given lux level.
+     * Return the darkening hysteresis threshold for the given value level.
      */
-    public float getDarkeningThreshold(float lux) {
-        float darkConstant = getReferenceLevel(lux, mDarkLevels);
-        float darkThreshold = lux * (1.0f - darkConstant);
+    float getDarkeningThreshold(float value) {
+        float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+        float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
-                + darkThreshold + ", lux=" + lux);
+                    + darkThreshold + ", value=" + value);
         }
         return darkThreshold;
     }
 
     /**
-     * Return the hysteresis constant for the closest lux threshold value to the
-     * current illuminance from the given array.
+     * Return the hysteresis constant for the closest threshold value from the given array.
      */
-    private float getReferenceLevel(float lux, float[] referenceLevels) {
+    private float getReferenceLevel(float value, float[] referenceLevels) {
         int index = 0;
-        while (mLuxLevels.length > index && lux >= mLuxLevels[index]) {
+        while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
             ++index;
         }
         return referenceLevels[index];
@@ -105,10 +105,10 @@
         return levelArray;
     }
 
-    public void dump(PrintWriter pw) {
+    void dump(PrintWriter pw) {
         pw.println("HysteresisLevels");
-        pw.println("  mBrightLevels=" + Arrays.toString(mBrightLevels));
-        pw.println("  mDarkLevels=" + Arrays.toString(mDarkLevels));
-        pw.println("  mLuxLevels=" + Arrays.toString(mLuxLevels));
+        pw.println("  mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
+        pw.println("  mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
+        pw.println("  mThresholdLevels=" + Arrays.toString(mThresholdLevels));
     }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 78e18e9..10dc156 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -67,6 +67,7 @@
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.KeyValueListParser;
@@ -364,6 +365,8 @@
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
         private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
+        private static final String KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS =
+                "tc_skip_not_ready_jobs";
         private static final String KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
                 "qc_allowed_time_per_period_ms";
         private static final String KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS =
@@ -376,6 +379,8 @@
                 "qc_window_size_frequent_ms";
         private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
                 "qc_window_size_rare_ms";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+                "qc_max_execution_time_ms";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -402,6 +407,7 @@
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_USE_HEARTBEATS = true;
+        private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
         private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
                 10 * 60 * 1000L; // 10 minutes
         private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS =
@@ -414,6 +420,8 @@
                 8 * 60 * 60 * 1000L; // 8 hours
         private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
                 24 * 60 * 60 * 1000L; // 24 hours
+        private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+                4 * 60 * 60 * 1000L; // 4 hours
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -538,6 +546,13 @@
          */
         public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
 
+        /**
+         * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
+         * ready now.
+         */
+        public boolean TIME_CONTROLLER_SKIP_NOT_READY_JOBS =
+                DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS;
+
         /** How much time each app will have to run jobs within their standby bucket window. */
         public long QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
                 DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS;
@@ -581,6 +596,12 @@
         public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
                 DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS;
 
+        /**
+         * The maximum amount of time an app can have its jobs running within a 24 hour window.
+         */
+        public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+                DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
+
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         void updateConstantsLocked(String value) {
@@ -653,6 +674,9 @@
             CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
             USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
+            TIME_CONTROLLER_SKIP_NOT_READY_JOBS = mParser.getBoolean(
+                    KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS,
+                    DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS);
             QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
                     KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS,
                     DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS);
@@ -671,6 +695,9 @@
             QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
                     KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS,
                     DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
+            QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
+                    KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
         }
 
         void dump(IndentingPrintWriter pw) {
@@ -705,6 +732,8 @@
             pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
             pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
+            pw.printPair(KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS,
+                    TIME_CONTROLLER_SKIP_NOT_READY_JOBS).println();
             pw.printPair(KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS,
                     QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS).println();
             pw.printPair(KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS,
@@ -717,6 +746,8 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS).println();
             pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS,
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
+                    QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
             pw.decreaseIndent();
         }
 
@@ -748,6 +779,11 @@
             proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
             proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
 
+            final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER);
+            proto.write(ConstantsProto.TimeController.SKIP_NOT_READY_JOBS,
+                    TIME_CONTROLLER_SKIP_NOT_READY_JOBS);
+            proto.end(tcToken);
+
             final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
             proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
                     QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS);
@@ -761,6 +797,8 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS);
             proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS,
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
+            proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
+                    QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
             proto.end(qcToken);
 
             proto.end(token);
@@ -861,6 +899,11 @@
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
                     cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
+                    synchronized (mLock) {
+                        for (int c = 0; c < mControllers.size(); ++c) {
+                            mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
+                        }
+                    }
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -868,6 +911,11 @@
                     Slog.d(TAG, "Removing jobs for user: " + userId);
                 }
                 cancelJobsForUser(userId);
+                synchronized (mLock) {
+                    for (int c = 0; c < mControllers.size(); ++c) {
+                        mControllers.get(c).onUserRemovedLocked(userId);
+                    }
+                }
             } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
                 // Has this package scheduled any jobs, such that we will take action
                 // if it were to be force-stopped?
@@ -942,9 +990,15 @@
         return mConstants;
     }
 
+    public boolean isChainedAttributionEnabled() {
+        return WorkSource.isChainedBatteryAttributionEnabled(getContext());
+    }
+
     @Override
     public void onStartUser(int userHandle) {
-        mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+        synchronized (mLock) {
+            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+        }
         // Let's kick any outstanding jobs for this user.
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
     }
@@ -957,7 +1011,9 @@
 
     @Override
     public void onStopUser(int userHandle) {
-        mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+        synchronized (mLock) {
+            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+        }
     }
 
     /**
@@ -2140,8 +2196,7 @@
 
         final boolean jobExists = mJobs.containsJob(job);
 
-        final int userId = job.getUserId();
-        final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
+        final boolean userStarted = areUsersStartedLocked(job);
 
         if (DEBUG) {
             Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
@@ -2229,7 +2284,7 @@
         try {
             componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                     job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                    userId) != null);
+                    job.getUserId()) != null);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -3042,6 +3097,13 @@
                     printed = true;
                     pw.println("user-stopped");
                 }
+                if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) {
+                    if (printed) {
+                        pw.print(" ");
+                    }
+                    printed = true;
+                    pw.println("source-user-stopped");
+                }
                 if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
                     if (printed) {
                         pw.print(" ");
@@ -3193,7 +3255,7 @@
                     pw.print(" (job=");
                     pw.print(job.isReady());
                     pw.print(" user=");
-                    pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
+                    pw.print(areUsersStartedLocked(job));
                     pw.print(" !pending=");
                     pw.print(!mPendingJobs.contains(job));
                     pw.print(" !active=");
@@ -3363,7 +3425,7 @@
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
                             job.isReady());
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
-                            ArrayUtils.contains(mStartedUsers, job.getUserId()));
+                            areUsersStartedLocked(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
                             mPendingJobs.contains(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 8f104e4..aca02bf 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -296,6 +296,12 @@
         mRequestedWhitelistJobs.remove(uid);
     }
 
+    @GuardedBy("mLock")
+    @Override
+    public void onAppRemovedLocked(String pkgName, int uid) {
+        mTrackedJobs.delete(uid);
+    }
+
     /**
      * Test to see if running the given job on the given network is insane.
      * <p>
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index 660c238..ac2dbdf 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -54,14 +54,13 @@
 import com.android.server.job.StateControllerProto;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
- * Controller that tracks whether a package has exceeded its standby bucket quota.
+ * Controller that tracks whether an app has exceeded its standby bucket quota.
  *
  * Each job in each bucket is given 10 minutes to run within its respective time window. Active
  * jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window,
@@ -98,6 +97,19 @@
             data.put(packageName, obj);
         }
 
+        /** Removes all the data for the user, if there was any. */
+        public void delete(int userId) {
+            mData.delete(userId);
+        }
+
+        /** Removes the data for the user and package, if there was any. */
+        public void delete(int userId, @NonNull String packageName) {
+            ArrayMap<String, T> data = mData.get(userId);
+            if (data != null) {
+                data.remove(packageName);
+            }
+        }
+
         @Nullable
         public T get(int userId, @NonNull String packageName) {
             ArrayMap<String, T> data = mData.get(userId);
@@ -190,6 +202,80 @@
         }
     }
 
+    private static int hashLong(long val) {
+        return (int) (val ^ (val >>> 32));
+    }
+
+    @VisibleForTesting
+    static class ExecutionStats {
+        /**
+         * The time at which this record should be considered invalid, in the elapsed realtime
+         * timebase.
+         */
+        public long invalidTimeElapsed;
+
+        public long windowSizeMs;
+
+        /** The total amount of time the app ran in its respective bucket window size. */
+        public long executionTimeInWindowMs;
+        public int bgJobCountInWindow;
+
+        /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+        public long executionTimeInMaxPeriodMs;
+        public int bgJobCountInMaxPeriod;
+
+        /**
+         * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
+         * the quota. This is only valid if
+         * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
+         * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+         */
+        public long quotaCutoffTimeElapsed;
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+                    .append("windowSize=").append(windowSizeMs).append(", ")
+                    .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
+                    .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
+                    .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
+                    .append(", ")
+                    .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
+                    .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+                    .toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ExecutionStats) {
+                ExecutionStats other = (ExecutionStats) obj;
+                return this.invalidTimeElapsed == other.invalidTimeElapsed
+                        && this.windowSizeMs == other.windowSizeMs
+                        && this.executionTimeInWindowMs == other.executionTimeInWindowMs
+                        && this.bgJobCountInWindow == other.bgJobCountInWindow
+                        && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
+                        && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
+                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            result = 31 * result + hashLong(invalidTimeElapsed);
+            result = 31 * result + hashLong(windowSizeMs);
+            result = 31 * result + hashLong(executionTimeInWindowMs);
+            result = 31 * result + bgJobCountInWindow;
+            result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
+            result = 31 * result + bgJobCountInMaxPeriod;
+            result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+            return result;
+        }
+    }
+
     /** List of all tracked jobs keyed by source package-userId combo. */
     private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
 
@@ -205,6 +291,9 @@
      */
     private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
 
+    /** Cached calculation results for each app, with the standby buckets as the array indices. */
+    private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
+
     private final AlarmManager mAlarmManager;
     private final ChargingTracker mChargeTracker;
     private final Handler mHandler;
@@ -222,11 +311,29 @@
     private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
 
     /**
-     * How much time the package should have before transitioning from out-of-quota to in-quota.
-     * This should not affect processing if the package is already in-quota.
+     * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+     * window.
+     */
+    private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
+
+    /**
+     * How much time the app should have before transitioning from out-of-quota to in-quota.
+     * This should not affect processing if the app is already in-quota.
      */
     private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
 
+    /**
+     * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
+     * an app will have enough quota to transition from out-of-quota to in-quota.
+     */
+    private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+
+    /**
+     * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+     * app will have enough quota to transition from out-of-quota to in-quota.
+     */
+    private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+
     private long mNextCleanupTimeElapsed = 0;
     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
             new AlarmManager.OnAlarmListener() {
@@ -250,7 +357,7 @@
     /** The maximum period any bucket can have. */
     private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
 
-    /** A package has reached its quota. The message should contain a {@link Package} object. */
+    /** An app has reached its quota. The message should contain a {@link Package} object. */
     private static final int MSG_REACHED_QUOTA = 0;
     /** Drop any old timing sessions. */
     private static final int MSG_CLEAN_UP_SESSIONS = 1;
@@ -328,12 +435,15 @@
                 Math.max(MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS));
         if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
             mAllowedTimePerPeriodMs = newAllowedTimeMs;
+            mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
             changed = true;
         }
         long newQuotaBufferMs = Math.max(0,
                 Math.min(5 * MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS));
         if (mQuotaBufferMs != newQuotaBufferMs) {
             mQuotaBufferMs = newQuotaBufferMs;
+            mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+            mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
             changed = true;
         }
         long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
@@ -360,6 +470,13 @@
             mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
             changed = true;
         }
+        long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS,
+                Math.min(MAX_PERIOD_MS, mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS));
+        if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+            mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+            mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+            changed = true;
+        }
 
         if (changed) {
             // Update job bookkeeping out of band.
@@ -371,6 +488,40 @@
         }
     }
 
+    @Override
+    public void onAppRemovedLocked(String packageName, int uid) {
+        if (packageName == null) {
+            Slog.wtf(TAG, "Told app removed but given null package name.");
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        mTrackedJobs.delete(userId, packageName);
+        Timer timer = mPkgTimers.get(userId, packageName);
+        if (timer != null) {
+            if (timer.isActive()) {
+                Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
+                timer.dropEverything();
+            }
+            mPkgTimers.delete(userId, packageName);
+        }
+        mTimingSessions.delete(userId, packageName);
+        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+        if (alarmListener != null) {
+            mAlarmManager.cancel(alarmListener);
+            mInQuotaAlarmListeners.delete(userId, packageName);
+        }
+        mExecutionStatsCache.delete(userId, packageName);
+    }
+
+    @Override
+    public void onUserRemovedLocked(int userId) {
+        mTrackedJobs.delete(userId);
+        mPkgTimers.delete(userId);
+        mTimingSessions.delete(userId);
+        mInQuotaAlarmListeners.delete(userId);
+        mExecutionStatsCache.delete(userId);
+    }
+
     /**
      * Returns an appropriate standby bucket for the job, taking into account any standby
      * exemptions.
@@ -394,7 +545,6 @@
     private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
         if (standbyBucket == NEVER_INDEX) return false;
-        if (standbyBucket == ACTIVE_INDEX) return true;
         // This check is needed in case the flag is toggled after a job has been registered.
         if (!mShouldThrottle) return true;
 
@@ -427,46 +577,152 @@
         if (standbyBucket == NEVER_INDEX) {
             return 0;
         }
-        final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
-        final long trailingRunDurationMs = getTrailingExecutionTimeLocked(
-                userId, packageName, bucketWindowSizeMs);
-        return mAllowedTimePerPeriodMs - trailingRunDurationMs;
+        final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+        return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
+                mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
     }
 
-    /** Returns how long the uid has had jobs running within the most recent window. */
+    /** Returns the execution stats of the app in the most recent window. */
     @VisibleForTesting
-    long getTrailingExecutionTimeLocked(final int userId, @NonNull final String packageName,
-            final long windowSizeMs) {
-        long totalTime = 0;
+    @NonNull
+    ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
+            final int standbyBucket) {
+        if (standbyBucket == NEVER_INDEX) {
+            Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
+            return new ExecutionStats();
+        }
+        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+        if (appStats == null) {
+            appStats = new ExecutionStats[mBucketPeriodsMs.length];
+            mExecutionStatsCache.add(userId, packageName, appStats);
+        }
+        ExecutionStats stats = appStats[standbyBucket];
+        if (stats == null) {
+            stats = new ExecutionStats();
+            appStats[standbyBucket] = stats;
+        }
+        final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+        Timer timer = mPkgTimers.get(userId, packageName);
+        if ((timer != null && timer.isActive())
+                || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
+                || stats.windowSizeMs != bucketWindowSizeMs) {
+            // The stats are no longer valid.
+            stats.windowSizeMs = bucketWindowSizeMs;
+            updateExecutionStatsLocked(userId, packageName, stats);
+        }
+
+        return stats;
+    }
+
+    @VisibleForTesting
+    void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+            @NonNull ExecutionStats stats) {
+        stats.executionTimeInWindowMs = 0;
+        stats.bgJobCountInWindow = 0;
+        stats.executionTimeInMaxPeriodMs = 0;
+        stats.bgJobCountInMaxPeriod = 0;
+        stats.quotaCutoffTimeElapsed = 0;
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
+        stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
         if (timer != null && timer.isActive()) {
-            totalTime = timer.getCurrentDuration(nowElapsed);
+            stats.executionTimeInWindowMs =
+                    stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
+            stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
+            // If the timer is active, the value will be stale at the next method call, so
+            // invalidate now.
+            stats.invalidTimeElapsed = nowElapsed;
+            if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                        nowElapsed - mAllowedTimeIntoQuotaMs);
+            }
+            if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                        nowElapsed - mMaxExecutionTimeIntoQuotaMs);
+            }
         }
 
         List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
         if (sessions == null || sessions.size() == 0) {
-            return totalTime;
+            return;
         }
 
-        final long startElapsed = nowElapsed - windowSizeMs;
+        final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
+        final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
+        // The minimum time between the start time and the beginning of the sessions that were
+        // looked at --> how much time the stats will be valid for.
+        long emptyTimeMs = Long.MAX_VALUE;
         // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
         // the most recent ones.
         for (int i = sessions.size() - 1; i >= 0; --i) {
             TimingSession session = sessions.get(i);
-            if (startElapsed < session.startTimeElapsed) {
-                totalTime += session.endTimeElapsed - session.startTimeElapsed;
-            } else if (startElapsed < session.endTimeElapsed) {
+
+            // Window management.
+            if (startWindowElapsed < session.startTimeElapsed) {
+                stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed;
+                stats.bgJobCountInWindow += session.bgJobCount;
+                emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
+                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                            session.startTimeElapsed + stats.executionTimeInWindowMs
+                                    - mAllowedTimeIntoQuotaMs);
+                }
+            } else if (startWindowElapsed < session.endTimeElapsed) {
                 // The session started before the window but ended within the window. Only include
                 // the portion that was within the window.
-                totalTime += session.endTimeElapsed - startElapsed;
+                stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed;
+                stats.bgJobCountInWindow += session.bgJobCount;
+                emptyTimeMs = 0;
+                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                            startWindowElapsed + stats.executionTimeInWindowMs
+                                    - mAllowedTimeIntoQuotaMs);
+                }
+            }
+
+            // Max period check.
+            if (startMaxElapsed < session.startTimeElapsed) {
+                stats.executionTimeInMaxPeriodMs +=
+                        session.endTimeElapsed - session.startTimeElapsed;
+                stats.bgJobCountInMaxPeriod += session.bgJobCount;
+                emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
+                if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                            session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
+                                    - mMaxExecutionTimeIntoQuotaMs);
+                }
+            } else if (startMaxElapsed < session.endTimeElapsed) {
+                // The session started before the window but ended within the window. Only include
+                // the portion that was within the window.
+                stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed;
+                stats.bgJobCountInMaxPeriod += session.bgJobCount;
+                emptyTimeMs = 0;
+                if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                            startMaxElapsed + stats.executionTimeInMaxPeriodMs
+                                    - mMaxExecutionTimeIntoQuotaMs);
+                }
             } else {
                 // This session ended before the window. No point in going any further.
-                return totalTime;
+                break;
             }
         }
-        return totalTime;
+        stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+    }
+
+    private void invalidateAllExecutionStatsLocked(final int userId,
+            @NonNull final String packageName) {
+        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+        if (appStats != null) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
+            for (int i = 0; i < appStats.length; ++i) {
+                ExecutionStats stats = appStats[i];
+                if (stats != null) {
+                    stats.invalidTimeElapsed = nowElapsed;
+                }
+            }
+        }
     }
 
     @VisibleForTesting
@@ -479,6 +735,8 @@
                 mTimingSessions.add(userId, packageName, sessions);
             }
             sessions.add(session);
+            // Adding a new session means that the current stats are now incorrect.
+            invalidateAllExecutionStatsLocked(userId, packageName);
 
             maybeScheduleCleanupAlarmLocked();
         }
@@ -612,87 +870,43 @@
     @VisibleForTesting
     void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
-        final String pkgString = string(userId, packageName);
         if (standbyBucket == NEVER_INDEX) {
             return;
-        } else if (standbyBucket == ACTIVE_INDEX) {
-            // ACTIVE apps are "always" in quota.
-            if (DEBUG) {
-                Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
-                        + " even though it is active");
-            }
-            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
+        }
 
-            QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+        final String pkgString = string(userId, packageName);
+        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+        if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
+                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+            // Already in quota. Why was this method called?
+            if (DEBUG) {
+                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
+                        + " even though it already has "
+                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+                        + "ms in its quota.");
+            }
             if (alarmListener != null) {
                 // Cancel any pending alarm.
                 mAlarmManager.cancel(alarmListener);
                 // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
                 alarmListener.setTriggerTime(0);
             }
-            return;
-        }
-
-        List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
-        if (sessions == null || sessions.size() == 0) {
-            // If there are no sessions, then the job is probably in quota.
-            if (DEBUG) {
-                Slog.wtf(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
-                        + " even though it is likely within its quota.");
-            }
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
-
-        final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        // How far back we need to look.
-        final long startElapsed = nowElapsed - bucketWindowSizeMs;
-
-        long totalTime = 0;
-        long cutoffTimeElapsed = nowElapsed;
-        for (int i = sessions.size() - 1; i >= 0; i--) {
-            TimingSession session = sessions.get(i);
-            if (startElapsed < session.startTimeElapsed) {
-                cutoffTimeElapsed = session.startTimeElapsed;
-                totalTime += session.endTimeElapsed - session.startTimeElapsed;
-            } else if (startElapsed < session.endTimeElapsed) {
-                // The session started before the window but ended within the window. Only
-                // include the portion that was within the window.
-                cutoffTimeElapsed = startElapsed;
-                totalTime += session.endTimeElapsed - startElapsed;
-            } else {
-                // This session ended before the window. No point in going any further.
-                break;
-            }
-            if (totalTime >= mAllowedTimePerPeriodMs) {
-                break;
-            }
-        }
-        if (totalTime < mAllowedTimePerPeriodMs) {
-            // Already in quota. Why was this method called?
-            if (DEBUG) {
-                Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
-                        + " even though it already has " + (mAllowedTimePerPeriodMs - totalTime)
-                        + "ms in its quota.");
-            }
-            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
-            return;
-        }
-
-        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
         if (alarmListener == null) {
             alarmListener = new QcAlarmListener(userId, packageName);
             mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
         }
 
-        // We add all the way back to the beginning of a session (or the window) even when we don't
-        // need to (in order to simplify the for loop above), so there might be some extra we
-        // need to add back.
-        final long extraTimeMs = totalTime - mAllowedTimePerPeriodMs;
         // The time this app will have quota again.
-        final long inQuotaTimeElapsed =
-                cutoffTimeElapsed + extraTimeMs + mQuotaBufferMs + bucketWindowSizeMs;
+        long inQuotaTimeElapsed =
+                stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+        if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
+            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+                    stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
+        }
         // Only schedule the alarm if:
         // 1. There isn't one currently scheduled
         // 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -702,13 +916,15 @@
         // TODO: this might be overengineering. Simplify if proven safe.
         if (!alarmListener.isWaiting()
                 || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
-                || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed - mQuotaBufferMs) {
+                || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
             if (DEBUG) Slog.d(TAG, "Scheduling start alarm for " + pkgString);
             // If the next time this app will have quota is at least 3 minutes before the
             // alarm is supposed to go off, reschedule the alarm.
             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
                     ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
             alarmListener.setTriggerTime(inQuotaTimeElapsed);
+        } else if (DEBUG) {
+            Slog.d(TAG, "No need to scheduling start alarm for " + pkgString);
         }
     }
 
@@ -771,10 +987,18 @@
         // How many background jobs ran during this session.
         public final int bgJobCount;
 
-        TimingSession(long startElapsed, long endElapsed, int jobCount) {
+        private final int mHashCode;
+
+        TimingSession(long startElapsed, long endElapsed, int bgJobCount) {
             this.startTimeElapsed = startElapsed;
             this.endTimeElapsed = endElapsed;
-            this.bgJobCount = jobCount;
+            this.bgJobCount = bgJobCount;
+
+            int hashCode = 0;
+            hashCode = 31 * hashCode + hashLong(startTimeElapsed);
+            hashCode = 31 * hashCode + hashLong(endTimeElapsed);
+            hashCode = 31 * hashCode + bgJobCount;
+            mHashCode = hashCode;
         }
 
         @Override
@@ -797,7 +1021,7 @@
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount});
+            return mHashCode;
         }
 
         public void dump(IndentingPrintWriter pw) {
@@ -857,6 +1081,9 @@
                     if (mRunningBgJobs.size() == 1) {
                         // Started tracking the first job.
                         mStartTimeElapsed = sElapsedRealtimeClock.millis();
+                        // Starting the timer means that all cached execution stats are now
+                        // incorrect.
+                        invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
                         scheduleCutoff();
                     }
                 }
@@ -882,6 +1109,15 @@
             }
         }
 
+        /**
+         * Stops tracking all jobs and cancels any pending alarms. This should only be called if
+         * the Timer is not going to be used anymore.
+         */
+        void dropEverything() {
+            mRunningBgJobs.clear();
+            cancelCutoff();
+        }
+
         private void emitSessionLocked(long nowElapsed) {
             if (mBgJobCount <= 0) {
                 // Nothing to emit.
@@ -912,6 +1148,12 @@
             }
         }
 
+        int getBgJobCount() {
+            synchronized (mLock) {
+                return mBgJobCount;
+            }
+        }
+
         void onChargingChanged(long nowElapsed, boolean isCharging) {
             synchronized (mLock) {
                 if (isCharging) {
@@ -924,6 +1166,9 @@
                         // repeatedly plugged in and unplugged, the job count for a package may be
                         // artificially high.
                         mBgJobCount = mRunningBgJobs.size();
+                        // Starting the timer means that all cached execution stats are now
+                        // incorrect.
+                        invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
                         // Schedule cutoff since we're now actively tracking for quotas again.
                         scheduleCutoff();
                     }
@@ -1185,6 +1430,11 @@
     }
 
     @VisibleForTesting
+    long getMaxExecutionTimeMs() {
+        return mMaxExecutionTimeMs;
+    }
+
+    @VisibleForTesting
     @Nullable
     List<TimingSession> getTimingSessions(int userId, String packageName) {
         return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index 61dc479..97c3bac 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -83,6 +83,28 @@
     public void onConstantsUpdatedLocked() {
     }
 
+    /** Called when a package is uninstalled from the device (not for an update). */
+    public void onAppRemovedLocked(String packageName, int uid) {
+    }
+
+    /** Called when a user is removed from the device. */
+    public void onUserRemovedLocked(int userId) {
+    }
+
+    /**
+     * Called when JobSchedulerService has determined that the job is not ready to be run. The
+     * Controller can evaluate if it can or should do something to promote this job's readiness.
+     */
+    public void evaluateStateLocked(JobStatus jobStatus) {
+    }
+
+    /**
+     * Called when something with the UID has changed. The controller should re-evaluate any
+     * internal state tracking dependent on this UID.
+     */
+    public void reevaluateStateLocked(int uid) {
+    }
+
     protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
         // This is very cheap to check (just a few conditions on data in JobStatus).
         final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint);
@@ -100,20 +122,6 @@
         return mService.areComponentsInPlaceLocked(jobStatus);
     }
 
-    /**
-     * Called when JobSchedulerService has determined that the job is not ready to be run. The
-     * Controller can evaluate if it can or should do something to promote this job's readiness.
-     */
-    public void evaluateStateLocked(JobStatus jobStatus) {
-    }
-
-    /**
-     * Called when something with the UID has changed. The controller should re-evaluate any
-     * internal state tracking dependent on this UID.
-     */
-    public void reevaluateStateLocked(int uid) {
-    }
-
     public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate);
     public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 04d5795..26f3caf 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -30,6 +30,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -68,7 +69,7 @@
 
         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
-        mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(mContext);
+        mChainedAttributionEnabled = mService.isChainedAttributionEnabled();
     }
 
     /**
@@ -110,11 +111,24 @@
                 it.next();
             }
             it.add(job);
+
             job.setTrackingController(JobStatus.TRACKING_TIME);
-            maybeUpdateAlarmsLocked(
-                    job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
-                    job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
-                    deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
+            WorkSource ws = deriveWorkSource(job.getSourceUid(), job.getSourcePackageName());
+            final long deadlineExpiredElapsed =
+                    job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE;
+            final long delayExpiredElapsed =
+                    job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE;
+            if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) {
+                if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+                    maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws);
+                }
+                if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
+                    maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws);
+                }
+            } else {
+                maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws);
+                maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws);
+            }
         }
     }
 
@@ -133,6 +147,34 @@
         }
     }
 
+    @Override
+    public void onConstantsUpdatedLocked() {
+        checkExpiredDelaysAndResetAlarm();
+        checkExpiredDeadlinesAndResetAlarm();
+    }
+
+    @Override
+    public void evaluateStateLocked(JobStatus job) {
+        if (!mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) {
+            return;
+        }
+
+        if (job.hasTimingDelayConstraint()
+                && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
+            checkExpiredDelaysAndResetAlarm();
+        }
+        if (job.hasDeadlineConstraint()
+                && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
+            checkExpiredDeadlinesAndResetAlarm();
+        }
+    }
+
+    @Override
+    public void reevaluateStateLocked(int uid) {
+        checkExpiredDelaysAndResetAlarm();
+        checkExpiredDeadlinesAndResetAlarm();
+    }
+
     /**
      * Determines whether this controller can stop tracking the given job.
      * The controller is no longer interested in a job once its time constraint is satisfied, and
@@ -156,14 +198,15 @@
      * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
      * if so, removing them from this list, and updating the alarm for the next expiry time.
      */
-    private void checkExpiredDeadlinesAndResetAlarm() {
+    @VisibleForTesting
+    void checkExpiredDeadlinesAndResetAlarm() {
         synchronized (mLock) {
             long nextExpiryTime = Long.MAX_VALUE;
             int nextExpiryUid = 0;
             String nextExpiryPackageName = null;
             final long nowElapsedMillis = sElapsedRealtimeClock.millis();
 
-            Iterator<JobStatus> it = mTrackedJobs.iterator();
+            ListIterator<JobStatus> it = mTrackedJobs.listIterator();
             while (it.hasNext()) {
                 JobStatus job = it.next();
                 if (!job.hasDeadlineConstraint()) {
@@ -171,9 +214,22 @@
                 }
 
                 if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
-                    mStateChangedListener.onRunJobNow(job);
+                    if (job.isReady()) {
+                        // If the job still isn't ready, there's no point trying to rush the
+                        // Scheduler.
+                        mStateChangedListener.onRunJobNow(job);
+                    }
                     it.remove();
                 } else {  // Sorted by expiry time, so take the next one and stop.
+                    if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS
+                            && !wouldBeReadyWithConstraintLocked(
+                            job, JobStatus.CONSTRAINT_DEADLINE)) {
+                        if (DEBUG) {
+                            Slog.i(TAG,
+                                    "Skipping " + job + " because deadline won't make it ready.");
+                        }
+                        continue;
+                    }
                     nextExpiryTime = job.getLatestRunTimeElapsed();
                     nextExpiryUid = job.getSourceUid();
                     nextExpiryPackageName = job.getSourcePackageName();
@@ -202,7 +258,8 @@
      * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
      * tracked jobs and marks them as ready as appropriate.
      */
-    private void checkExpiredDelaysAndResetAlarm() {
+    @VisibleForTesting
+    void checkExpiredDelaysAndResetAlarm() {
         synchronized (mLock) {
             final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             long nextDelayTime = Long.MAX_VALUE;
@@ -223,6 +280,15 @@
                         ready = true;
                     }
                 } else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
+                    if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS
+                            && !wouldBeReadyWithConstraintLocked(
+                            job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+                        if (DEBUG) {
+                            Slog.i(TAG,
+                                    "Skipping " + job + " because delay won't make it ready.");
+                        }
+                        continue;
+                    }
                     // If this job still doesn't have its delay constraint satisfied,
                     // then see if it is the next upcoming delay time for the alarm.
                     final long jobDelayTime = job.getEarliestRunTime();
@@ -262,11 +328,13 @@
         return false;
     }
 
-    private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
-            WorkSource ws) {
+    private void maybeUpdateDelayAlarmLocked(long delayExpiredElapsed, WorkSource ws) {
         if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
             setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
         }
+    }
+
+    private void maybeUpdateDeadlineAlarmLocked(long deadlineExpiredElapsed, WorkSource ws) {
         if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
             setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
         }
@@ -297,11 +365,7 @@
     }
 
     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
-        final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
-        if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
-            return earliestWakeupTimeElapsed;
-        }
-        return proposedAlarmTimeElapsedMillis;
+        return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
     }
 
     private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
index b211948..3903f2a 100644
--- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
@@ -23,7 +23,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -65,6 +64,10 @@
     private static final int APN_IPV6 = 2;
     private static final int APN_IPV4V6 = 3;
 
+    // these must match the NetworkCapability enum flags in IAGnssRil.hal
+    private static final int AGNSS_NET_CAPABILITY_NOT_METERED = 1 << 0;
+    private static final int AGNSS_NET_CAPABILITY_NOT_ROAMING = 1 << 1;
+
     // Default time limit in milliseconds for the ConnectivityManager to find a suitable
     // network with SUPL connectivity or report an error.
     private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 10 * 1000;
@@ -95,23 +98,42 @@
      * Network attributes needed when updating HAL about network connectivity status changes.
      */
     private static class NetworkAttributes {
-        NetworkCapabilities mCapabilities;
-        String mApn;
-        int mType = ConnectivityManager.TYPE_NONE;
+        private NetworkCapabilities mCapabilities;
+        private String mApn;
+        private int mType = ConnectivityManager.TYPE_NONE;
 
         /**
          * Returns true if the capabilities that we pass on to HAL change between {@curCapabilities}
          * and {@code newCapabilities}.
          */
-        static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
+        private static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
                 NetworkCapabilities newCapabilities) {
             if (curCapabilities == null || newCapabilities == null) {
                 return true;
             }
 
-            return curCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
-                    != newCapabilities.hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+            // Monitor for roaming and metered capability changes.
+            return hasCapabilityChanged(curCapabilities, newCapabilities,
+                    NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                    || hasCapabilityChanged(curCapabilities, newCapabilities,
+                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        }
+
+        private static boolean hasCapabilityChanged(NetworkCapabilities curCapabilities,
+                NetworkCapabilities newCapabilities, int capability) {
+            return curCapabilities.hasCapability(capability)
+                    != newCapabilities.hasCapability(capability);
+        }
+
+        private static short getCapabilityFlags(NetworkCapabilities capabilities) {
+            short capabilityFlags = 0;
+            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
+                capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_ROAMING;
+            }
+            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
+                capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_METERED;
+            }
+            return capabilityFlags;
         }
     }
 
@@ -328,36 +350,31 @@
         boolean networkAvailable = isConnected && TelephonyManager.getDefault().getDataEnabled();
         NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
                 capabilities);
-        String apnName = networkAttributes.mApn;
+        String apn = networkAttributes.mApn;
         int type = networkAttributes.mType;
         // When isConnected is false, capabilities argument is null. So, use last received
         // capabilities.
         capabilities = networkAttributes.mCapabilities;
-        boolean isRoaming = !capabilities.hasTransport(
-                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-
         Log.i(TAG, String.format(
                 "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
-                        + ", availableNetworkCount: %d",
+                        + ", apn: %s, availableNetworkCount: %d",
                 agpsDataConnStateAsString(),
                 isConnected,
                 network,
                 capabilities,
+                apn,
                 mAvailableNetworkAttributes.size()));
 
         if (native_is_agps_ril_supported()) {
-            String defaultApn = getSelectedApn();
-            if (defaultApn == null) {
-                defaultApn = "dummy-apn";
-            }
-
             native_update_network_state(
                     isConnected,
                     type,
-                    isRoaming,
+                    !capabilities.hasTransport(
+                            NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
                     networkAvailable,
-                    apnName,
-                    defaultApn);
+                    apn != null ? apn : "",
+                    network.getNetworkHandle(),
+                    NetworkAttributes.getCapabilityFlags(capabilities));
         } else if (DEBUG) {
             Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not  supported");
         }
@@ -398,9 +415,9 @@
         // TODO(b/119278134): The synchronous method ConnectivityManager.getNetworkInfo() must
         // not be called inside the asynchronous ConnectivityManager.NetworkCallback methods.
         NetworkInfo info = mConnMgr.getNetworkInfo(network);
-        String apnName = null;
+        String apn = null;
         if (info != null) {
-            apnName = info.getExtraInfo();
+            apn = info.getExtraInfo();
         }
 
         if (DEBUG) {
@@ -413,21 +430,21 @@
         }
 
         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
-            if (apnName == null) {
+            if (apn == null) {
                 // assign a dummy value in the case of C2K as otherwise we will have a runtime
                 // exception in the following call to native_agps_data_conn_open
-                apnName = "dummy-apn";
+                apn = "dummy-apn";
             }
-            int apnIpType = getApnIpType(apnName);
+            int apnIpType = getApnIpType(apn);
             setRouting();
             if (DEBUG) {
                 String message = String.format(
                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
-                        apnName,
+                        apn,
                         apnIpType);
                 Log.d(TAG, message);
             }
-            native_agps_data_conn_open(apnName, apnIpType);
+            native_agps_data_conn_open(apn, apnIpType);
             mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
         }
     }
@@ -596,26 +613,6 @@
         return APN_INVALID;
     }
 
-    private String getSelectedApn() {
-        Uri uri = Uri.parse("content://telephony/carriers/preferapn");
-        try (Cursor cursor = mContext.getContentResolver().query(
-                uri,
-                new String[]{"apn"},
-                null /* selection */,
-                null /* selectionArgs */,
-                Carriers.DEFAULT_SORT_ORDER)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getString(0);
-            } else {
-                Log.e(TAG, "No APN found to select.");
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error encountered on selecting the APN.", e);
-        }
-
-        return null;
-    }
-
     // AGPS support
     private native void native_agps_data_conn_open(String apn, int apnIpType);
 
@@ -626,6 +623,6 @@
     // AGPS ril support
     private static native boolean native_is_agps_ril_supported();
 
-    private native void native_update_network_state(boolean connected, int type,
-            boolean roaming, boolean available, String extraInfo, String defaultAPN);
-}
+    private native void native_update_network_state(boolean connected, int type, boolean roaming,
+            boolean available, String apn, long networkHandle, short capabilities);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index adfa8d5..ea8c792 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -112,6 +112,7 @@
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.wm.WindowManagerInternal;
 
 import libcore.util.HexEncoding;
 
@@ -1870,6 +1871,7 @@
             DevicePolicyManager dpm = (DevicePolicyManager)
                     mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
             dpm.reportPasswordChanged(userId);
+            LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
         });
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index c938f5e..c1f3468 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -20,10 +20,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
-import android.media.MediaDescription;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -36,7 +36,6 @@
 import android.media.session.MediaSession;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
-import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -94,8 +93,9 @@
     private PendingIntent mLaunchIntent;
 
     // TransportPerformer fields
-
     private Bundle mExtras;
+    // Note: Avoid unparceling the bundle inside MediaMetadata since unparceling in system process
+    // may result in throwing an exception.
     private MediaMetadata mMetadata;
     private PlaybackState mPlaybackState;
     private ParceledListSlice mQueue;
@@ -117,6 +117,9 @@
     private boolean mIsActive = false;
     private boolean mDestroyed = false;
 
+    private long mDuration;
+    private String mMetadataDescription;
+
     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
             ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
         mOwnerPid = ownerPid;
@@ -451,7 +454,7 @@
         pw.println(indent + "audioAttrs=" + mAudioAttrs);
         pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
                 + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
-        pw.println(indent + "metadata:" + getShortMetadataString());
+        pw.println(indent + "metadata: " + mMetadataDescription);
         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
                 + (mQueue == null ? 0 : mQueue.getList().size()));
     }
@@ -494,13 +497,6 @@
         });
     }
 
-    private String getShortMetadataString() {
-        int fields = mMetadata == null ? 0 : mMetadata.size();
-        MediaDescription description = mMetadata == null ? null : mMetadata
-                .getDescription();
-        return "size=" + fields + ", description=" + description;
-    }
-
     private void logCallbackException(
             String msg, ISessionControllerCallbackHolder holder, Exception e) {
         Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
@@ -670,12 +666,10 @@
 
     private PlaybackState getStateWithUpdatedPosition() {
         PlaybackState state;
-        long duration = -1;
+        long duration;
         synchronized (mLock) {
             state = mPlaybackState;
-            if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
-                duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            }
+            duration = mDuration;
         }
         PlaybackState result = null;
         if (state != null) {
@@ -793,7 +787,7 @@
         }
 
         @Override
-        public void setMetadata(MediaMetadata metadata) {
+        public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
             synchronized (mLock) {
                 MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
                         .build();
@@ -804,6 +798,8 @@
                     temp.size();
                 }
                 mMetadata = temp;
+                mDuration = duration;
+                mMetadataDescription = metadataDescription;
             }
             mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc364c0..bad259b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -47,17 +47,12 @@
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.UserHandle.USER_NULL;
 import static android.os.UserHandle.USER_SYSTEM;
-import static android.service.notification.NotificationListenerService
-        .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService
-        .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService
-        .NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
-import static android.service.notification.NotificationListenerService
-        .NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
-import static android.service.notification.NotificationListenerService
-        .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -65,8 +60,7 @@
 import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService
-        .REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -583,7 +577,6 @@
     private void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized (mPolicyFile) {
-
             InputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
@@ -3138,7 +3131,11 @@
                 throws RemoteException {
             Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
             Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
-            Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
+            if (automaticZenRule.getOwner() == null
+                    && automaticZenRule.getConfigurationActivity() == null) {
+                throw new NullPointerException(
+                        "Rule must have a conditionproviderservice and/or configuration activity");
+            }
             Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
@@ -3151,7 +3148,11 @@
                 throws RemoteException {
             Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
             Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
-            Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
+            if (automaticZenRule.getOwner() == null
+                    && automaticZenRule.getConfigurationActivity() == null) {
+                throw new NullPointerException(
+                        "Rule must have a conditionproviderservice and/or configuration activity");
+            }
             Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
             enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
 
@@ -3185,6 +3186,16 @@
         }
 
         @Override
+        public void setAutomaticZenRuleState(String id, Condition condition) {
+            Preconditions.checkNotNull(id, "id is null");
+            Preconditions.checkNotNull(condition, "Condition is null");
+
+            enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
+
+            mZenModeHelper.setAutomaticZenRuleState(id, condition);
+        }
+
+        @Override
         public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
             enforcePolicyAccess(pkg, "setInterruptionFilter");
             final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
@@ -3357,14 +3368,12 @@
                 Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                 return null;
             }
-            synchronized(mPolicyFile) {
-                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                try {
-                    writePolicyXml(baos, true /*forBackup*/);
-                    return baos.toByteArray();
-                } catch (IOException e) {
-                    Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
-                }
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                writePolicyXml(baos, true /*forBackup*/);
+                return baos.toByteArray();
+            } catch (IOException e) {
+                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
             }
             return null;
         }
@@ -3383,14 +3392,12 @@
                 Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                 return;
             }
-            synchronized(mPolicyFile) {
-                final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
-                try {
-                    readPolicyXml(bais, true /*forRestore*/);
-                    handleSavePolicyFile();
-                } catch (NumberFormatException | XmlPullParserException | IOException e) {
-                    Slog.w(TAG, "applyRestore: error reading payload", e);
-                }
+            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+            try {
+                readPolicyXml(bais, true /*forRestore*/);
+                handleSavePolicyFile();
+            } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                Slog.w(TAG, "applyRestore: error reading payload", e);
             }
         }
 
@@ -6700,7 +6707,7 @@
         Bundle hidden = new Bundle();
         Bundle systemGeneratedSmartActions = new Bundle();
         Bundle smartReplies = new Bundle();
-        Bundle audiblyAlerted = new Bundle();
+        Bundle lastAudiblyAlerted = new Bundle();
         Bundle noisy = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
@@ -6732,7 +6739,7 @@
             systemGeneratedSmartActions.putParcelableArrayList(key,
                     record.getSystemGeneratedSmartActions());
             smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
-            audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
+            lastAudiblyAlerted.putLong(key, record.getLastAudiblyAlertedMs());
             noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
         }
         final int M = keys.size();
@@ -6745,7 +6752,7 @@
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
                 channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
-                systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
+                systemGeneratedSmartActions, smartReplies, lastAudiblyAlerted, noisy);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 50810cc..39451d4 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -132,6 +132,9 @@
     // user
     private long mInterruptionTimeMs;
 
+    // The most recent time the notification made noise or buzzed the device, or -1 if it did not.
+    private long mLastAudiblyAlertedMs;
+
     // Is this record an update of an old record?
     public boolean isUpdate;
     private int mPackagePriority;
@@ -172,7 +175,6 @@
     private final NotificationStats mStats;
     private int mUserSentiment;
     private boolean mIsInterruptive;
-    private boolean mAudiblyAlerted;
     private boolean mTextChanged;
     private boolean mRecordedInterruption;
     private int mNumberOfSmartRepliesAdded;
@@ -1051,7 +1053,7 @@
     }
 
     public void setAudiblyAlerted(boolean audiblyAlerted) {
-        mAudiblyAlerted = audiblyAlerted;
+        mLastAudiblyAlertedMs = audiblyAlerted ? System.currentTimeMillis() : -1;
     }
 
     public void setTextChanged(boolean textChanged) {
@@ -1070,9 +1072,9 @@
         return mIsInterruptive;
     }
 
-    /** Returns true if the notification audibly alerted the user. */
-    public boolean getAudiblyAlerted() {
-        return mAudiblyAlerted;
+    /** Returns the time the notification audibly alerted the user. */
+    public long getLastAudiblyAlertedMs() {
+        return mLastAudiblyAlertedMs;
     }
 
     protected void setPeopleOverride(ArrayList<String> people) {
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index b080a73..571f799 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -29,8 +29,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 
+/**
+ * Helper class for managing active rules from
+ * {@link android.service.notification.ConditionProviderService CPSes}.
+ */
 public class ZenModeConditions implements ConditionProviders.Callback {
     private static final String TAG = ZenModeHelper.TAG;
     private static final boolean DEBUG = ZenModeHelper.DEBUG;
@@ -41,8 +44,6 @@
     @VisibleForTesting
     protected final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
 
-    private boolean mFirstEvaluation = true;
-
     public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
         mHelper = helper;
         mConditionProviders = conditionProviders;
@@ -73,8 +74,10 @@
         final ArraySet<Uri> current = new ArraySet<>();
         evaluateRule(config.manualRule, current, null, processSubscriptions);
         for (ZenRule automaticRule : config.automaticRules.values()) {
-            evaluateRule(automaticRule, current, trigger, processSubscriptions);
-            updateSnoozing(automaticRule);
+            if (automaticRule.component != null) {
+                evaluateRule(automaticRule, current, trigger, processSubscriptions);
+                updateSnoozing(automaticRule);
+            }
         }
 
         synchronized (mSubscriptions) {
@@ -90,7 +93,6 @@
                 }
             }
         }
-        mFirstEvaluation = false;
     }
 
     @Override
@@ -114,23 +116,14 @@
         if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
         ZenModeConfig config = mHelper.getConfig();
         if (config == null) return;
-        ComponentName trigger = null;
-        boolean updated = updateCondition(id, condition, config.manualRule);
-        for (ZenRule automaticRule : config.automaticRules.values()) {
-            updated |= updateCondition(id, condition, automaticRule);
-            updated |= updateSnoozing(automaticRule);
-            if (updated) {
-                trigger = automaticRule.component;
-            }
-        }
-        if (updated) {
-            mHelper.setConfig(config, trigger, "conditionChanged");
-        }
+        mHelper.setAutomaticZenRuleState(id, condition);
     }
 
+    // Only valid for CPS backed rules
     private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger,
             boolean processSubscriptions) {
         if (rule == null || rule.conditionId == null) return;
+        if (rule.configurationActivity != null) return;
         final Uri id = rule.conditionId;
         boolean isSystemCondition = false;
         for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
@@ -140,6 +133,7 @@
                 isSystemCondition = true;
             }
         }
+        // ensure that we have a record of the rule if it's backed by an currently alive CPS
         if (!isSystemCondition) {
             final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
             if (DEBUG) Log.d(TAG, "Ensure external rule exists: " + (cp != null) + " for " + id);
@@ -147,7 +141,8 @@
                 mConditionProviders.ensureRecordExists(rule.component, id, cp);
             }
         }
-        if (rule.component == null) {
+        // empty rule? disable and bail early
+        if (rule.component == null && rule.enabler == null) {
             Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
             rule.enabled = false;
             return;
@@ -155,6 +150,8 @@
         if (current != null) {
             current.add(id);
         }
+
+        // If the rule is bound by a CPS and the CPS is alive, tell them about the rule
         if (processSubscriptions && ((trigger != null && trigger.equals(rule.component))
                 || isSystemCondition)) {
             if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component);
@@ -167,40 +164,20 @@
                 if (DEBUG) Log.d(TAG, "zmc failed to subscribe");
             }
         }
-        if (rule.condition == null) {
+        // backfill the rule state from CPS backed components if it's missing
+        if (rule.component != null && rule.condition == null) {
             rule.condition = mConditionProviders.findCondition(rule.component, rule.conditionId);
             if (rule.condition != null && DEBUG) Log.d(TAG, "Found existing condition for: "
                     + rule.conditionId);
         }
     }
 
-    private boolean isAutomaticActive(ComponentName component) {
-        if (component == null) return false;
-        final ZenModeConfig config = mHelper.getConfig();
-        if (config == null) return false;
-        for (ZenRule rule : config.automaticRules.values()) {
-            if (component.equals(rule.component) && rule.isAutomaticActive()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private boolean updateSnoozing(ZenRule rule) {
-        if (rule != null && rule.snoozing && (mFirstEvaluation || !rule.isTrueOrUnknown())) {
+        if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
             rule.snoozing = false;
             if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
             return true;
         }
         return false;
     }
-
-    private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
-        if (id == null || rule == null || rule.conditionId == null) return false;
-        if (!rule.conditionId.equals(id)) return false;
-        if (Objects.equals(condition, rule.condition)) return false;
-        rule.condition = condition;
-        return true;
-    }
-
 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 94d276c..f01d343 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -26,6 +26,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -282,20 +284,25 @@
 
     public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
         if (!isSystemRule(automaticZenRule)) {
-            ServiceInfo owner = getServiceInfo(automaticZenRule.getOwner());
-            if (owner == null) {
-                throw new IllegalArgumentException("Owner is not a condition provider service");
+            PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
+            if (component == null) {
+                component = getActivityInfo(automaticZenRule.getConfigurationActivity());
             }
-
+            if (component == null) {
+                throw new IllegalArgumentException("Lacking enabled CPS or config activity");
+            }
             int ruleInstanceLimit = -1;
-            if (owner.metaData != null) {
-                ruleInstanceLimit = owner.metaData.getInt(
+            if (component.metaData != null) {
+                ruleInstanceLimit = component.metaData.getInt(
                         ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
             }
-            if (ruleInstanceLimit > 0 && ruleInstanceLimit
-                    < (getCurrentInstanceCount(automaticZenRule.getOwner()) + 1)) {
+            int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+                    + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+                    + 1;
+            if (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount) {
                 throw new IllegalArgumentException("Rule instance limit exceeded");
             }
+
         }
 
         ZenModeConfig newConfig;
@@ -377,11 +384,73 @@
         }
     }
 
-    public int getCurrentInstanceCount(ComponentName owner) {
+    public void setAutomaticZenRuleState(String id, Condition condition) {
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return;
+
+            newConfig = mConfig.copy();
+        }
+        setAutomaticZenRuleState(newConfig, newConfig.automaticRules.get(id), condition);
+    }
+
+    public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition) {
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return;
+            newConfig = mConfig.copy();
+        }
+
+        setAutomaticZenRuleState(newConfig,
+                findMatchingRule(newConfig, ruleDefinition, condition),
+                condition);
+    }
+
+    private void setAutomaticZenRuleState(ZenModeConfig config, ZenRule rule, Condition condition) {
+        if (rule == null) return;
+
+        rule.condition = condition;
+        updateSnoozing(rule);
+        setConfigLocked(config, rule.component, "conditionChanged");
+    }
+
+    private ZenRule findMatchingRule(ZenModeConfig config, Uri id, Condition condition) {
+        if (ruleMatches(id, condition, config.manualRule)) {
+            return config.manualRule;
+        } else {
+            for (ZenRule automaticRule : config.automaticRules.values()) {
+                if (ruleMatches(id, condition, automaticRule)) {
+                    return automaticRule;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean ruleMatches(Uri id, Condition condition, ZenRule rule) {
+        if (id == null || rule == null || rule.conditionId == null) return false;
+        if (!rule.conditionId.equals(id)) return false;
+        if (Objects.equals(condition, rule.condition)) return false;
+        return true;
+    }
+
+    private boolean updateSnoozing(ZenRule rule) {
+        if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+            rule.snoozing = false;
+            if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+            return true;
+        }
+        return false;
+    }
+
+    public int getCurrentInstanceCount(ComponentName cn) {
+        if (cn == null) {
+            return 0;
+        }
         int count = 0;
         synchronized (mConfig) {
             for (ZenRule rule : mConfig.automaticRules.values()) {
-                if (rule.component != null && rule.component.equals(owner)) {
+                if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
                     count++;
                 }
             }
@@ -401,7 +470,7 @@
             if (packages != null) {
                 final int packageCount = packages.length;
                 for (int i = 0; i < packageCount; i++) {
-                    if (packages[i].equals(rule.component.getPackageName())) {
+                    if (packages[i].equals(rule.pkg)) {
                         return true;
                     }
                 }
@@ -410,18 +479,6 @@
         }
     }
 
-    // Checks zen rule properties are the same (doesn't check creation time, name nor enabled)
-    // used to check if default rules were customized or not
-    private boolean ruleValuesEqual(AutomaticZenRule rule, ZenRule defaultRule) {
-        if (rule == null || defaultRule == null) {
-            return false;
-        }
-        return rule.getInterruptionFilter() ==
-                NotificationManager.zenModeToInterruptionFilter(defaultRule.zenMode)
-                && rule.getConditionId().equals(defaultRule.conditionId)
-                && rule.getOwner().equals(defaultRule.component);
-    }
-
     protected void updateDefaultZenRules() {
         updateDefaultAutomaticRuleNames();
         for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
@@ -443,7 +500,8 @@
     }
 
     private boolean isSystemRule(AutomaticZenRule rule) {
-        return ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
+        return rule.getOwner() != null
+                && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
     }
 
     private ServiceInfo getServiceInfo(ComponentName owner) {
@@ -465,11 +523,31 @@
         return null;
     }
 
+    private ActivityInfo getActivityInfo(ComponentName configActivity) {
+        Intent queryIntent = new Intent();
+        queryIntent.setComponent(configActivity);
+        List<ResolveInfo> installedComponents = mPm.queryIntentActivitiesAsUser(
+                queryIntent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA,
+                UserHandle.getCallingUserId());
+        if (installedComponents != null) {
+            for (int i = 0, count = installedComponents.size(); i < count; i++) {
+                ResolveInfo resolveInfo = installedComponents.get(i);
+                return resolveInfo.activityInfo;
+            }
+        }
+        return null;
+    }
+
     private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) {
         if (isNew) {
             rule.id = ZenModeConfig.newRuleId();
             rule.creationTime = System.currentTimeMillis();
             rule.component = automaticZenRule.getOwner();
+            rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+            rule.pkg = (rule.component != null)
+                    ? rule.component.getPackageName()
+                    : rule.configurationActivity.getPackageName();
         }
 
         if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -488,14 +566,10 @@
     }
 
     protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) {
-        if (rule.zenPolicy != null) {
-            return new AutomaticZenRule(rule.name, rule.component, rule.conditionId, rule.zenPolicy,
-                    rule.enabled, rule.creationTime);
-        } else {
-            return new AutomaticZenRule(rule.name, rule.component, rule.conditionId,
-                    NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
-                    rule.creationTime);
-        }
+        return new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity,
+                rule.conditionId, rule.zenPolicy,
+                NotificationManager.zenModeToInterruptionFilter(rule.zenMode),
+                rule.enabled, rule.creationTime);
     }
 
     public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) {
@@ -697,8 +771,9 @@
                     ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
                     if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
                         try {
-                            mPm.getPackageInfo(rule.component.getPackageName(),
-                                    PackageManager.MATCH_ANY_USER);
+                            if (rule.pkg != null) {
+                                mPm.getPackageInfo(rule.pkg, PackageManager.MATCH_ANY_USER);
+                            }
                         } catch (PackageManager.NameNotFoundException e) {
                             newConfig.automaticRules.removeAt(i);
                         }
@@ -753,11 +828,14 @@
                 if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
                 return true;
             }
-            // may modify config
+            // handle CPS backed conditions - danger! may modify config
             mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
+
             mConfigs.put(config.user, config);
             if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
             ZenLog.traceConfig(reason, mConfig, config);
+
+            // send some broadcasts
             final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
                     getNotificationPolicy(config));
             if (!config.equals(mConfig)) {
diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
new file mode 100644
index 0000000..886cfb2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
@@ -0,0 +1,178 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Process;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides data to back {@code ModuleInfo} related APIs in the package manager. The data is stored
+ * as an XML resource in a configurable "module metadata" package.
+ */
+@VisibleForTesting
+public class ModuleInfoProvider {
+    private static final String TAG = "PackageManager.ModuleInfoProvider";
+
+    /**
+     * The key in the package's application level metadata bundle that provides a resource reference
+     * to the module metadata.
+     */
+    private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA";
+
+
+    private final Context mContext;
+    private final IPackageManager mPackageManager;
+    private final Map<String, ModuleInfo> mModuleInfo;
+
+    // TODO: Move this to an earlier boot phase if anybody requires it then.
+    private volatile boolean mMetadataLoaded;
+
+    ModuleInfoProvider(Context context, IPackageManager packageManager) {
+        mContext = context;
+        mPackageManager = packageManager;
+        mModuleInfo = new ArrayMap<>();
+    }
+
+    @VisibleForTesting
+    public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) {
+        mContext = null;
+        mPackageManager = null;
+        mModuleInfo = new ArrayMap<>();
+        loadModuleMetadata(metadata, resources);
+    }
+
+    /** Called by the {@code PackageManager} when it has completed its boot sequence */
+    public void systemReady() {
+        final String packageName = mContext.getResources().getString(
+                R.string.config_defaultModuleMetadataProvider);
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.w(TAG, "No configured module metadata provider.");
+            return;
+        }
+
+        final Resources packageResources;
+        final PackageInfo pi;
+        try {
+            pi = mPackageManager.getPackageInfo(packageName,
+                PackageManager.GET_META_DATA, Process.SYSTEM_UID);
+
+            Context packageContext = mContext.createPackageContext(packageName, 0);
+            packageResources = packageContext.getResources();
+        } catch (RemoteException | NameNotFoundException e) {
+            Slog.w(TAG, "Unable to discover metadata package: " + packageName, e);
+            return;
+        }
+
+        XmlResourceParser parser = packageResources.getXml(
+                pi.applicationInfo.metaData.getInt(MODULE_METADATA_KEY));
+        loadModuleMetadata(parser, packageResources);
+    }
+
+    private void loadModuleMetadata(XmlResourceParser parser, Resources packageResources) {
+        try {
+            // The format for the module metadata is straightforward :
+            //
+            // The following attributes on <module> are currently defined :
+            // -- name : A resource reference to a User visible package name, maps to
+            //           ModuleInfo#getName
+            // -- packageName : The package name of the module, see ModuleInfo#getPackageName
+            // -- isHidden : Whether the module is hidden, see ModuleInfo#isHidden
+            //
+            // <module-metadata>
+            //   <module name="@string/resource" packageName="package_name" isHidden="false|true" />
+            //   <module .... />
+            // </module-metadata>
+
+            XmlUtils.beginDocument(parser, "module-metadata");
+            while (true) {
+                XmlUtils.nextElement(parser);
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                if (!"module".equals(parser.getName())) {
+                    Slog.w(TAG, "Unexpected metadata element: " + parser.getName());
+                    mModuleInfo.clear();
+                    break;
+                }
+
+                // TODO: The module name here is fetched using the resource configuration applied
+                // at the time of parsing this information. This is probably not the best approach
+                // to dealing with this as we'll now have to listen to all config changes and
+                // regenerate the data if required. Also, is this the right way to parse a resource
+                // reference out of an XML file ?
+                final String moduleName = packageResources.getString(
+                        Integer.parseInt(parser.getAttributeValue(null, "name").substring(1)));
+                final String modulePackageName = XmlUtils.readStringAttribute(parser,
+                        "packageName");
+                final boolean isHidden = XmlUtils.readBooleanAttribute(parser, "isHidden");
+
+                ModuleInfo mi = new ModuleInfo();
+                mi.setHidden(isHidden);
+                mi.setPackageName(modulePackageName);
+                mi.setName(moduleName);
+
+                mModuleInfo.put(modulePackageName, mi);
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Slog.w(TAG, "Error parsing module metadata", e);
+            mModuleInfo.clear();
+        } finally {
+            parser.close();
+            mMetadataLoaded = true;
+        }
+    }
+
+    List<ModuleInfo> getInstalledModules(int flags) {
+        if (!mMetadataLoaded) {
+            throw new IllegalStateException("Call to getInstalledModules before metadata loaded");
+        }
+
+        return new ArrayList<>(mModuleInfo.values());
+    }
+
+    ModuleInfo getModuleInfo(String packageName, int flags) {
+        if (!mMetadataLoaded) {
+            throw new IllegalStateException("Call to getModuleInfo before metadata loaded");
+        }
+
+        return mModuleInfo.get(packageName);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a95e730..e038f9b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -165,6 +165,7 @@
 
     // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
     //           shouldn't be needed at all.
+    // TODO(b/118865310): Implement staged sessions logic.
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
 
@@ -1130,7 +1131,7 @@
             mInstallHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    // TODO: remove this mock implementation.
+                    // TODO(b/118865310): remove this mock implementation.
                     if (session.isStaged()) {
                         // If the session is aborted, don't keep it in memory. Only store
                         // sessions successfully staged.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6cfb846..28fb01d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,12 +162,14 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageManager.ModuleInfoFlags;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.CheckPermissionDelegate;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
@@ -323,6 +325,7 @@
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -591,12 +594,6 @@
     public static final int REASON_LAST = REASON_SHARED;
 
     /**
-     * Version number for the package parser cache. Increment this whenever the format or
-     * extent of cached data changes. See {@code PackageParser#setCacheDir}.
-     */
-    private static final String PACKAGE_PARSER_CACHE_VERSION = "1";
-
-    /**
      * Whether the package parser cache is enabled.
      */
     private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
@@ -723,6 +720,8 @@
 
     private PackageManager mPackageManager;
 
+    private final ModuleInfoProvider mModuleInfoProvider;
+
     class PackageParserCallback implements PackageParser.Callback {
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -1064,9 +1063,6 @@
                         + verificationId + " packageName:" + packageName);
                 return;
             }
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                    "Updating IntentFilterVerificationInfo for package " + packageName
-                            +" verificationId:" + verificationId);
 
             synchronized (mPackages) {
                 if (verified) {
@@ -1084,19 +1080,47 @@
                     int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
                     boolean needUpdate = false;
 
-                    // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have
-                    // already been set by the User thru the Disambiguation dialog
+                    if (DEBUG_DOMAIN_VERIFICATION) {
+                        Slog.d(TAG,
+                                "Updating IntentFilterVerificationInfo for package " + packageName
+                                + " verificationId:" + verificationId
+                                + " verified=" + verified);
+                    }
+
+                    // In a success case, we promote from undefined or ASK to ALWAYS.  This
+                    // supports a flow where the app fails validation but then ships an updated
+                    // APK that passes, and therefore deserves to be in ALWAYS.
+                    //
+                    // If validation failed, the undefined state winds up in the basic ASK behavior,
+                    // but apps that previously passed and became ALWAYS are *demoted* out of
+                    // that state, since they would not deserve the ALWAYS behavior in case of a
+                    // clean install.
                     switch (userStatus) {
+                        case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                            if (!verified) {
+                                // updatedStatus is already UNDEFINED
+                                needUpdate = true;
+
+                                if (DEBUG_DOMAIN_VERIFICATION) {
+                                    Slog.d(TAG, "Formerly validated but now failing; demoting");
+                                }
+                            }
+                            break;
+
                         case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+                            // Stay in 'undefined' on verification failure
                             if (verified) {
                                 updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                            } else {
-                                updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
                             }
                             needUpdate = true;
+                            if (DEBUG_DOMAIN_VERIFICATION) {
+                                Slog.d(TAG, "Applying update; old=" + userStatus
+                                        + " new=" + updatedStatus);
+                            }
                             break;
 
                         case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+                            // Keep in 'ask' on failure
                             if (verified) {
                                 updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
                                 needUpdate = true;
@@ -1112,6 +1136,8 @@
                                 packageName, updatedStatus, userId);
                         scheduleWritePackageRestrictionsLocked(userId);
                     }
+                } else {
+                    Slog.i(TAG, "autoVerify ignored when installing for all users");
                 }
             }
         }
@@ -2302,7 +2328,7 @@
                 }
             }
 
-            mCacheDir = preparePackageParserCache(mIsUpgrade);
+            mCacheDir = preparePackageParserCache();
 
             // Set flag to monitor and not change apk file paths when
             // scanning install directories.
@@ -3008,6 +3034,8 @@
         } // synchronized (mPackages)
         } // synchronized (mInstallLock)
 
+        mModuleInfoProvider = new ModuleInfoProvider(mContext, this);
+
         // Now after opening every single application zip, make sure they
         // are all flushed.  Not really needed, but keeps things nice and
         // tidy.
@@ -3169,7 +3197,7 @@
         setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr());
     }
 
-    private static File preparePackageParserCache(boolean isUpgrade) {
+    private static @Nullable File preparePackageParserCache() {
         if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
             return null;
         }
@@ -3190,17 +3218,25 @@
             return null;
         }
 
-        // If this is a system upgrade scenario, delete the contents of the package cache dir.
-        // This also serves to "GC" unused entries when the package cache version changes (which
-        // can only happen during upgrades).
-        if (isUpgrade) {
-            FileUtils.deleteContents(cacheBaseDir);
+        // There are several items that need to be combined together to safely
+        // identify cached items. In particular, changing the value of certain
+        // feature flags should cause us to invalidate any caches.
+        final String cacheName = SystemProperties.digestOf(
+                "ro.build.fingerprint",
+                "persist.sys.isolated_storage");
+
+        // Reconcile cache directories, keeping only what we'd actually use.
+        for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
+            if (Objects.equals(cacheName, cacheDir.getName())) {
+                Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
+            } else {
+                Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
+                FileUtils.deleteContentsAndDir(cacheDir);
+            }
         }
 
-
-        // Return the versioned package cache directory. This is something like
-        // "/data/system/package_cache/1"
-        File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+        // Return the versioned package cache directory.
+        File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
 
         if (cacheDir == null) {
             // Something went wrong. Attempt to delete everything and return.
@@ -3226,7 +3262,7 @@
             File frameworkDir = new File(Environment.getRootDirectory(), "framework");
             if (cacheDir.lastModified() < frameworkDir.lastModified()) {
                 FileUtils.deleteContents(cacheBaseDir);
-                cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+                cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
             }
         }
 
@@ -4939,6 +4975,16 @@
     }
 
     @Override
+    public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) {
+        return mModuleInfoProvider.getModuleInfo(packageName, flags);
+    }
+
+    @Override
+    public List<ModuleInfo> getInstalledModules(int flags) {
+        return mModuleInfoProvider.getInstalledModules(flags);
+    }
+
+    @Override
     public String[] getSystemSharedLibraryNames() {
         // allow instant applications
         synchronized (mPackages) {
@@ -15269,7 +15315,7 @@
                         | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
                 deletePackageAction = mayDeletePackageLocked(res.removedInfo,
                         prepareResult.originalPs, prepareResult.disabledPs,
-                        prepareResult.childPackageSettings, deleteFlags, installArgs.user);
+                        prepareResult.childPackageSettings, deleteFlags, null /* all users */);
                 if (deletePackageAction == null) {
                     throw new ReconcileFailure(
                             PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
@@ -16708,6 +16754,7 @@
         int status = ivi.getStatus();
         switch (status) {
             case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
             case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
                 return true;
 
@@ -17356,28 +17403,22 @@
      * make sure this flag is set for partially installed apps. If not its meaningless to
      * delete a partially installed application.
      */
-    private void removePackageDataLIF(PackageSetting ps, int[] allUserHandles,
+    private void removePackageDataLIF(final PackageSetting deletedPs, int[] allUserHandles,
             PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
-        String packageName = ps.name;
-        if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
+        String packageName = deletedPs.name;
+        if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
         // Retrieve object to delete permissions for shared user later on
-        final PackageParser.Package deletedPkg;
-        final PackageSetting deletedPs;
-        // reader
-        synchronized (mPackages) {
-            deletedPkg = mPackages.get(packageName);
-            deletedPs = mSettings.mPackages.get(packageName);
-            if (outInfo != null) {
-                outInfo.removedPackage = packageName;
-                outInfo.installerPackageName = ps.installerPackageName;
-                outInfo.isStaticSharedLib = deletedPkg != null
-                        && deletedPkg.staticSharedLibName != null;
-                outInfo.populateUsers(deletedPs == null ? null
-                        : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs);
-            }
+        final PackageParser.Package deletedPkg = deletedPs.pkg;
+        if (outInfo != null) {
+            outInfo.removedPackage = packageName;
+            outInfo.installerPackageName = deletedPs.installerPackageName;
+            outInfo.isStaticSharedLib = deletedPkg != null
+                    && deletedPkg.staticSharedLibName != null;
+            outInfo.populateUsers(deletedPs == null ? null
+                    : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs);
         }
 
-        removePackageLI(ps.name, (flags & PackageManager.DELETE_CHATTY) != 0);
+        removePackageLI(deletedPs.name, (flags & PackageManager.DELETE_CHATTY) != 0);
 
         if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
             final PackageParser.Package resolvedPkg;
@@ -17386,8 +17427,8 @@
             } else {
                 // We don't have a parsed package when it lives on an ejected
                 // adopted storage device, so fake something together
-                resolvedPkg = new PackageParser.Package(ps.name);
-                resolvedPkg.setVolumeUuid(ps.volumeUuid);
+                resolvedPkg = new PackageParser.Package(deletedPs.name);
+                resolvedPkg.setVolumeUuid(deletedPs.volumeUuid);
             }
             destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL,
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
@@ -17447,10 +17488,10 @@
                         if (DEBUG_REMOVE) {
                             Slog.d(TAG, "    user " + userId + " => " + installed);
                         }
-                        if (installed != ps.getInstalled(userId)) {
+                        if (installed != deletedPs.getInstalled(userId)) {
                             installedStateChanged = true;
                         }
-                        ps.setInstalled(installed, userId);
+                        deletedPs.setInstalled(installed, userId);
                     }
                 }
             }
@@ -17460,7 +17501,7 @@
                 mSettings.writeLPr();
             }
             if (installedStateChanged) {
-                mSettings.writeKernelMappingLPr(ps);
+                mSettings.writeKernelMappingLPr(deletedPs);
             }
         }
         if (removedAppId != -1) {
@@ -17530,12 +17571,12 @@
     /*
      * Tries to delete system package.
      */
-    private void deleteSystemPackageLIF(DeletePackageAction action,
-            PackageParser.Package deletedPkg, PackageSetting deletedPs, int[] allUserHandles,
-            int flags, PackageRemovedInfo outInfo, boolean writeSettings)
+    private void deleteSystemPackageLIF(DeletePackageAction action, PackageSetting deletedPs,
+            int[] allUserHandles, int flags, PackageRemovedInfo outInfo, boolean writeSettings)
             throws SystemDeleteException {
         final boolean applyUserRestrictions
                 = (allUserHandles != null) && (outInfo.origUsers != null);
+        final PackageParser.Package deletedPkg = deletedPs.pkg;
         // Confirm if the system package has been updated
         // An updated system app can be deleted. This will also have to restore
         // the system pkg from system partition
@@ -17895,16 +17936,18 @@
             PackageSetting[] children = mSettings.getChildSettingsLPr(ps);
             action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user);
         }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
         if (null == action) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: action was null");
             return false;
         }
 
-        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
 
         try {
             executeDeletePackageLIF(action, packageName, deleteCodeAndResources,
                     allUserHandles, writeSettings, replacingPackage);
         } catch (SystemDeleteException e) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: system deletion failure", e);
             return false;
         }
         return true;
@@ -17951,42 +17994,48 @@
             unsuspendForSuspendingPackage(packageName, userId);
         }
 
-
         if (!systemApp || action.mayDeleteUnupdatedSystemApp) {
             // The caller is asking that the package only be deleted for a single
             // user.  To do this, we just mark its uninstalled state and delete
             // its data. If this is a system app, we only allow this to happen if
             // they have set the special DELETE_SYSTEM_APP which requests different
             // semantics than normal for uninstalling system apps.
-            markPackageUninstalledForUserLPw(ps, user);
-
-            if (!systemApp) {
-                // Do not uninstall the APK if an app should be cached
-                boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
-                if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
-                    // Other user still have this package installed, so all
+            synchronized (mPackages) {
+                markPackageUninstalledForUserLPw(ps, user);
+                if (!systemApp) {
+                    // Do not uninstall the APK if an app should be cached
+                    boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
+                    if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
+                        // Other users still have this package installed, so all
+                        // we need to do is clear this user's data and save that
+                        // it is uninstalled.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+                        clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+                        scheduleWritePackageRestrictionsLocked(user);
+                        return;
+                    } else {
+                        // We need to set it back to 'installed' so the uninstall
+                        // broadcasts will be sent correctly.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
+                        if (userId != UserHandle.USER_ALL) {
+                            ps.setInstalled(true, userId);
+                        } else {
+                            for (int origUserId : outInfo.origUsers) {
+                                ps.setInstalled(true, origUserId);
+                            }
+                        }
+                        mSettings.writeKernelMappingLPr(ps);
+                    }
+                } else {
+                    // This is a system app, so we assume that the
+                    // other users still have this package installed, so all
                     // we need to do is clear this user's data and save that
                     // it is uninstalled.
-                    if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
-                    clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
+                    if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
+                    clearPackageStateForUserLIF(ps, userId, outInfo, flags);
                     scheduleWritePackageRestrictionsLocked(user);
                     return;
-                } else {
-                    // We need to set it back to 'installed' so the uninstall
-                    // broadcasts will be sent correctly.
-                    if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
-                    ps.setInstalled(true, user.getIdentifier());
-                    mSettings.writeKernelMappingLPr(ps);
                 }
-            } else {
-                // This is a system app, so we assume that the
-                // other users still have this package installed, so all
-                // we need to do is clear this user's data and save that
-                // it is uninstalled.
-                if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
-                clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
-                scheduleWritePackageRestrictionsLocked(user);
-                return;
             }
         }
 
@@ -18015,8 +18064,7 @@
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.name);
             // When an updated system application is deleted we delete the existing resources
             // as well and fall back to existing code in system partition
-            deleteSystemPackageLIF(
-                    action, ps.pkg, ps, allUserHandles, flags, outInfo, writeSettings);
+            deleteSystemPackageLIF(action, ps, allUserHandles, flags, outInfo, writeSettings);
         } else {
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name);
             deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
@@ -20210,6 +20258,8 @@
                 }
             }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
         }
+
+        mModuleInfoProvider.systemReady();
     }
 
     public void waitForAppDataPrepared() {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d1d5818..4f20590 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2691,10 +2691,12 @@
                 if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
                     // If we're not adding a guest/demo user or a managed profile and the limit has
                     // been reached, cannot add a user.
+                    Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
                     return null;
                 }
                 // If we're adding a guest and there already exists one, bail.
                 if (isGuest && findCurrentGuestUser() != null) {
+                    Log.e(LOG_TAG, "Cannot add guest user. Guest user already exists.");
                     return null;
                 }
                 // In legacy mode, restricted profile's parent can only be the owner user
@@ -2937,13 +2939,26 @@
             final UserData userData;
             int currentUser = ActivityManager.getCurrentUser();
             if (currentUser == userHandle) {
-                Log.w(LOG_TAG, "Current user cannot be removed");
+                Log.w(LOG_TAG, "Current user cannot be removed.");
                 return false;
             }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
                     userData = mUsers.get(userHandle);
-                    if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
+                    if (userHandle == UserHandle.USER_SYSTEM) {
+                        Log.e(LOG_TAG, "System user cannot be removed.");
+                        return false;
+                    }
+
+                    if (userData == null) {
+                        Log.e(LOG_TAG, String.format(
+                                "Cannot remove user %d, invalid user id provided.", userHandle));
+                        return false;
+                    }
+
+                    if (mRemovingUserIds.get(userHandle)) {
+                        Log.e(LOG_TAG, String.format(
+                                "User %d is already scheduled for removal.", userHandle));
                         return false;
                     }
 
@@ -2962,7 +2977,7 @@
             try {
                 mAppOpsService.removeUser(userHandle);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
+                Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
 
             if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -2986,6 +3001,7 @@
                             }
                         });
             } catch (RemoteException e) {
+                Log.w(LOG_TAG, "Failed to stop user during removal.", e);
                 return false;
             }
             return res == ActivityManager.USER_OP_SUCCESS;
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index f74aa1d..6d4bc82 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -62,6 +62,13 @@
     private static final String PACKAGE_SEPARATOR = ",";
 
     /**
+     * Limit on how many files we store for a single owner, to avoid one app causing
+     * unbounded memory consumption.
+     */
+    @VisibleForTesting
+    static final int MAX_FILES_PER_OWNER = 100;
+
+    /**
      * Regular expression to match the expected format of an input line describing one file.
      * <p>Example: {@code D:10:package.name1,package.name2:/escaped/path}
      * <p>The capturing groups are the file type, user ID, loading packages and escaped file path
@@ -515,6 +522,9 @@
         private boolean add(String path, char fileType, int userId, String loadingPackage) {
             DynamicCodeFile fileInfo = mFileUsageMap.get(path);
             if (fileInfo == null) {
+                if (mFileUsageMap.size() >= MAX_FILES_PER_OWNER) {
+                    return false;
+                }
                 fileInfo = new DynamicCodeFile(fileType, userId, loadingPackage);
                 mFileUsageMap.put(path, fileInfo);
                 return true;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 31f5ce4..b58c811 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -799,10 +799,6 @@
                     continue;
                 }
 
-                if (bp.isRemoved()) {
-                    continue;
-                }
-
                 // Limit ephemeral apps to ephemeral allowed permissions.
                 if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
                     if (DEBUG_PERMISSIONS) {
@@ -951,7 +947,8 @@
                                     // how to disable the API to simulate revocation as legacy
                                     // apps don't expect to run with revoked permissions.
                                     if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
-                                        if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                                        if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+                                                && !bp.isRemoved()) {
                                             flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
                                             // We changed the flags, hence have to write.
                                             updatedUserIds = ArrayUtils.appendInt(
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3ba1155..f370edf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -599,6 +599,9 @@
 
     private boolean mAodShowing;
 
+    private boolean mPerDisplayFocusEnabled = false;
+    private int mTopFocusedDisplayId = INVALID_DISPLAY;
+
     private static final int MSG_ENABLE_POINTER_LOCATION = 1;
     private static final int MSG_DISABLE_POINTER_LOCATION = 2;
     private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
@@ -1811,6 +1814,9 @@
         mHandleVolumeKeysInWM = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_handleVolumeKeysInWindowManager);
 
+        mPerDisplayFocusEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
         readConfigurationDependentBehaviors();
 
         mAccessibilityManager = (AccessibilityManager) context.getSystemService(
@@ -2542,6 +2548,23 @@
     /** {@inheritDoc} */
     @Override
     public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+        final long result = interceptKeyBeforeDispatchingInner(win, event, policyFlags);
+        final int eventDisplayId = event.getDisplayId();
+        if (result == 0 && !mPerDisplayFocusEnabled
+                && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
+            // Someone tries to send a key event to a display which doesn't have a focused window.
+            // We drop the event here, or it will cause ANR.
+            // TODO (b/121057974): The user may be confused about why the key doesn't work, so we
+            // may need to deal with this problem.
+            Slog.i(TAG, "Dropping this event targeting display #" + eventDisplayId
+                    + " because the focus is on display #" + mTopFocusedDisplayId);
+            return -1;
+        }
+        return result;
+    }
+
+    private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event,
+            int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
@@ -3123,6 +3146,11 @@
     }
 
     @Override
+    public void setTopFocusedDisplay(int displayId) {
+        mTopFocusedDisplayId = displayId;
+    }
+
+    @Override
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3d474e3..3da325c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -1034,6 +1034,13 @@
     public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
 
     /**
+     * Called when the top focused display is changed.
+     *
+     * @param displayId The ID of the top focused display.
+     */
+    void setTopFocusedDisplay(int displayId);
+
+    /**
      * Apply the keyguard policy to a specific window.
      *
      * @param win The window to apply the keyguard policy.
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 35013de..f37ca12 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -503,6 +503,18 @@
             return userState.removeRoleHolder(roleName, packageName);
         }
 
+        @Override
+        public List<String> getHeldRolesFromController(@NonNull String packageName) {
+            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+            getContext().enforceCallingOrSelfPermission(
+                    RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+                    "getRolesHeldFromController");
+
+            int userId = UserHandle.getCallingUserId();
+            RoleUserState userState = getOrCreateUserState(userId);
+            return userState.getHeldRoles(packageName);
+        }
+
         @CheckResult
         private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
             return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
diff --git a/services/core/java/com/android/server/role/RoleManagerShellCommand.java b/services/core/java/com/android/server/role/RoleManagerShellCommand.java
index 336b311..b245e98 100644
--- a/services/core/java/com/android/server/role/RoleManagerShellCommand.java
+++ b/services/core/java/com/android/server/role/RoleManagerShellCommand.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.util.Log;
 
 import java.io.PrintWriter;
 import java.util.concurrent.CompletableFuture;
@@ -47,7 +48,8 @@
                 mResult.get(5, TimeUnit.SECONDS);
                 return 0;
             } catch (Exception e) {
-                getErrPrintWriter().println("Error: " + e.toString());
+                getErrPrintWriter().println("Error: see logcat for details.\n"
+                        + Log.getStackTraceString(e));
                 return -1;
             }
         }
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index d55e261..630a39c 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -47,6 +47,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -315,6 +316,21 @@
     }
 
     /**
+     * @see android.app.role.RoleManager#getHeldRolesFromController
+     */
+    @NonNull
+    public List<String> getHeldRoles(@NonNull String packageName) {
+        ArrayList<String> result = new ArrayList<>();
+        int size = mRoles.size();
+        for (int i = 0; i < size; i++) {
+            if (mRoles.valueAt(i).contains(packageName)) {
+                result.add(mRoles.keyAt(i));
+            }
+        }
+        return result;
+    }
+
+    /**
      * Schedule writing the state to file.
      */
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index f0ebb75..4ec8b87 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -74,6 +74,7 @@
 import android.os.StatsLogEventWrapper;
 import android.os.SynchronousResultReceiver;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -1140,7 +1141,8 @@
             e.writeLong(rssHighWaterMarkInBytes);
             pulledData.add(e);
         }
-        // TODO(b/119598534): Reset HWM counters here.
+        // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
+        SystemProperties.set("sys.rss_hwm_reset.on", "1");
     }
 
     private void pullBinderCallsStats(
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 6b0419e..2cab63a 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -42,6 +42,7 @@
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
@@ -214,6 +215,27 @@
             }
         }
     }
+    @Override
+    public void onTextClassifierEvent(
+            TextClassificationSessionId sessionId,
+            TextClassifierEvent event) throws RemoteException {
+        Preconditions.checkNotNull(event);
+        final String packageName = event.getEventContext() == null
+                ? null
+                : event.getEventContext().getPackageName();
+        validateInput(mContext, packageName);
+
+        synchronized (mLock) {
+            UserState userState = getCallingUserStateLocked();
+            if (userState.isBoundLocked()) {
+                userState.mService.onTextClassifierEvent(sessionId, event);
+            } else {
+                userState.mPendingRequests.add(new PendingRequest(
+                        () -> onTextClassifierEvent(sessionId, event),
+                        null /* onServiceFailure */, null /* binder */, this, userState));
+            }
+        }
+    }
 
     @Override
     public void onDetectLanguage(
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 3291a45..ced5935 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -19,12 +19,16 @@
 import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
 import android.app.admin.DevicePolicyManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
+import android.app.UserSwitchObserver;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -35,7 +39,9 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.DeadObjectException;
@@ -50,6 +56,7 @@
 import android.provider.Settings;
 import android.service.trust.TrustAgentService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -58,11 +65,13 @@
 import android.util.Xml;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -106,8 +115,11 @@
     private static final int MSG_STOP_USER = 12;
     private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
     private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14;
+    private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15;
 
     private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
+    private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
+    private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000;
 
     private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
@@ -132,6 +144,11 @@
     @GuardedBy("mUsersUnlockedByBiometric")
     private final SparseBooleanArray mUsersUnlockedByBiometric = new SparseBooleanArray();
 
+    private final ArrayMap<Integer, TrustTimeoutAlarmListener> mTrustTimeoutAlarmListenerForUser =
+            new ArrayMap<>();
+    private AlarmManager mAlarmManager;
+    private final SettingsObserver mSettingsObserver;
+
     private final StrongAuthTracker mStrongAuthTracker;
 
     private boolean mTrustAgentsCanRun = false;
@@ -144,6 +161,8 @@
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mLockPatternUtils = new LockPatternUtils(context);
         mStrongAuthTracker = new StrongAuthTracker(context);
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        mSettingsObserver = new SettingsObserver(mHandler);
     }
 
     @Override
@@ -170,7 +189,130 @@
         }
     }
 
-    // Agent management
+    // Extend unlock config and logic
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri TRUST_AGENTS_EXTEND_UNLOCK =
+                Settings.Secure.getUriFor(Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK);
+
+        private final Uri LOCK_SCREEN_WHEN_TRUST_LOST =
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST);
+
+        private final ContentResolver mContentResolver;
+        private boolean mTrustAgentsExtendUnlock;
+        private boolean mLockWhenTrustLost;
+
+        /**
+         * Creates a settings observer
+         *
+         * @param handler The handler to run {@link #onChange} on, or null if none.
+         */
+        SettingsObserver(Handler handler) {
+            super(handler);
+            mContentResolver = getContext().getContentResolver();
+            updateContentObserver();
+        }
+
+        void updateContentObserver() {
+            mContentResolver.unregisterContentObserver(this);
+            mContentResolver.registerContentObserver(TRUST_AGENTS_EXTEND_UNLOCK,
+                    false /* notifyForDescendents */,
+                    this /* observer */,
+                    mCurrentUser);
+            mContentResolver.registerContentObserver(LOCK_SCREEN_WHEN_TRUST_LOST,
+                    false /* notifyForDescendents */,
+                    this /* observer */,
+                    mCurrentUser);
+
+            // Update the value immediately
+            onChange(true /* selfChange */, TRUST_AGENTS_EXTEND_UNLOCK);
+            onChange(true /* selfChange */, LOCK_SCREEN_WHEN_TRUST_LOST);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (TRUST_AGENTS_EXTEND_UNLOCK.equals(uri)) {
+                mTrustAgentsExtendUnlock =
+                        Settings.Secure.getIntForUser(
+                                mContentResolver,
+                                Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK,
+                                0 /* default */,
+                                mCurrentUser) != 0;
+            } else if (LOCK_SCREEN_WHEN_TRUST_LOST.equals(uri)) {
+                mLockWhenTrustLost =
+                        Settings.Secure.getIntForUser(
+                                mContentResolver,
+                                Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
+                                0 /* default */,
+                                mCurrentUser) != 0;
+            }
+        }
+
+        boolean getTrustAgentsExtendUnlock() {
+            return mTrustAgentsExtendUnlock;
+        }
+
+        boolean getLockWhenTrustLost() {
+            return mLockWhenTrustLost;
+        }
+    }
+
+    private void maybeLockScreen(int userId) {
+        if (userId != mCurrentUser) {
+            return;
+        }
+
+        if (mSettingsObserver.getLockWhenTrustLost()) {
+            if (DEBUG) Slog.d(TAG, "Locking device because trust was lost");
+            try {
+                WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error locking screen when trust was lost");
+            }
+
+            // If active unlocking is not allowed, cancel any pending trust timeouts because the
+            // screen is already locked.
+            TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
+            if (alarm != null && mSettingsObserver.getTrustAgentsExtendUnlock()) {
+                mAlarmManager.cancel(alarm);
+                alarm.setQueued(false /* isQueued */);
+            }
+        }
+    }
+
+    private void scheduleTrustTimeout(int userId, boolean override) {
+        int shouldOverride = override ? 1 : 0;
+        if (override) {
+            shouldOverride = 1;
+        }
+        mHandler.obtainMessage(MSG_SCHEDULE_TRUST_TIMEOUT, userId, shouldOverride).sendToTarget();
+    }
+
+    private void handleScheduleTrustTimeout(int userId, int shouldOverride) {
+        long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
+        userId = mCurrentUser;
+        TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
+
+        // Cancel existing trust timeouts for this user if needed.
+        if (alarm != null) {
+            if (shouldOverride == 0 && alarm.isQueued()) {
+                if (DEBUG) Slog.d(TAG, "Found existing trust timeout alarm. Skipping.");
+                return;
+            }
+            mAlarmManager.cancel(alarm);
+        } else {
+            alarm = new TrustTimeoutAlarmListener(userId);
+            mTrustTimeoutAlarmListenerForUser.put(userId, alarm);
+        }
+
+        if (DEBUG) Slog.d(TAG, "\tSetting up trust timeout alarm");
+        alarm.setQueued(true /* isQueued */);
+        mAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
+                mHandler);
+    }
+
+   // Agent management
 
     private static final class AgentInfo {
         CharSequence label;
@@ -202,14 +344,36 @@
         }
     }
 
+
     public void updateTrust(int userId, int flags) {
+        updateTrust(userId, flags, false /* isFromUnlock */);
+    }
+
+    private void updateTrust(int userId, int flags, boolean isFromUnlock) {
         boolean managed = aggregateIsTrustManaged(userId);
         dispatchOnTrustManagedChanged(managed, userId);
         if (mStrongAuthTracker.isTrustAllowedForUser(userId)
                 && isTrustUsuallyManagedInternal(userId) != managed) {
             updateTrustUsuallyManaged(userId, managed);
         }
+
         boolean trusted = aggregateIsTrusted(userId);
+        IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+        boolean showingKeyguard = true;
+        try {
+            showingKeyguard = wm.isKeyguardLocked();
+        } catch (RemoteException e) {
+        }
+
+        if (mSettingsObserver.getTrustAgentsExtendUnlock()) {
+            trusted = trusted && (!showingKeyguard || isFromUnlock) && userId == mCurrentUser;
+            if (DEBUG) {
+                Slog.d(TAG, "Extend unlock setting trusted as " + Boolean.toString(trusted)
+                        + " && " + Boolean.toString(!showingKeyguard)
+                        + " && " + Boolean.toString(userId == mCurrentUser));
+            }
+        }
+
         boolean changed;
         synchronized (mUserIsTrusted) {
             changed = mUserIsTrusted.get(userId) != trusted;
@@ -218,6 +382,11 @@
         dispatchOnTrustChanged(trusted, userId, flags);
         if (changed) {
             refreshDeviceLockedForUser(userId);
+            if (!trusted) {
+                maybeLockScreen(userId);
+            } else {
+                scheduleTrustTimeout(userId, false /* override */);
+            }
         }
     }
 
@@ -704,6 +873,8 @@
     private void dispatchUnlockAttempt(boolean successful, int userId) {
         if (successful) {
             mStrongAuthTracker.allowTrustFromUnlock(userId);
+            // Allow the presence of trust on a successful unlock attempt to extend unlock.
+            updateTrust(userId, 0 /* flags */, true);
         }
 
         for (int i = 0; i < mActiveAgents.size(); i++) {
@@ -1033,8 +1204,11 @@
             synchronized(mUsersUnlockedByBiometric) {
                 mUsersUnlockedByBiometric.put(userId, true);
             }
+            // In extend unlock mode we need to refresh trust state here, which will call
+            // refreshDeviceLockedForUser()
+            int updateTrustOnUnlock = mSettingsObserver.getTrustAgentsExtendUnlock() ? 1 : 0;
             mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId,
-                    0 /* arg2 */).sendToTarget();
+                    updateTrustOnUnlock).sendToTarget();
         }
 
         @Override
@@ -1114,6 +1288,7 @@
                     break;
                 case MSG_SWITCH_USER:
                     mCurrentUser = msg.arg1;
+                    mSettingsObserver.updateContentObserver();
                     refreshDeviceLockedForUser(UserHandle.USER_ALL);
                     break;
                 case MSG_STOP_USER:
@@ -1134,8 +1309,14 @@
                     }
                     break;
                 case MSG_REFRESH_DEVICE_LOCKED_FOR_USER:
+                    if (msg.arg2 == 1) {
+                        updateTrust(msg.arg1, 0 /* flags */, true);
+                    }
                     refreshDeviceLockedForUser(msg.arg1);
                     break;
+                case MSG_SCHEDULE_TRUST_TIMEOUT:
+                    handleScheduleTrustTimeout(msg.arg1, msg.arg2);
+                    break;
             }
         }
     };
@@ -1245,6 +1426,15 @@
                         + " agentsCanRun=" + canAgentsRunForUser(userId));
             }
 
+            // Cancel pending alarms if we require some auth anyway.
+            if (!isTrustAllowedForUser(userId)) {
+                TrustTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
+                if (alarm != null && alarm.isQueued()) {
+                    alarm.setQueued(false /* isQueued */);
+                    mAlarmManager.cancel(alarm);
+                }
+            }
+
             refreshAgentList(userId);
 
             // The list of active trust agents may not have changed, if there was a previous call
@@ -1283,4 +1473,35 @@
             }
         }
     }
+
+    private class TrustTimeoutAlarmListener implements OnAlarmListener {
+        private final int mUserId;
+        private boolean mIsQueued = false;
+
+        TrustTimeoutAlarmListener(int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAlarm() {
+            mIsQueued = false;
+            int strongAuthState = mStrongAuthTracker.getStrongAuthForUser(mUserId);
+
+            // Only fire if trust can unlock.
+            if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
+                if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
+                mLockPatternUtils.requireStrongAuth(
+                        mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
+            }
+            maybeLockScreen(mUserId);
+        }
+
+        public void setQueued(boolean isQueued) {
+            mIsQueued = isQueued;
+        }
+
+        public boolean isQueued() {
+            return mIsQueued;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/utils/UserTokenWatcher.java b/services/core/java/com/android/server/utils/UserTokenWatcher.java
new file mode 100644
index 0000000..a3e58f8
--- /dev/null
+++ b/services/core/java/com/android/server/utils/UserTokenWatcher.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.android.server.utils;
+
+import android.annotation.UserIdInt;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.TokenWatcher;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+
+/**
+ * Multi-user aware {@link TokenWatcher}.
+ *
+ * {@link UserTokenWatcher} is thread-safe.
+ */
+public final class UserTokenWatcher {
+
+    private final Callback mCallback;
+    private final Handler mHandler;
+    private final String mTag;
+
+    @GuardedBy("mWatchers")
+    private final SparseArray<TokenWatcher> mWatchers = new SparseArray<>(1);
+
+    public UserTokenWatcher(Callback callback, Handler handler, String tag) {
+        mCallback = callback;
+        mHandler = handler;
+        mTag = tag;
+    }
+
+    /**
+     * Record that this token has been acquired for the given user.  When acquire is called, and
+     * the user's count goes from 0 to 1, the acquired callback is called on the given
+     * handler.
+     *
+     * Note that the same {@code token} can only be acquired once per user. If this
+     * {@code token} has already been acquired for the given user, no action is taken. The first
+     * subsequent call to {@link #release} will release this {@code token}
+     * immediately.
+     *
+     * @param token  An IBinder object.
+     * @param tag    A string used by the {@link #dump} method for debugging,
+     *               to see who has references.
+     * @param userId A user id
+     */
+    public void acquire(IBinder token, String tag, @UserIdInt int userId) {
+        synchronized (mWatchers) {
+            TokenWatcher watcher = mWatchers.get(userId);
+            if (watcher == null) {
+                watcher = new InnerTokenWatcher(userId, mHandler, mTag);
+                mWatchers.put(userId, watcher);
+            }
+            watcher.acquire(token, tag);
+        }
+    }
+
+    /**
+     * Record that this token has been released for the given user.  When release is called, and
+     * the user's count goes from 1 to 0, the released callback is called on the given
+     * handler.
+     *
+     * @param token  An IBinder object.
+     * @param userId A user id
+     */
+    public void release(IBinder token, @UserIdInt int userId) {
+        synchronized (mWatchers) {
+            TokenWatcher watcher = mWatchers.get(userId);
+            if (watcher != null) {
+                watcher.release(token);
+            }
+        }
+    }
+
+    /**
+     * Returns whether the given user has any registered tokens that have not been cleaned up.
+     *
+     * @return true, if the given user has registered tokens.
+     */
+    public boolean isAcquired(@UserIdInt int userId) {
+        synchronized (mWatchers) {
+            TokenWatcher watcher = mWatchers.get(userId);
+            return watcher != null && watcher.isAcquired();
+        }
+    }
+
+    /**
+     * Dumps the current state.
+     */
+    public void dump(PrintWriter pw) {
+        synchronized (mWatchers) {
+            for (int i = 0; i < mWatchers.size(); i++) {
+                int userId = mWatchers.keyAt(i);
+                TokenWatcher watcher = mWatchers.valueAt(i);
+                if (watcher.isAcquired()) {
+                    pw.print("User ");
+                    pw.print(userId);
+                    pw.println(":");
+                    watcher.dump(new IndentingPrintWriter(pw, " "));
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback for {@link UserTokenWatcher}.
+     */
+    public interface Callback {
+
+        /**
+         * Reports that the first token has been acquired for the given user.
+         */
+        void acquired(@UserIdInt int userId);
+
+        /**
+         * Reports that the last token has been release for the given user.
+         */
+        void released(@UserIdInt int userId);
+    }
+
+    private final class InnerTokenWatcher extends TokenWatcher {
+        private final int mUserId;
+
+        private InnerTokenWatcher(int userId, Handler handler, String tag) {
+            super(handler, tag);
+            this.mUserId = userId;
+        }
+
+        @Override
+        public void acquired() {
+            // We MUST NOT hold any locks while invoking the callbacks.
+            mCallback.acquired(mUserId);
+        }
+
+        @Override
+        public void released() {
+            // We MUST NOT hold any locks while invoking the callbacks.
+            mCallback.released(mUserId);
+
+            synchronized (mWatchers) {
+                final TokenWatcher watcher = mWatchers.get(mUserId);
+                if (watcher != null && !watcher.isAcquired()) {
+                    mWatchers.remove(mUserId);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 10542d5..973499f 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -212,7 +212,7 @@
         removeStackReferenceIfNeeded(stack);
         releaseSelfIfNeeded();
         mService.updateSleepIfNeededLocked();
-        onStackOrderChanged();
+        onStackOrderChanged(stack);
     }
 
     void positionChildAtTop(ActivityStack stack, boolean includingParents) {
@@ -280,7 +280,7 @@
         if (!wasContained) {
             stack.setParent(this);
         }
-        onStackOrderChanged();
+        onStackOrderChanged(stack);
     }
 
     private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
@@ -1309,9 +1309,13 @@
         mStackOrderChangedCallbacks.remove(listener);
     }
 
-    private void onStackOrderChanged() {
+    /**
+     * Notifies of a stack order change
+     * @param stack The stack which triggered the order change
+     */
+    private void onStackOrderChanged(ActivityStack stack) {
         for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
-            mStackOrderChangedCallbacks.get(i).onStackOrderChanged();
+            mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
         }
     }
 
@@ -1390,6 +1394,6 @@
      * Callback for when the order of the stacks in the display changes.
      */
     interface OnStackOrderChangedListener {
-        void onStackOrderChanged();
+        void onStackOrderChanged(ActivityStack stack);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 12690a9..1023182 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -3,6 +3,9 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.processStateAmToProto;
+import static android.app.WaitResult.LAUNCH_STATE_COLD;
+import static android.app.WaitResult.LAUNCH_STATE_HOT;
+import static android.app.WaitResult.LAUNCH_STATE_WARM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -80,6 +83,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_TIMEOUT;
 
+import android.app.WaitResult;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
 import android.content.Intent;
@@ -101,10 +105,10 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
 /**
@@ -259,6 +263,19 @@
             activityRecordIdHashCode = System.identityHashCode(launchedActivity);
             this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
         }
+
+        @WaitResult.LaunchState int getLaunchState() {
+            switch (type) {
+                case TYPE_TRANSITION_WARM_LAUNCH:
+                    return LAUNCH_STATE_WARM;
+                case TYPE_TRANSITION_HOT_LAUNCH:
+                    return LAUNCH_STATE_HOT;
+                case TYPE_TRANSITION_COLD_LAUNCH:
+                    return LAUNCH_STATE_COLD;
+                default:
+                    return -1;
+            }
+        }
     }
 
     ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context, Looper looper) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index de8024f..5f00bcc 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -86,6 +86,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
 import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER;
 import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK;
@@ -145,6 +146,7 @@
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
+import android.app.WaitResult.LaunchState;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -582,6 +584,9 @@
             if (info.maxAspectRatio != 0) {
                 pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
             }
+            if (info.minAspectRatio != 0) {
+                pw.println(prefix + "minAspectRatio=" + info.minAspectRatio);
+            }
         }
     }
 
@@ -2180,7 +2185,7 @@
                 .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
         if (info != null) {
             mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
-                    info.windowsFullyDrawnDelayMs);
+                    info.windowsFullyDrawnDelayMs, info.getLaunchState());
         }
     }
 
@@ -2204,8 +2209,9 @@
             final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
                     .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
             final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
+            final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
             mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
-                    windowsDrawnDelayMs);
+                    windowsDrawnDelayMs, launchState);
             mStackSupervisor.sendWaitingVisibleReportLocked(this);
             finishLaunchTickingLocked();
             if (task != null) {
@@ -2591,7 +2597,10 @@
         outBounds.setEmpty();
         final float maxAspectRatio = info.maxAspectRatio;
         final ActivityStack stack = getActivityStack();
-        if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0
+        final float minAspectRatio = info.minAspectRatio;
+
+        if (task == null || stack == null || task.inMultiWindowMode()
+                || (maxAspectRatio == 0 && minAspectRatio == 0)
                 || isInVrUiMode(getConfiguration())) {
             // We don't set override configuration if that activity task isn't fullscreen. I.e. the
             // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
@@ -2606,20 +2615,35 @@
         final Rect appBounds = getParent().getWindowConfiguration().getAppBounds();
         final int containingAppWidth = appBounds.width();
         final int containingAppHeight = appBounds.height();
-        int maxActivityWidth = containingAppWidth;
-        int maxActivityHeight = containingAppHeight;
+        final float containingRatio = Math.max(containingAppWidth, containingAppHeight)
+                / (float) Math.min(containingAppWidth, containingAppHeight);
 
-        if (containingAppWidth < containingAppHeight) {
-            // Width is the shorter side, so we use that to figure-out what the max. height
-            // should be given the aspect ratio.
-            maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f);
-        } else {
-            // Height is the shorter side, so we use that to figure-out what the max. width
-            // should be given the aspect ratio.
-            maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f);
+        int activityWidth = containingAppWidth;
+        int activityHeight = containingAppHeight;
+
+        if (containingRatio > maxAspectRatio && maxAspectRatio != 0) {
+            if (containingAppWidth < containingAppHeight) {
+                // Width is the shorter side, so we use that to figure-out what the max. height
+                // should be given the aspect ratio.
+                activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f);
+            } else {
+                // Height is the shorter side, so we use that to figure-out what the max. width
+                // should be given the aspect ratio.
+                activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f);
+            }
+        } else if (containingRatio < minAspectRatio && minAspectRatio != 0) {
+            if (containingAppWidth < containingAppHeight) {
+                // Width is the shorter side, so we use the height to figure-out what the max. width
+                // should be given the aspect ratio.
+                activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f);
+            } else {
+                // Height is the shorter side, so we use the width to figure-out what the max.
+                // height should be given the aspect ratio.
+                activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f);
+            }
         }
 
-        if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) {
+        if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
             // The display matches or is less than the activity aspect ratio, so nothing else to do.
             // Return the existing bounds. If this method is running for the first time,
             // {@link #getRequestedOverrideBounds()} will be empty (representing no override). If
@@ -2634,12 +2658,21 @@
         // Also account for the left / top insets (e.g. from display cutouts), which will be clipped
         // away later in StackWindowController.adjustConfigurationForBounds(). Otherwise, the app
         // bounds would end up too small.
-        outBounds.set(0, 0, maxActivityWidth + appBounds.left, maxActivityHeight + appBounds.top);
+        outBounds.set(0, 0, activityWidth + appBounds.left, activityHeight + appBounds.top);
 
-        if (mAtmService.mWindowManager.getNavBarPosition(getDisplayId()) == NAV_BAR_LEFT) {
+        final int navBarPosition = mAtmService.mWindowManager.getNavBarPosition(getDisplayId());
+        if (navBarPosition == NAV_BAR_LEFT) {
             // Position the activity frame on the opposite side of the nav bar.
-            outBounds.left = appBounds.right - maxActivityWidth;
+            outBounds.left = appBounds.right - activityWidth;
             outBounds.right = appBounds.right;
+        } else if (navBarPosition == NAV_BAR_RIGHT) {
+            // Position the activity frame on the opposite side of the nav bar.
+            outBounds.left = 0;
+            outBounds.right = activityWidth + appBounds.left;
+        } else {
+            // Horizontally center the frame.
+            outBounds.left = appBounds.left + (containingAppWidth - activityWidth) / 2;
+            outBounds.right = outBounds.left + activityWidth;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index c43fd24..f58b83d 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -599,7 +599,8 @@
         }
     }
 
-    void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long totalTime) {
+    void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long totalTime,
+            @WaitResult.LaunchState int launchState) {
         boolean changed = false;
         for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
             WaitResult w = mWaitingActivityLaunched.remove(i);
@@ -610,6 +611,7 @@
                     w.who = new ComponentName(r.info.packageName, r.info.name);
                 }
                 w.totalTime = totalTime;
+                w.launchState = launchState;
                 // Do not modify w.result.
             }
         }
@@ -1252,7 +1254,8 @@
             mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
             r.finishLaunchTickingLocked();
             if (fromTimeout) {
-                reportActivityLaunchedLocked(fromTimeout, r, INVALID_DELAY);
+                reportActivityLaunchedLocked(fromTimeout, r, INVALID_DELAY,
+                        -1 /* launchState */);
             }
 
             // This is a hack to semi-deal with a race condition
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 57bfc29..36701ea 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,6 +73,8 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
 
@@ -1339,6 +1341,21 @@
                 voiceInteractor);
         final int preferredWindowingMode = mLaunchParams.mWindowingMode;
 
+        computeLaunchingTaskFlags();
+
+        computeSourceStack();
+
+        mIntent.setFlags(mLaunchFlags);
+
+        ActivityRecord reusedActivity = getReusableIntentActivity();
+
+        mSupervisor.getLaunchParamsController().calculate(
+                reusedActivity != null ? reusedActivity.getTaskRecord() : mInTask,
+                r.info.windowLayout, r, sourceRecord, options, PHASE_BOUNDS, mLaunchParams);
+        mPreferredDisplayId =
+                mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId
+                        : DEFAULT_DISPLAY;
+
         // Do not start home activity if it cannot be launched on preferred display. We are not
         // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
         // fallback to launch on other displays.
@@ -1348,14 +1365,6 @@
             return START_CANCELED;
         }
 
-        computeLaunchingTaskFlags();
-
-        computeSourceStack();
-
-        mIntent.setFlags(mLaunchFlags);
-
-        ActivityRecord reusedActivity = getReusableIntentActivity();
-
         if (reusedActivity != null) {
             // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
             // still needs to be a lock task mode violation since the task gets cleared out and
@@ -1651,14 +1660,13 @@
 
         mLaunchParams.reset();
 
+        // Preferred display id is the only state we need for now and it could be updated again
+        // after we located a reusable task (which might be resided in another display).
         mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r,
-                sourceRecord, options, mLaunchParams);
-
-        if (mLaunchParams.hasPreferredDisplay()) {
-            mPreferredDisplayId = mLaunchParams.mPreferredDisplayId;
-        } else {
-            mPreferredDisplayId = DEFAULT_DISPLAY;
-        }
+                sourceRecord, options, PHASE_DISPLAY, mLaunchParams);
+        mPreferredDisplayId =
+                mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId
+                        : DEFAULT_DISPLAY;
 
         mLaunchMode = r.launchMode;
 
@@ -2502,14 +2510,9 @@
 
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
                  || mPreferredDisplayId != DEFAULT_DISPLAY) {
-            // We don't pass in the default display id into the get launch stack call so it can do a
-            // full resolution.
-            mLaunchParams.mPreferredDisplayId =
-                    mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
             final boolean onTop = aOptions == null || !aOptions.getAvoidMoveToFront();
             final ActivityStack stack =
                     mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
-            mLaunchParams.mPreferredDisplayId = mPreferredDisplayId;
             return stack;
         }
         // Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 8624bff..8d49bf3 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -91,6 +91,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -733,6 +734,17 @@
     }
 
     boolean windowsAreFocusable() {
+        if (mTargetSdk < Build.VERSION_CODES.Q) {
+            final int pid = mActivityRecord != null
+                    ? (mActivityRecord.app != null ? mActivityRecord.app.getPid() : 0) : 0;
+            final AppWindowToken topFocusedAppOfMyProcess =
+                    mWmService.mRoot.mTopFocusedAppByProcess.get(pid);
+            if (topFocusedAppOfMyProcess != null && topFocusedAppOfMyProcess != this) {
+                // For the apps below Q, there can be only one app which has the focused window per
+                // process, because legacy apps may not be ready for a multi-focus system.
+                return false;
+            }
+        }
         return getWindowConfiguration().canReceiveKeys() || mAlwaysFocusable;
     }
 
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 731ebb8..f9980be 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -128,9 +128,10 @@
         mAnimationHandler = animationHandler;
         if (animationHandler != null) {
             // If an animation handler is provided, then ensure that it runs on the sf vsync tick
-            handler.runWithScissors(() -> mChoreographer = Choreographer.getSfInstance(),
-                    0 /* timeout */);
-            animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mChoreographer));
+            handler.post(() -> {
+                mChoreographer = Choreographer.getSfInstance();
+                animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mChoreographer));
+            });
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f4c5ca..1943efc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,9 +61,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -103,6 +103,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
+import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE;
 import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
@@ -169,6 +170,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.TriConsumer;
+import com.android.server.AnimationThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.DisplayRotationUtil;
 import com.android.server.wm.utils.RotationCache;
@@ -861,7 +863,7 @@
 
         AnimationHandler animationHandler = new AnimationHandler();
         mBoundsAnimationController = new BoundsAnimationController(service.mContext,
-                mAppTransition, SurfaceAnimationThread.getHandler(), animationHandler);
+                mAppTransition, AnimationThread.getHandler(), animationHandler);
 
         if (mWmService.mInputManager != null) {
             final InputChannel inputChannel = mWmService.mInputManager.monitorInput("Display "
@@ -2832,6 +2834,11 @@
         forAllWindows(mScheduleToastTimeout, false /* traverseTopToBottom */);
     }
 
+    WindowState findFocusedWindowIfNeeded() {
+        return (mWmService.mPerDisplayFocusEnabled
+                || mWmService.mRoot.mTopFocusedAppByProcess.isEmpty()) ? findFocusedWindow() : null;
+    }
+
     WindowState findFocusedWindow() {
         mTmpWindow = null;
 
@@ -2844,7 +2851,6 @@
         return mTmpWindow;
     }
 
-
     /**
      * Update the focused window and make some adjustments if the focus has changed.
      *
@@ -2856,31 +2862,31 @@
      * @param updateInputWindows Whether to sync the window information to the input module.
      * @return {@code true} if the focused window has changed.
      */
-    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows, boolean focusFound) {
-        final WindowState newFocus = findFocusedWindow();
+    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+        WindowState newFocus = findFocusedWindowIfNeeded();
         if (mCurrentFocus == newFocus) {
             return false;
         }
         boolean imWindowChanged = false;
-        // TODO (b/111080190): Multi-Session IME
-        if (!focusFound) {
-            final WindowState imWindow = mInputMethodWindow;
-            if (imWindow != null) {
-                final WindowState prevTarget = mInputMethodTarget;
+        final WindowState imWindow = mInputMethodWindow;
+        if (imWindow != null) {
+            final WindowState prevTarget = mInputMethodTarget;
+            final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
+            imWindowChanged = prevTarget != newTarget;
 
-                final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
-                imWindowChanged = prevTarget != newTarget;
-
-                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
-                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
-                    assignWindowLayers(false /* setLayoutNeeded */);
-                }
+            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+                    && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                assignWindowLayers(false /* setLayoutNeeded */);
             }
         }
 
         if (imWindowChanged) {
             mWmService.mWindowsChanged = true;
             setLayoutNeeded();
+            newFocus = findFocusedWindowIfNeeded();
+        }
+        if (mCurrentFocus != newFocus) {
+            mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
         }
 
         if (DEBUG_FOCUS_LIGHT || mWmService.localLOGV) Slog.v(TAG_WM, "Changing focus from "
diff --git a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
index 4a20f1a..c9173a6 100644
--- a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
+++ b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
@@ -19,113 +19,142 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
-import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.TokenWatcher;
-import android.util.Log;
-import android.util.Pair;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.UserTokenWatcher;
+import com.android.server.wm.LockTaskController.LockTaskToken;
 
-public class KeyguardDisableHandler extends Handler {
+class KeyguardDisableHandler {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM;
 
-    private static final int ALLOW_DISABLE_YES = 1;
-    private static final int ALLOW_DISABLE_NO = 0;
-    private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
-    private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher
+    private final UserTokenWatcher mAppTokenWatcher;
+    private final UserTokenWatcher mSystemTokenWatcher;
 
-    // Message.what constants
-    static final int KEYGUARD_DISABLE = 1;
-    static final int KEYGUARD_REENABLE = 2;
-    static final int KEYGUARD_POLICY_CHANGED = 3;
+    private int mCurrentUser = UserHandle.USER_SYSTEM;
+    private Injector mInjector;
 
-    final Context mContext;
-    final WindowManagerPolicy mPolicy;
-    KeyguardTokenWatcher mKeyguardTokenWatcher;
-
-    public KeyguardDisableHandler(final Context context, final WindowManagerPolicy policy) {
-        mContext = context;
-        mPolicy = policy;
+    @VisibleForTesting
+    KeyguardDisableHandler(Injector injector, Handler handler) {
+        mInjector = injector;
+        mAppTokenWatcher = new UserTokenWatcher(mCallback, handler, TAG);
+        mSystemTokenWatcher = new UserTokenWatcher(mCallback, handler, TAG);
     }
 
-    @SuppressWarnings("unchecked")
-    @Override
-    public void handleMessage(Message msg) {
-        if (mKeyguardTokenWatcher == null) {
-            mKeyguardTokenWatcher = new KeyguardTokenWatcher(this);
-        }
-
-        switch (msg.what) {
-            case KEYGUARD_DISABLE:
-                final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj;
-                mKeyguardTokenWatcher.acquire(pair.first, pair.second);
-                break;
-
-            case KEYGUARD_REENABLE:
-                mKeyguardTokenWatcher.release((IBinder)msg.obj);
-                break;
-
-            case KEYGUARD_POLICY_CHANGED:
-                mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN;
-                if (mKeyguardTokenWatcher.isAcquired()) {
-                    // If we are currently disabled we need to know if the keyguard
-                    // should be re-enabled, so determine the allow state immediately.
-                    mKeyguardTokenWatcher.updateAllowState();
-                    if (mAllowDisableKeyguard != ALLOW_DISABLE_YES) {
-                        mPolicy.enableKeyguard(true);
-                    }
-                } else {
-                    // lazily evaluate this next time we're asked to disable keyguard
-                    mPolicy.enableKeyguard(true);
-                }
-                break;
+    public void setCurrentUser(int user) {
+        synchronized (this) {
+            mCurrentUser = user;
+            updateKeyguardEnabledLocked(UserHandle.USER_ALL);
         }
     }
 
-    class KeyguardTokenWatcher extends TokenWatcher {
-
-        public KeyguardTokenWatcher(final Handler handler) {
-            super(handler, TAG);
+    void updateKeyguardEnabled(int userId) {
+        synchronized (this) {
+            updateKeyguardEnabledLocked(userId);
         }
+    }
 
-        public void updateAllowState() {
-            // We fail safe and prevent disabling keyguard in the unlikely event this gets
-            // called before DevicePolicyManagerService has started.
-            DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
-                    Context.DEVICE_POLICY_SERVICE);
-            if (dpm != null) {
-                try {
-                    mAllowDisableKeyguard = dpm.getPasswordQuality(null,
-                            ActivityManager.getService().getCurrentUser().id)
-                            == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ?
-                                    ALLOW_DISABLE_YES : ALLOW_DISABLE_NO;
-                } catch (RemoteException re) {
-                    // Nothing much we can do
-                }
-            }
+    private void updateKeyguardEnabledLocked(int userId) {
+        if (mCurrentUser == userId || userId == UserHandle.USER_ALL) {
+            mInjector.enableKeyguard(shouldKeyguardBeEnabled(mCurrentUser));
+        }
+    }
+
+    void disableKeyguard(IBinder token, String tag, int callingUid, int userId) {
+        UserTokenWatcher watcherForCaller = watcherForCallingUid(token, callingUid);
+        watcherForCaller.acquire(token, tag, mInjector.getProfileParentId(userId));
+    }
+
+    void reenableKeyguard(IBinder token, int callingUid, int userId) {
+        UserTokenWatcher watcherForCaller = watcherForCallingUid(token, callingUid);
+        watcherForCaller.release(token, mInjector.getProfileParentId(userId));
+    }
+
+    private UserTokenWatcher watcherForCallingUid(IBinder token, int callingUid) {
+        if (Process.isApplicationUid(callingUid)) {
+            return mAppTokenWatcher;
+        } else if (callingUid == Process.SYSTEM_UID && token instanceof LockTaskToken) {
+            // We allow the lock task token here as a legacy case, because it enforces its own
+            // security guarantees.
+            // NOTE: DO NOT add new usages of this API in system server. It is deprecated and
+            // easily misused.
+            return mSystemTokenWatcher;
+        } else {
+            throw new UnsupportedOperationException("Only apps can use the KeyguardLock API");
+        }
+    }
+
+    private boolean shouldKeyguardBeEnabled(int userId) {
+        final boolean dpmRequiresPassword = mInjector.dpmRequiresPassword(mCurrentUser);
+        final boolean keyguardSecure = mInjector.isKeyguardSecure(mCurrentUser);
+
+        final boolean allowedFromApps = !dpmRequiresPassword && !keyguardSecure;
+        // The system can disable the keyguard for lock task mode even if the keyguard is secure,
+        // because it enforces its own security guarantees.
+        final boolean allowedFromSystem = !dpmRequiresPassword;
+
+        final boolean shouldBeDisabled = allowedFromApps && mAppTokenWatcher.isAcquired(userId)
+                        || allowedFromSystem && mSystemTokenWatcher.isAcquired(userId);
+        return !shouldBeDisabled;
+    }
+
+    // Callback happens on mHandler thread.
+    private final UserTokenWatcher.Callback mCallback = new UserTokenWatcher.Callback() {
+        @Override
+        public void acquired(int userId) {
+            updateKeyguardEnabled(userId);
         }
 
         @Override
-        public void acquired() {
-            if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) {
-                updateAllowState();
-            }
-            if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) {
-                mPolicy.enableKeyguard(false);
-            } else {
-                Log.v(TAG, "Not disabling keyguard since device policy is enforced");
-            }
+        public void released(int userId) {
+            updateKeyguardEnabled(userId);
         }
+    };
 
-        @Override
-        public void released() {
-            mPolicy.enableKeyguard(true);
-        }
+    static KeyguardDisableHandler create(Context context, WindowManagerPolicy policy,
+            Handler handler) {
+        final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+        return new KeyguardDisableHandler(new Injector() {
+            @Override
+            public boolean dpmRequiresPassword(int userId) {
+                DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+                        Context.DEVICE_POLICY_SERVICE);
+                return dpm == null || dpm.getPasswordQuality(null, userId)
+                        != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+            }
+
+            @Override
+            public boolean isKeyguardSecure(int userId) {
+                return policy.isKeyguardSecure(userId);
+            }
+
+            @Override
+            public int getProfileParentId(int userId) {
+                return userManager.getProfileParentId(userId);
+            }
+
+            @Override
+            public void enableKeyguard(boolean enabled) {
+                policy.enableKeyguard(enabled);
+            }
+        }, handler);
+    }
+
+    interface Injector {
+        boolean dpmRequiresPassword(int userId);
+
+        boolean isKeyguardSecure(int userId);
+
+        int getProfileParentId(int userId);
+
+        void enableKeyguard(boolean enabled);
     }
 }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 0947577..59c02f7 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -20,6 +20,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -74,7 +75,7 @@
      * @param result    The resulting params.
      */
     void calculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
-                   ActivityRecord source, ActivityOptions options, LaunchParams result) {
+                   ActivityRecord source, ActivityOptions options, int phase, LaunchParams result) {
         result.reset();
 
         if (task != null || activity != null) {
@@ -89,7 +90,7 @@
             mTmpResult.reset();
             final LaunchParamsModifier modifier = mModifiers.get(i);
 
-            switch(modifier.onCalculate(task, layout, activity, source, options, mTmpCurrent,
+            switch(modifier.onCalculate(task, layout, activity, source, options, phase, mTmpCurrent,
                     mTmpResult)) {
                 case RESULT_SKIP:
                     // Do not apply any results when we are told to skip
@@ -125,7 +126,7 @@
 
     boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
             ActivityRecord source, ActivityOptions options) {
-        calculate(task, layout, activity, source, options, mTmpParams);
+        calculate(task, layout, activity, source, options, PHASE_BOUNDS, mTmpParams);
 
         // No changes, return.
         if (mTmpParams.isEmpty()) {
@@ -259,6 +260,25 @@
          */
         int RESULT_CONTINUE = 2;
 
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({PHASE_DISPLAY, PHASE_WINDOWING_MODE, PHASE_BOUNDS})
+        @interface Phase {}
+
+        /**
+         * Stops once we are done with preferred display calculation.
+         */
+        int PHASE_DISPLAY = 0;
+
+        /**
+         * Stops once we are done with windowing mode calculation.
+         */
+        int PHASE_WINDOWING_MODE = 1;
+
+        /**
+         * Stops once we are done with window bounds calculation.
+         */
+        int PHASE_BOUNDS = 2;
+
         /**
          * Returns the launch params that the provided activity launch params should be overridden
          * to. {@link LaunchParamsModifier} can use this for various purposes, including: 1)
@@ -277,6 +297,7 @@
          *                      launched should have this be non-null.
          * @param source        the Activity that launched a new task. Could be {@code null}.
          * @param options       {@link ActivityOptions} used to start the activity with.
+         * @param phase         the calculation phase, see {@link LaunchParamsModifier.Phase}
          * @param currentParams launching params after the process of last {@link
          *                      LaunchParamsModifier}.
          * @param outParams     the result params to be set.
@@ -284,7 +305,7 @@
          */
         @Result
         int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
-                ActivityRecord source, ActivityOptions options, LaunchParams currentParams,
-                LaunchParams outParams);
+                ActivityRecord source, ActivityOptions options, @Phase int phase,
+                LaunchParams currentParams, LaunchParams outParams);
     }
 }
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 3b66f7d..e6e6275 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -54,6 +54,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.util.Pair;
@@ -126,7 +127,7 @@
     /** Tag used for disabling of keyguard */
     private static final String LOCK_TASK_TAG = "Lock-to-App";
 
-    private final IBinder mToken = new Binder();
+    private final IBinder mToken = new LockTaskToken();
     private final ActivityStackSupervisor mSupervisor;
     private final Context mContext;
 
@@ -180,6 +181,17 @@
      */
     private final Handler mHandler;
 
+    /**
+     * Stores the user for which we're trying to dismiss the keyguard and then subsequently
+     * disable it.
+     *
+     * Tracking this ensures we don't mistakenly disable the keyguard if we've stopped trying to
+     * between the dismiss request and when it succeeds.
+     *
+     * Must only be accessed from the Handler thread.
+     */
+    private int mPendingDisableFromDismiss = UserHandle.USER_NULL;
+
     LockTaskController(Context context, ActivityStackSupervisor supervisor,
             Handler handler) {
         mContext = context;
@@ -740,16 +752,18 @@
      * Should only be called on the handler thread to avoid race.
      */
     private void setKeyguardState(int lockTaskModeState, int userId) {
+        mPendingDisableFromDismiss = UserHandle.USER_NULL;
         if (lockTaskModeState == LOCK_TASK_MODE_NONE) {
-            mWindowManager.reenableKeyguard(mToken);
+            mWindowManager.reenableKeyguard(mToken, userId);
 
         } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
             if (isKeyguardAllowed(userId)) {
-                mWindowManager.reenableKeyguard(mToken);
+                mWindowManager.reenableKeyguard(mToken, userId);
             } else {
                 // If keyguard is not secure and it is locked, dismiss the keyguard before
                 // disabling it, which avoids the platform to think the keyguard is still on.
                 if (mWindowManager.isKeyguardLocked() && !mWindowManager.isKeyguardSecure()) {
+                    mPendingDisableFromDismiss = userId;
                     mWindowManager.dismissKeyguard(new IKeyguardDismissCallback.Stub() {
                         @Override
                         public void onDismissError() throws RemoteException {
@@ -759,7 +773,13 @@
                         @Override
                         public void onDismissSucceeded() throws RemoteException {
                             mHandler.post(
-                                    () -> mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG));
+                                    () -> {
+                                        if (mPendingDisableFromDismiss == userId) {
+                                            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG,
+                                                    userId);
+                                            mPendingDisableFromDismiss = UserHandle.USER_NULL;
+                                        }
+                                    });
                         }
 
                         @Override
@@ -768,12 +788,12 @@
                         }
                     }, null);
                 } else {
-                    mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+                    mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId);
                 }
             }
 
         } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED
-            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId);
         }
     }
 
@@ -898,4 +918,10 @@
             default: return "unknown=" + mLockTaskModeState;
         }
     }
+
+    /** Marker class for the token used to disable keyguard. */
+    static class LockTaskToken extends Binder {
+        private LockTaskToken() {
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 42cd8e8..ec2d673 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -387,7 +387,13 @@
     }
 
     @Override
-    public void onStackOrderChanged() {
+    public void onStackOrderChanged(ActivityStack stack) {
+        if (DEBUG) Slog.d(TAG, "onStackOrderChanged(): stack=" + stack);
+        if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) {
+            // The stack is not visible, so ignore this change
+            return;
+        }
+
         // If the activity display stack order changes, cancel any running recents animation in
         // place
         mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE,
@@ -429,7 +435,7 @@
         }
 
         for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
-            final TaskRecord task = (TaskRecord) targetStack.getChildAt(i);
+            final TaskRecord task = targetStack.getChildAt(i);
             if (task.getBaseIntent().getComponent().equals(component)) {
                 return task.getTopActivity();
             }
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 84a32fc..d0144fd 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1615,33 +1615,35 @@
             return candidateTask.getStack();
         }
 
+        int windowingMode;
+        if (launchParams != null) {
+            // When launch params is not null, we always defer to its windowing mode. Sometimes
+            // it could be unspecified, which indicates it should inherit windowing mode from
+            // display.
+            windowingMode = launchParams.mWindowingMode;
+        } else {
+            windowingMode = options != null ? options.getLaunchWindowingMode()
+                    : r.getWindowingMode();
+        }
+        windowingMode = activityDisplay.validateWindowingMode(windowingMode, r, candidateTask,
+                r.getActivityType());
+
         // Return the topmost valid stack on the display.
         for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
             final ActivityStack stack = activityDisplay.getChildAt(i);
-            if (isValidLaunchStack(stack, r)) {
+            if (isValidLaunchStack(stack, r, windowingMode)) {
                 return stack;
             }
         }
 
         // If there is no valid stack on the external display - check if new dynamic stack will do.
         if (displayId != DEFAULT_DISPLAY) {
-            final int windowingMode;
-            if (launchParams != null) {
-                // When launch params is not null, we always defer to its windowing mode. Sometimes
-                // it could be unspecified, which indicates it should inherit windowing mode from
-                // display.
-                windowingMode = launchParams.mWindowingMode;
-            } else {
-                windowingMode = options != null ? options.getLaunchWindowingMode()
-                        : r.getWindowingMode();
-            }
             final int activityType =
                     options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
                             ? options.getLaunchActivityType() : r.getActivityType();
             return activityDisplay.createStack(windowingMode, activityType, true /*onTop*/);
         }
 
-        Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
         return null;
     }
 
@@ -1653,7 +1655,7 @@
     }
 
     // TODO: Can probably be consolidated into getLaunchStack()...
-    private boolean isValidLaunchStack(ActivityStack stack, ActivityRecord r) {
+    private boolean isValidLaunchStack(ActivityStack stack, ActivityRecord r, int windowingMode) {
         switch (stack.getActivityType()) {
             case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
             case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
@@ -1661,11 +1663,13 @@
         }
         // There is a 1-to-1 relationship between stack and task when not in
         // primary split-windowing mode.
-        if (stack.getWindowingMode() != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            return false;
-        } else {
-            return r.supportsSplitScreenWindowingMode();
+        if (stack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                && r.supportsSplitScreenWindowingMode()
+                && (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_UNDEFINED)) {
+            return true;
         }
+        return false;
     }
 
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3bbef92..801e5f2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -79,6 +79,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.function.Consumer;
 
 /** Root {@link WindowContainer} for the device. */
@@ -122,7 +123,10 @@
 
     // The ID of the display which is responsible for receiving display-unspecified key and pointer
     // events.
-    int mTopFocusedDisplayId = INVALID_DISPLAY;
+    private int mTopFocusedDisplayId = INVALID_DISPLAY;
+
+    // Map from the PID to the top most app which has a focused window of the process.
+    final HashMap<Integer, AppWindowToken> mTopFocusedAppByProcess = new HashMap<>();
 
     // Only a separate transaction until we separate the apply surface changes
     // transaction from the global transaction.
@@ -157,50 +161,33 @@
     }
 
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+        mTopFocusedAppByProcess.clear();
         boolean changed = false;
         int topFocusedDisplayId = INVALID_DISPLAY;
-
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final DisplayContent dc = mChildren.get(i);
-            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows,
-                    topFocusedDisplayId != INVALID_DISPLAY /* focusFound */);
-            if (topFocusedDisplayId == INVALID_DISPLAY && dc.mCurrentFocus != null) {
-                topFocusedDisplayId = dc.getDisplayId();
+            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows);
+            final WindowState newFocus = dc.mCurrentFocus;
+            if (newFocus != null) {
+                final int pidOfNewFocus = newFocus.mSession.mPid;
+                if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
+                    mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mAppToken);
+                }
+                if (topFocusedDisplayId == INVALID_DISPLAY) {
+                    topFocusedDisplayId = dc.getDisplayId();
+                }
             }
         }
         if (topFocusedDisplayId == INVALID_DISPLAY) {
             topFocusedDisplayId = DEFAULT_DISPLAY;
         }
-        // TODO(b/118865114): Review if need callback top focus display change to view component.
-        // (i.e. Activity or View)
-        // Currently we only tracked topFocusedDisplayChanged for notifying InputMethodManager via
-        // ViewRootImpl.windowFocusChanged to refocus IME window when top display focus changed
-        // but window focus remain the same case.
-        // It may need to review if any use case that need to add new callback for reporting
-        // this change.
-        final boolean topFocusedDisplayChanged =
-                mTopFocusedDisplayId != topFocusedDisplayId && mode == UPDATE_FOCUS_NORMAL;
         if (mTopFocusedDisplayId != topFocusedDisplayId) {
             mTopFocusedDisplayId = topFocusedDisplayId;
-            mWmService.mInputManager.setFocusedDisplay(mTopFocusedDisplayId);
+            mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
+            mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
             if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId="
-                    + mTopFocusedDisplayId);
+                    + topFocusedDisplayId);
         }
-
-        // Report window focus or top display focus changed through REPORT_FOCUS_CHANGE.
-        forAllDisplays((dc) -> {
-            final boolean windowFocusChanged =
-                    dc.mCurrentFocus != null && dc.mCurrentFocus != dc.mLastFocus;
-            final boolean isTopFocusedDisplay =
-                    topFocusedDisplayChanged && dc.getDisplayId() == mTopFocusedDisplayId;
-            if (windowFocusChanged || isTopFocusedDisplay) {
-                final Message msg = mWmService.mH.obtainMessage(
-                        WindowManagerService.H.REPORT_FOCUS_CHANGE, dc);
-                msg.arg1 = topFocusedDisplayChanged ? 1 : 0;
-                mWmService.mH.sendMessage(msg);
-            }
-        });
-
         return changed;
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 1fb7979..5107b52 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -51,6 +51,7 @@
 import android.view.Gravity;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 
@@ -102,19 +103,27 @@
         mSupervisor = supervisor;
     }
 
+    @VisibleForTesting
+    int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout, ActivityRecord activity,
+            ActivityRecord source, ActivityOptions options, LaunchParams currentParams,
+            LaunchParams outParams) {
+        return onCalculate(task, layout, activity, source, options, PHASE_BOUNDS, currentParams,
+                outParams);
+    }
+
     @Override
     public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
                            ActivityRecord activity, ActivityRecord source, ActivityOptions options,
-                           LaunchParams currentParams, LaunchParams outParams) {
+                           int phase, LaunchParams currentParams, LaunchParams outParams) {
         initLogBuilder(task, activity);
-        final int result = calculate(task, layout, activity, source, options, currentParams,
+        final int result = calculate(task, layout, activity, source, options, phase, currentParams,
                 outParams);
         outputLog();
         return result;
     }
 
     private int calculate(TaskRecord task, ActivityInfo.WindowLayout layout,
-            ActivityRecord activity, ActivityRecord source, ActivityOptions options,
+            ActivityRecord activity, ActivityRecord source, ActivityOptions options, int phase,
             LaunchParams currentParams, LaunchParams outParams) {
         final ActivityRecord root;
         if (task != null) {
@@ -145,6 +154,10 @@
                     + display.getWindowingMode());
         }
 
+        if (phase == PHASE_DISPLAY) {
+            return RESULT_CONTINUE;
+        }
+
         // STEP 2: Resolve launch windowing mode.
         // STEP 2.1: Determine if any parameter has specified initial bounds. That might be the
         // launch bounds from activity options, or size/gravity passed in layout. It also treats the
@@ -247,6 +260,10 @@
         outParams.mWindowingMode = launchMode == display.getWindowingMode()
                 ? WINDOWING_MODE_UNDEFINED : launchMode;
 
+        if (phase == PHASE_WINDOWING_MODE) {
+            return RESULT_CONTINUE;
+        }
+
         // STEP 3: Determine final launch bounds based on resolved windowing mode and activity
         // requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is
         // for all other windowing modes that's not freeform mode. One can read comments in
@@ -288,12 +305,6 @@
             displayId = optionLaunchId;
         }
 
-        if (displayId == INVALID_DISPLAY && source != null) {
-            final int sourceDisplayId = source.getDisplayId();
-            if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
-            displayId = sourceDisplayId;
-        }
-
         ActivityStack stack =
                 (displayId == INVALID_DISPLAY && task != null) ? task.getStack() : null;
         if (stack != null) {
@@ -301,6 +312,12 @@
             displayId = stack.mDisplayId;
         }
 
+        if (displayId == INVALID_DISPLAY && source != null) {
+            final int sourceDisplayId = source.getDisplayId();
+            if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
+            displayId = sourceDisplayId;
+        }
+
         if (displayId != INVALID_DISPLAY
                 && mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) == null) {
             displayId = currentParams.mPreferredDisplayId;
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 53d2cb0..c006a7b 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -16,6 +16,12 @@
 
 package com.android.server.wm;
 
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
+import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
@@ -25,21 +31,18 @@
 
 import com.android.server.wm.WindowManagerService.H;
 
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-
 public class TaskTapPointerEventListener implements PointerEventListener {
 
-    final private Region mTouchExcludeRegion = new Region();
+    private final Region mTouchExcludeRegion = new Region();
+    private final Region mTmpRegion = new Region();
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler;
     private final Runnable mMoveDisplayToTop;
     private final Rect mTmpRect = new Rect();
     private int mPointerIconType = TYPE_NOT_SPECIFIED;
+    private int mLastDownX;
+    private int mLastDownY;
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
@@ -47,7 +50,22 @@
         mDisplayContent = displayContent;
         mHandler = new Handler(mService.mH.getLooper());
         mMoveDisplayToTop = () -> {
+            int x;
+            int y;
+            synchronized (this) {
+                x = mLastDownX;
+                y = mLastDownY;
+            }
             synchronized (mService.mGlobalLock) {
+                if (!mService.mPerDisplayFocusEnabled
+                        && mService.mRoot.getTopFocusedDisplayContent() != mDisplayContent
+                        && inputMethodWindowContains(x, y)) {
+                    // In a single focus system, if the input method window and the input method
+                    // target window are on the different displays, when the user is tapping on the
+                    // input method window, we don't move its display to top. Otherwise, the input
+                    // method target window will lose the focus.
+                    return;
+                }
                 mDisplayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
                         mDisplayContent, true /* includingParents */);
             }
@@ -70,6 +88,8 @@
                         mService.mTaskPositioningController.handleTapOutsideTask(
                                 mDisplayContent, x, y);
                     }
+                    mLastDownX = x;
+                    mLastDownY = y;
                     mHandler.post(mMoveDisplayToTop);
                 }
             }
@@ -122,4 +142,13 @@
     private int getDisplayId() {
         return mDisplayContent.getDisplayId();
     }
+
+    private boolean inputMethodWindowContains(int x, int y) {
+        final WindowState inputMethodWindow = mDisplayContent.mInputMethodWindow;
+        if (inputMethodWindow == null || !inputMethodWindow.isVisibleLw()) {
+            return false;
+        }
+        inputMethodWindow.getTouchableRegion(mTmpRegion);
+        return mTmpRegion.contains(x, y);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 9f1a587..646fdd9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -334,6 +334,11 @@
     public abstract void registerAppTransitionListener(AppTransitionListener listener);
 
     /**
+     * Reports that the password for the given user has changed.
+     */
+    public abstract void reportPasswordChanged(int userId);
+
+    /**
      * Retrieves a height of input method window for given display.
      */
     public abstract int getInputMethodWindowVisibleHeight(int displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 002d6d4..e3ced83 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
@@ -72,7 +73,6 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -181,7 +181,6 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -235,6 +234,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.view.WindowManagerPolicyThread;
 import com.android.server.AnimationThread;
@@ -396,7 +396,7 @@
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
                 case ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED:
-                    mKeyguardDisableHandler.sendEmptyMessage(KEYGUARD_POLICY_CHANGED);
+                    mKeyguardDisableHandler.updateKeyguardEnabled(getSendingUserId());
                     break;
             }
         }
@@ -611,6 +611,9 @@
     boolean mClientFreezingScreen = false;
     int mAppsFreezingScreen = 0;
 
+    @VisibleForTesting
+    boolean mPerDisplayFocusEnabled;
+
     // State while inside of layoutAndPlaceSurfacesLocked().
     boolean mFocusMayChange;
 
@@ -944,6 +947,8 @@
                 com.android.internal.R.integer.config_maxUiWidth);
         mDisableTransitionAnimation = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_disableTransitionAnimation);
+        mPerDisplayFocusEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mDisplayWindowSettings = new DisplayWindowSettings(this);
@@ -961,7 +966,7 @@
 
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
-        mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
+        mKeyguardDisableHandler = KeyguardDisableHandler.create(mContext, mPolicy, mH);
 
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -1040,7 +1045,7 @@
         IntentFilter filter = new IntentFilter();
         // Track changes to DevicePolicyManager state so we can enable/disable keyguard.
         filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
+        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
 
         mLatencyTracker = LatencyTracker.getInstance(context);
 
@@ -2817,45 +2822,38 @@
     }
 
     @Override
-    public void disableKeyguard(IBinder token, String tag) {
+    public void disableKeyguard(IBinder token, String tag, int userId) {
+        userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false /* allowAll */, ALLOW_FULL_ONLY, "disableKeyguard", null);
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
             != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires DISABLE_KEYGUARD permission");
         }
-        // If this isn't coming from the system then don't allow disabling the lockscreen
-        // to bypass security.
-        if (Binder.getCallingUid() != SYSTEM_UID && isKeyguardSecure()) {
-            Log.d(TAG_WM, "current mode is SecurityMode, ignore disableKeyguard");
-            return;
+        final int callingUid = Binder.getCallingUid();
+        final long origIdentity = Binder.clearCallingIdentity();
+        try {
+            mKeyguardDisableHandler.disableKeyguard(token, tag, callingUid, userId);
+        } finally {
+            Binder.restoreCallingIdentity(origIdentity);
         }
-
-        // If this isn't coming from the current profiles, ignore it.
-        if (!isCurrentProfileLocked(UserHandle.getCallingUserId())) {
-            Log.d(TAG_WM, "non-current profiles, ignore disableKeyguard");
-            return;
-        }
-
-        if (token == null) {
-            throw new IllegalArgumentException("token == null");
-        }
-
-        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
-                KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag)));
     }
 
     @Override
-    public void reenableKeyguard(IBinder token) {
+    public void reenableKeyguard(IBinder token, int userId) {
+        userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false /* allowAll */, ALLOW_FULL_ONLY, "reenableKeyguard", null);
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
             != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires DISABLE_KEYGUARD permission");
         }
-
-        if (token == null) {
-            throw new IllegalArgumentException("token == null");
+        Preconditions.checkNotNull(token, "token is null");
+        final int callingUid = Binder.getCallingUid();
+        final long origIdentity = Binder.clearCallingIdentity();
+        try {
+            mKeyguardDisableHandler.reenableKeyguard(token, callingUid, userId);
+        } finally {
+            Binder.restoreCallingIdentity(origIdentity);
         }
-
-        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
-                KeyguardDisableHandler.KEYGUARD_REENABLE, token));
     }
 
     /**
@@ -3139,11 +3137,7 @@
             mCurrentUserId = newUserId;
             mCurrentProfileIds = currentProfileIds;
             mPolicy.setCurrentUserLw(newUserId);
-
-            // If keyguard was disabled, re-enable it
-            // TODO: Keep track of keyguardEnabled state per user and use here...
-            // e.g. enabled = mKeyguardDisableHandler.getEnabledStateForUser(newUserId);
-            mPolicy.enableKeyguard(true);
+            mKeyguardDisableHandler.setCurrentUser(newUserId);
 
             // Hide windows that should not be seen by the new user.
             mRoot.switchUser();
@@ -4484,7 +4478,6 @@
 
                     AccessibilityController accessibilityController = null;
 
-                    final boolean topFocusedDisplayChanged = msg.arg1 != 0;
                     synchronized (mGlobalLock) {
                         // TODO(multidisplay): Accessibility supported only of default desiplay.
                         if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
@@ -4495,19 +4488,7 @@
                         newFocus = displayContent.mCurrentFocus;
                     }
                     if (lastFocus == newFocus) {
-                        // Report focus to ViewRootImpl when top focused display changes.
-                        // Or, nothing to do for no window focus change.
-                        if (topFocusedDisplayChanged && newFocus != null) {
-                            if (DEBUG_FOCUS_LIGHT) {
-                                Slog.d(TAG, "Reporting focus: " + newFocus
-                                        + " due to top focused display change.");
-                            }
-                            // See {@link IWindow#windowFocusChanged} to know why set
-                            // reportToClient as false.
-                            newFocus.reportFocusChangedSerialized(true, mInTouchMode,
-                                    false /* reportToClient */);
-                            notifyFocusChanged();
-                        }
+                        // Focus is not changing, so nothing to do.
                         return;
                     }
                     synchronized (mGlobalLock) {
@@ -4529,15 +4510,13 @@
 
                     if (newFocus != null) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
-                        newFocus.reportFocusChangedSerialized(true, mInTouchMode,
-                                true /* reportToClient */);
+                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
                         notifyFocusChanged();
                     }
 
                     if (lastFocus != null) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
-                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode,
-                                true /* reportToClient */);
+                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
                     }
                 } break;
 
@@ -4554,8 +4533,7 @@
                     for (int i = 0; i < N; i++) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
                                 losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode,
-                                true /* reportToClient */);
+                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
                     }
                 } break;
 
@@ -7124,6 +7102,11 @@
         }
 
         @Override
+        public void reportPasswordChanged(int userId) {
+            mKeyguardDisableHandler.updateKeyguardEnabled(userId);
+        }
+
+        @Override
         public int getInputMethodWindowVisibleHeight(int displayId) {
             synchronized (mGlobalLock) {
                 final DisplayContent dc = mRoot.getDisplayContent(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d2dfa76..e78c12c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2168,11 +2168,13 @@
                 mTmpRect.inset(-delta, -delta);
             }
             region.set(mTmpRect);
-            region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
+            cropRegionToStackBoundsIfNeeded(region);
         } else {
             // Not modal or full screen modal
-            getTouchableRegion(region, true /* forSurface */);
+            getTouchableRegion(region);
         }
+        // Translate to surface based coordinates.
+        region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
 
         return flags;
     }
@@ -2809,16 +2811,6 @@
 
     /** Get the touchable region in global coordinates. */
     void getTouchableRegion(Region outRegion) {
-        getTouchableRegion(outRegion, false /* forSurface */);
-    }
-
-    /** If {@param forSuface} is {@code true}, the region will be translated to surface based. */
-    private void getTouchableRegion(Region outRegion, boolean forSurface) {
-        if (inPinnedWindowingMode() && !isFocused()) {
-            outRegion.setEmpty();
-            return;
-        }
-
         final Rect frame = mWindowFrames.mFrame;
         switch (mTouchableInsets) {
             default:
@@ -2833,18 +2825,11 @@
                 break;
             case TOUCHABLE_INSETS_REGION: {
                 outRegion.set(mGivenTouchableRegion);
+                outRegion.translate(frame.left, frame.top);
                 break;
             }
         }
         cropRegionToStackBoundsIfNeeded(outRegion);
-
-        if (forSurface) {
-            if (mTouchableInsets != TOUCHABLE_INSETS_REGION) {
-                outRegion.translate(-frame.left, -frame.top);
-            }
-            outRegion.getBounds(mTmpRect);
-            applyInsets(outRegion, mTmpRect, mAttrs.surfaceInsets);
-        }
     }
 
     private void cropRegionToStackBoundsIfNeeded(Region region) {
@@ -2866,13 +2851,12 @@
      * Report a focus change.  Must be called with no locks held, and consistently
      * from the same serialized thread (such as dispatched from a handler).
      */
-    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode,
-            boolean reportToClient) {
+    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
         try {
-            mClient.windowFocusChanged(focused, inTouchMode, reportToClient);
+            mClient.windowFocusChanged(focused, inTouchMode);
         } catch (RemoteException e) {
         }
-        if (mFocusCallbacks != null && reportToClient) {
+        if (mFocusCallbacks != null) {
             final int N = mFocusCallbacks.beginBroadcast();
             for (int i=0; i<N; i++) {
                 IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0e349b7..58fd30e 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -92,7 +92,6 @@
 using android::hardware::gnss::V1_0::IAGnss;
 using android::hardware::gnss::V1_0::IAGnssCallback;
 using android::hardware::gnss::V1_0::IAGnssCallback;
-using android::hardware::gnss::V1_0::IAGnssRil;
 using android::hardware::gnss::V1_0::IAGnssRilCallback;
 using android::hardware::gnss::V1_0::IGnssBatching;
 using android::hardware::gnss::V1_0::IGnssBatchingCallback;
@@ -121,6 +120,8 @@
 using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback;
 using IGnssMeasurementCallback_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurementCallback;
 using IGnssMeasurementCallback_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurementCallback;
+using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil;
+using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil;
 
 struct GnssDeathRecipient : virtual public hidl_death_recipient
 {
@@ -141,7 +142,8 @@
 sp<IGnss_V1_1> gnssHal_V1_1 = nullptr;
 sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
 sp<IGnssXtra> gnssXtraIface = nullptr;
-sp<IAGnssRil> agnssRilIface = nullptr;
+sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
+sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
 sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
 sp<IAGnss> agnssIface = nullptr;
 sp<IGnssBatching> gnssBatchingIface = nullptr;
@@ -1247,11 +1249,21 @@
         gnssXtraIface = gnssXtra;
     }
 
-    auto gnssRil = gnssHal->getExtensionAGnssRil();
-    if (!gnssRil.isOk()) {
-        ALOGD("Unable to get a handle to AGnssRil");
+    if (gnssHal_V2_0 != nullptr) {
+        auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0();
+        if (!agnssRil_V2_0.isOk()) {
+            ALOGD("Unable to get a handle to AGnssRil_V2_0");
+        } else {
+            agnssRilIface_V2_0 = agnssRil_V2_0;
+            agnssRilIface = agnssRilIface_V2_0;
+        }
     } else {
-        agnssRilIface = gnssRil;
+        auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
+        if (!agnssRil_V1_0.isOk()) {
+            ALOGD("Unable to get a handle to AGnssRil");
+        } else {
+            agnssRilIface = agnssRil_V1_0;
+        }
     }
 
     auto gnssAgnss = gnssHal->getExtensionAGnss();
@@ -1496,17 +1508,17 @@
 
 static void android_location_GnssLocationProvider_agps_set_reference_location_cellid(
         JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid) {
-    IAGnssRil::AGnssRefLocation location;
+    IAGnssRil_V1_0::AGnssRefLocation location;
 
     if (agnssRilIface == nullptr) {
         ALOGE("No AGPS RIL interface in agps_set_reference_location_cellid");
         return;
     }
 
-    switch (static_cast<IAGnssRil::AGnssRefLocationType>(type)) {
-        case IAGnssRil::AGnssRefLocationType::GSM_CELLID:
-        case IAGnssRil::AGnssRefLocationType::UMTS_CELLID:
-          location.type = static_cast<IAGnssRil::AGnssRefLocationType>(type);
+    switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) {
+        case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID:
+        case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID:
+          location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type);
           location.cellID.mcc = mcc;
           location.cellID.mnc = mnc;
           location.cellID.lac = lac;
@@ -1529,7 +1541,7 @@
     }
 
     const char *setid = env->GetStringUTFChars(setid_string, nullptr);
-    agnssRilIface->setSetId((IAGnssRil::SetIDType)type, setid);
+    agnssRilIface->setSetId((IAGnssRil_V1_0::SetIDType)type, setid);
     env->ReleaseStringUTFChars(setid_string, setid);
 }
 
@@ -1771,26 +1783,44 @@
                                                                        jint type,
                                                                        jboolean roaming,
                                                                        jboolean available,
-                                                                       jstring extraInfo,
-                                                                       jstring apn) {
-    if (agnssRilIface != nullptr) {
+                                                                       jstring apn,
+                                                                       jlong networkHandle,
+                                                                       jshort capabilities) {
+    if (agnssRilIface == nullptr) {
+        ALOGE("AGnssRilInterface does not exist");
+        return;
+    }
+
+    const char *c_apn = env->GetStringUTFChars(apn, nullptr);
+    const android::hardware::hidl_string hidl_apn{c_apn};
+    if (agnssRilIface_V2_0 != nullptr) {
+        IAGnssRil_V2_0::NetworkAttributes networkAttributes = {
+            .networkHandle = static_cast<uint64_t>(networkHandle),
+            .isConnected = static_cast<bool>(connected),
+            .capabilities = static_cast<uint16_t>(capabilities),
+            .apn = hidl_apn
+        };
+
+        auto result = agnssRilIface_V2_0->updateNetworkState_2_0(networkAttributes);
+        if (!result.isOk() || !result) {
+            ALOGE("updateNetworkState_2_0 failed");
+        }
+    } else {
         auto result = agnssRilIface->updateNetworkState(connected,
-                                                       static_cast<IAGnssRil::NetworkType>(type),
-                                                       roaming);
+                static_cast<IAGnssRil_V1_0::NetworkType>(type), roaming);
         if (!result.isOk() || !result) {
             ALOGE("updateNetworkState failed");
         }
 
-        const char *c_apn = env->GetStringUTFChars(apn, nullptr);
-        result = agnssRilIface->updateNetworkAvailability(available, c_apn);
-        if (!result.isOk() || !result) {
-            ALOGE("updateNetworkAvailability failed");
+        if (!hidl_apn.empty()) {
+            result = agnssRilIface->updateNetworkAvailability(available, hidl_apn);
+            if (!result.isOk() || !result) {
+                ALOGE("updateNetworkAvailability failed");
+            }
         }
-
-        env->ReleaseStringUTFChars(apn, c_apn);
-    } else {
-        ALOGE("AGnssRilInterface does not exist");
     }
+
+    env->ReleaseStringUTFChars(apn, c_apn);
 }
 
 static jboolean android_location_GnssGeofenceProvider_is_geofence_supported(
@@ -2098,7 +2128,6 @@
     }
 }
 
-
 static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
     if (gnssBatchingIface == nullptr) {
         return 0; // batching not supported, size = 0
@@ -2317,7 +2346,7 @@
     {"native_is_agps_ril_supported", "()Z",
             reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_is_agps_ril_supported)},
     {"native_update_network_state",
-            "(ZIZZLjava/lang/String;Ljava/lang/String;)V",
+            "(ZIZZLjava/lang/String;JS)V",
             reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_update_network_state)},
     {"native_agps_data_conn_open",
             "(Ljava/lang/String;I)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7186cdf..bd3dfe9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -164,7 +164,6 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.net.metrics.IpConnectivityLog;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Build;
@@ -478,7 +477,8 @@
 
     /**
      * Strings logged with {@link
-     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}.
+     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
+     * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
      */
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -4328,6 +4328,11 @@
             }
         }
 
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_WIDGET_PROVIDER)
+                .setAdmin(admin)
+                .write();
+
         if (changedProviders != null) {
             mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
@@ -4355,6 +4360,11 @@
             }
         }
 
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER)
+                .setAdmin(admin)
+                .write();
+
         if (changedProviders != null) {
             mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
@@ -5491,6 +5501,12 @@
         final long id = mInjector.binderClearCallingIdentity();
         try {
             mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
+            final boolean isDelegate = (admin == null);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
@@ -5556,7 +5572,14 @@
             final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
             try {
                 IKeyChainService keyChain = keyChainConnection.getService();
-                return keyChain.removeKeyPair(alias);
+                final boolean result = keyChain.removeKeyPair(alias);
+                final boolean isDelegate = (who == null);
+                DevicePolicyEventLogger
+                        .createEvent(DevicePolicyEnums.REMOVE_KEY_PAIR)
+                        .setAdmin(callerPackage)
+                        .setBoolean(isDelegate)
+                        .write();
+                return result;
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Removing keypair", e);
             } finally {
@@ -5740,6 +5763,14 @@
                         return false;
                     }
                 }
+                final boolean isDelegate = (who == null);
+                DevicePolicyEventLogger
+                        .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
+                        .setAdmin(callerPackage)
+                        .setBoolean(isDelegate)
+                        .setInt(idAttestationFlags)
+                        .setStrings(algorithm)
+                        .write();
                 return true;
             }
         } catch (RemoteException e) {
@@ -5768,6 +5799,12 @@
                 return false;
             }
             keyChain.setUserSelectable(alias, isUserSelectable);
+            final boolean isDelegate = (who == null);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_KEY_PAIR_CERTIFICATE)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
+                    .write();
             return true;
         } catch (InterruptedException e) {
             Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
@@ -5830,6 +5867,12 @@
                     sendPrivateKeyAliasResponse(chosenAlias, response);
                 }
             }, null, Activity.RESULT_OK, null, null);
+            final String adminPackageName =
+                    (aliasChooser != null ? aliasChooser.getPackageName() : null);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.CHOOSE_PRIVATE_KEY_ALIAS)
+                    .setAdmin(adminPackageName)
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
@@ -5925,9 +5968,9 @@
 
             // If set, remove exclusive scopes from all other delegates
             if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) {
-                for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) {
-                    final String currentPackage = entry.getKey();
-                    final List<String> currentScopes = entry.getValue();
+                for (int i = policy.mDelegationMap.size() - 1; i >= 0; --i) {
+                    final String currentPackage = policy.mDelegationMap.keyAt(i);
+                    final List<String> currentScopes = policy.mDelegationMap.valueAt(i);
 
                     if (!currentPackage.equals(delegatePackage)) {
                         // Iterate through all other delegates
@@ -5935,7 +5978,7 @@
                             // And if this delegate had some exclusive scopes which are now moved
                             // to the new delegate, notify about its delegation changes.
                             if (currentScopes.isEmpty()) {
-                                policy.mDelegationMap.remove(currentPackage);
+                                policy.mDelegationMap.removeAt(i);
                             }
                             sendDelegationChangedBroadcast(currentPackage,
                                     new ArrayList<>(currentScopes), userId);
@@ -6219,6 +6262,11 @@
     public void setCertInstallerPackage(ComponentName who, String installerPackage)
             throws SecurityException {
         setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CERT_INSTALLER_PACKAGE)
+                .setAdmin(who)
+                .setStrings(installerPackage)
+                .write();
     }
 
     @Override
@@ -6250,6 +6298,13 @@
             if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) {
                 throw new UnsupportedOperationException();
             }
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
+                    .setAdmin(admin)
+                    .setStrings(vpnPackage)
+                    .setBoolean(lockdown)
+                    .setInt(/* number of vpn packages */ 0)
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(token);
         }
@@ -6965,6 +7020,11 @@
                 updateScreenCaptureDisabled(userHandle, disabled);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_SCREEN_CAPTURE_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     /**
@@ -7036,6 +7096,11 @@
                 mInjector.binderRestoreCallingIdentity(ident);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_AUTO_TIME_REQUIRED)
+                .setAdmin(who)
+                .setBoolean(required)
+                .write();
     }
 
     /**
@@ -7167,6 +7232,10 @@
                             DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
             mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
                     RemoteBugreportUtils.REMOTE_BUGREPORT_TIMEOUT_MILLIS);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.REQUEST_BUGREPORT)
+                    .setAdmin(who)
+                    .write();
             return true;
         } catch (RemoteException re) {
             // should never happen
@@ -7377,6 +7446,11 @@
         }
         // Tell the user manager that the restrictions have changed.
         pushUserRestrictions(userHandle);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CAMERA_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     /**
@@ -7528,6 +7602,13 @@
             // Notify package manager.
             mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_KEEP_UNINSTALLED_PACKAGES)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(packageList.toArray(new String[0]))
+                .write();
     }
 
     @Override
@@ -7585,6 +7666,11 @@
             if (isAdb()) {
                 // Log device owner provisioning was started using adb.
                 MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
+                DevicePolicyEventLogger
+                        .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB)
+                        .setAdmin(admin)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
+                        .write();
             }
 
             mOwners.setDeviceOwner(admin, ownerName, userId);
@@ -7854,6 +7940,11 @@
             if (isAdb()) {
                 // Log profile owner provisioning was started using adb.
                 MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
+                DevicePolicyEventLogger
+                        .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB)
+                        .setAdmin(who)
+                        .setStrings(LOG_TAG_PROFILE_OWNER)
+                        .write();
             }
 
             mOwners.setProfileOwner(who, ownerName, userHandle);
@@ -7941,6 +8032,10 @@
                 mInjector.binderRestoreCallingIdentity(token);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_DEVICE_OWNER_LOCK_SCREEN_INFO)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -8127,6 +8222,10 @@
         final long id = mInjector.binderClearCallingIdentity();
         try {
             mUserManager.setUserName(userId, profileName);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
+                    .setAdmin(who)
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
@@ -8601,6 +8700,13 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        final String activityPackage =
+                (activity != null ? activity.getPackageName() : null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_PERSISTENT_PREFERRED_ACTIVITY)
+                .setAdmin(who)
+                .setStrings(activityPackage, getIntentFilterActions(filter))
+                .write();
     }
 
     @Override
@@ -8664,6 +8770,13 @@
         final long id = mInjector.binderClearCallingIdentity();
         try {
             mUserManager.setApplicationRestrictions(packageName, settings, userHandle);
+            final boolean isDelegate = (who == null);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
+                    .setStrings(packageName)
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
@@ -8795,6 +8908,24 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_INTENT_FILTER)
+                .setAdmin(who)
+                .setStrings(getIntentFilterActions(filter))
+                .setInt(flags)
+                .write();
+    }
+
+    private static String[] getIntentFilterActions(IntentFilter filter) {
+        if (filter == null) {
+            return null;
+        }
+        final int actionsCount = filter.countActions();
+        final String[] actions = new String[actionsCount];
+        for (int i = 0; i < actionsCount; i++) {
+            actions[i] = filter.getAction(i);
+        }
+        return actions;
     }
 
     @Override
@@ -8914,6 +9045,13 @@
             admin.permittedAccessiblityServices = packageList;
             saveSettingsLocked(UserHandle.getCallingUserId());
         }
+        final String[] packageArray =
+                packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PERMITTED_ACCESSIBILITY_SERVICES)
+                .setAdmin(who)
+                .setStrings(packageArray)
+                .write();
         return true;
     }
 
@@ -9088,6 +9226,13 @@
             admin.permittedInputMethods = packageList;
             saveSettingsLocked(callingUserId);
         }
+        final String[] packageArray =
+                packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PERMITTED_INPUT_METHODS)
+                .setAdmin(who)
+                .setStrings(packageArray)
+                .write();
         return true;
     }
 
@@ -9628,6 +9773,7 @@
     public String[] setPackagesSuspended(ComponentName who, String callerPackage,
             String[] packageNames, boolean suspended) {
         int callingUserId = UserHandle.getCallingUserId();
+        String[] result = null;
         synchronized (getLockObject()) {
             // Ensure the caller is a DO/PO or a package access delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9635,7 +9781,8 @@
 
             long id = mInjector.binderClearCallingIdentity();
             try {
-                return mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended,
+                result = mIPackageManager
+                        .setPackagesSuspendedAsUser(packageNames, suspended,
                         null, null, null, PLATFORM_PACKAGE_NAME, callingUserId);
             } catch (RemoteException re) {
                 // Shouldn't happen.
@@ -9643,8 +9790,18 @@
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
-            return packageNames;
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PACKAGES_SUSPENDED)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(packageNames)
+                .write();
+        if (result != null) {
+            return result;
+        }
+        return packageNames;
     }
 
     @Override
@@ -9781,6 +9938,7 @@
     public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
             boolean hidden) {
         int callingUserId = UserHandle.getCallingUserId();
+        boolean result = false;
         synchronized (getLockObject()) {
             // Ensure the caller is a DO/PO or a package access delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9788,16 +9946,23 @@
 
             long id = mInjector.binderClearCallingIdentity();
             try {
-                return mIPackageManager.setApplicationHiddenSettingAsUser(
-                        packageName, hidden, callingUserId);
+                result = mIPackageManager
+                        .setApplicationHiddenSettingAsUser(packageName, hidden, callingUserId);
             } catch (RemoteException re) {
                 // shouldn't happen
                 Slog.e(LOG_TAG, "Failed to setApplicationHiddenSetting", re);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
-            return false;
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_APPLICATION_HIDDEN)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(packageName, hidden ? "hidden" : "not_hidden")
+                .write();
+        return result;
     }
 
     @Override
@@ -9862,10 +10027,18 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(packageName)
+                .write();
     }
 
     @Override
     public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
+        int numberOfAppsInstalled = 0;
         synchronized (getLockObject()) {
             // Ensure the caller is a DO/PO or an enable system app delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9887,7 +10060,6 @@
                 if (VERBOSE_LOG) {
                     Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
                 }
-                int numberOfAppsInstalled = 0;
                 if (activitiesToEnable != null) {
                     for (ResolveInfo info : activitiesToEnable) {
                         if (info.activityInfo != null) {
@@ -9903,7 +10075,6 @@
                         }
                     }
                 }
-                return numberOfAppsInstalled;
             } catch (RemoteException e) {
                 // shouldn't happen
                 Slog.wtf(LOG_TAG, "Failed to resolve intent for: " + intent);
@@ -9912,6 +10083,14 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP_WITH_INTENT)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(intent.getAction())
+                .write();
+        return numberOfAppsInstalled;
     }
 
     private boolean isSystemApp(IPackageManager pm, String packageName, int userId)
@@ -9928,6 +10107,7 @@
     @Override
     public boolean installExistingPackage(ComponentName who, String callerPackage,
             String packageName) {
+        boolean result;
         synchronized (getLockObject()) {
             // Ensure the caller is a PO or an install existing package delegate
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9946,7 +10126,8 @@
                 }
 
                 // Install the package.
-                return mIPackageManager.installExistingPackageAsUser(packageName, callingUserId,
+                result = mIPackageManager
+                        .installExistingPackageAsUser(packageName, callingUserId,
                         0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY)
                         == PackageManager.INSTALL_SUCCEEDED;
             } catch (RemoteException re) {
@@ -9956,6 +10137,16 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        if (result) {
+            final boolean isDelegate = (who == null);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.INSTALL_EXISTING_PACKAGE)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
+                    .setStrings(packageName)
+                    .write();
+        }
+        return result;
     }
 
     @Override
@@ -10019,6 +10210,13 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        final boolean isDelegate = (who == null);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_UNINSTALL_BLOCKED)
+                .setAdmin(callerPackage)
+                .setBoolean(isDelegate)
+                .setStrings(packageName)
+                .write();
     }
 
     @Override
@@ -10060,6 +10258,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALLER_ID_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -10098,6 +10301,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -10197,6 +10405,11 @@
                 saveSettingsLocked(UserHandle.getCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_BLUETOOTH_CONTACT_SHARING_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -10356,6 +10569,12 @@
                     } else {
                         sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING);
                     }
+                    DevicePolicyEventLogger
+                            .createEvent(DevicePolicyEnums.SET_LOCKTASK_MODE_ENABLED)
+                            .setAdmin(admin.info.getPackageName())
+                            .setBoolean(isEnabled)
+                            .setStrings(pkg)
+                            .write();
                 }
             }
         }
@@ -10524,6 +10743,11 @@
         synchronized (getLockObject()) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
+                    .setAdmin(who)
+                    .setBoolean(on)
+                    .write();
         }
     }
 
@@ -10553,6 +10777,10 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_USER_ICON)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -10578,6 +10806,11 @@
             }
             mLockPatternUtils.setLockScreenDisabled(disabled, userId);
             mInjector.getIWindowManager().dismissKeyguard(null /* callback */, null /* message */);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_KEYGUARD_DISABLED)
+                    .setAdmin(who)
+                    .setBoolean(disabled)
+                    .write();
         } catch (RemoteException e) {
             // Same process, does not happen.
         } finally {
@@ -10616,6 +10849,11 @@
                 saveSettingsLocked(userId);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_STATUS_BAR_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
         return true;
     }
 
@@ -11036,6 +11274,11 @@
         mContext.sendBroadcastAsUser(
                 new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED),
                 UserHandle.SYSTEM);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_SYSTEM_UPDATE_POLICY)
+                .setAdmin(who)
+                .setInt(policy != null ? policy.getPolicyType() : 0)
+                .write();
     }
 
     @Override
@@ -11584,11 +11827,15 @@
 
         final long ident = mInjector.binderClearCallingIdentity();
         try {
-            final WifiInfo wifiInfo = mInjector.getWifiManager().getConnectionInfo();
-            if (wifiInfo == null) {
+            String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
+            if (macAddresses == null) {
                 return null;
             }
-            return wifiInfo.hasRealMacAddress() ? wifiInfo.getMacAddress() : null;
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.GET_WIFI_MAC_ADDRESS)
+                    .setAdmin(admin)
+                    .write();
+            return macAddresses.length > 0 ? macAddresses[0] : null;
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
@@ -11634,6 +11881,10 @@
             if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
                 throw new IllegalStateException("Cannot be called with ongoing call on the device");
             }
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.REBOOT)
+                    .setAdmin(admin)
+                    .write();
             mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -11655,6 +11906,10 @@
                 saveSettingsLocked(userHandle);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_SHORT_SUPPORT_MESSAGE)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -11685,6 +11940,10 @@
                 saveSettingsLocked(userHandle);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_LONG_SUPPORT_MESSAGE)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -11750,6 +12009,10 @@
             admin.organizationColor = color;
             saveSettingsLocked(userHandle);
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -13153,6 +13416,7 @@
         }
 
         final long id = mInjector.binderClearCallingIdentity();
+        String ownerType = null;
         try {
             synchronized (getLockObject()) {
                 /*
@@ -13173,6 +13437,7 @@
                     bundle = new PersistableBundle();
                 }
                 if (isProfileOwner(admin, callingUserId)) {
+                    ownerType = ADMIN_TYPE_PROFILE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_PROFILE_OWNER);
                     transferProfileOwnershipLocked(admin, target, callingUserId);
@@ -13183,6 +13448,7 @@
                         notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
                     }
                 } else if (isDeviceOwner(admin, callingUserId)) {
+                    ownerType = ADMIN_TYPE_DEVICE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_DEVICE_OWNER);
                     transferDeviceOwnershipLocked(admin, target, callingUserId);
@@ -13194,6 +13460,11 @@
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.TRANSFER_OWNERSHIP)
+                .setAdmin(admin)
+                .setStrings(target.getPackageName(), ownerType)
+                .write();
     }
 
     private void prepareTransfer(ComponentName admin, ComponentName target,
@@ -13669,6 +13940,11 @@
                         mContext, updateFileDescriptor, callback, mInjector, mConstants);
             }
             updateInstaller.startInstallUpdate();
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE)
+                    .setAdmin(admin)
+                    .setBoolean(isDeviceAB())
+                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
@@ -13694,6 +13970,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+                .setAdmin(who)
+                .setStrings(packageName)
+                .write();
     }
 
     @Override
@@ -13713,6 +13994,13 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        if (isRemoved) {
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
+                    .setAdmin(who)
+                    .setStrings(packageName)
+                    .write();
+        }
         return isRemoved;
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e1b83fc..cf03d61 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -724,6 +724,10 @@
         mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
         traceEnd();
 
+        traceBeginAndSlog("StartSensorPrivacyService");
+        mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
+        traceEnd();
+
         // The sensor service needs access to package manager service, app ops
         // service, and permissions service, therefore we start it after them.
         // Start sensor service in a separate thread. Completion should be checked
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index b9cc372..f037905 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -139,7 +139,8 @@
         DROPPED_IPV6_MULTICAST_PING,
         DROPPED_IPV6_NON_ICMP_MULTICAST,
         DROPPED_802_3_FRAME,
-        DROPPED_ETHERTYPE_BLACKLISTED;
+        DROPPED_ETHERTYPE_BLACKLISTED,
+        DROPPED_ARP_REPLY_SPA_NO_HOST;
 
         // Returns the negative byte offset from the end of the APF data segment for
         // a given counter.
@@ -156,7 +157,7 @@
     /**
      * When APFv4 is supported, loads R1 with the offset of the specified counter.
      */
-    private void maybeSetCounter(ApfGenerator gen, Counter c) {
+    private void maybeSetupCounter(ApfGenerator gen, Counter c) {
         if (mApfCapabilities.hasDataAccess()) {
             gen.addLoadImmediate(Register.R1, c.offset());
         }
@@ -288,16 +289,18 @@
     private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
 
     private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
-    private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
-    private static final short ARP_OPCODE_REQUEST = 1;
-    private static final short ARP_OPCODE_REPLY = 2;
     private static final byte[] ARP_IPV4_HEADER = {
             0, 1, // Hardware type: Ethernet (1)
             8, 0, // Protocol type: IP (0x0800)
             6,    // Hardware size: 6
             4,    // Protocol size: 4
     };
-    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+    private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
+    // Opcode: ARP request (0x0001), ARP reply (0x0002)
+    private static final short ARP_OPCODE_REQUEST = 1;
+    private static final short ARP_OPCODE_REPLY = 2;
+    private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
     // Do not log ApfProgramEvents whose actual lifetimes was less than this.
     private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
     // Limit on the Black List size to cap on program usage for this
@@ -816,7 +819,7 @@
                     gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
                 }
             }
-            maybeSetCounter(gen, Counter.DROPPED_RA);
+            maybeSetupCounter(gen, Counter.DROPPED_RA);
             gen.addJump(mCountAndDropLabel);
             gen.defineLabel(nextFilterLabel);
             return filterLifetime;
@@ -883,6 +886,8 @@
         //   pass
         // if not ARP IPv4 reply or request
         //   pass
+        // if ARP reply source ip is 0.0.0.0
+        //   drop
         // if unicast ARP reply
         //   pass
         // if interface has no IPv4 address
@@ -897,18 +902,23 @@
 
         // Pass if not ARP IPv4.
         gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
-        maybeSetCounter(gen, Counter.PASSED_ARP_NON_IPV4);
+        maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
         gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
 
         // Pass if unknown ARP opcode.
         gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
         gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
-        maybeSetCounter(gen, Counter.PASSED_ARP_UNKNOWN);
+        maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
         gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
 
+        // Drop if ARP reply source IP is 0.0.0.0
+        gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
+        maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
+        gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
+
         // Pass if unicast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
+        maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
         gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
 
         // Either a unicast request, a unicast reply, or a broadcast reply.
@@ -916,17 +926,17 @@
         if (mIPv4Address == null) {
             // When there is no IPv4 address, drop GARP replies (b/29404209).
             gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetCounter(gen, Counter.DROPPED_GARP_REPLY);
+            maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
             gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
         } else {
             // When there is an IPv4 address, drop unicast/broadcast requests
             // and broadcast replies with a different target IPv4 address.
             gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
+            maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
             gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
         }
 
-        maybeSetCounter(gen, Counter.PASSED_ARP);
+        maybeSetupCounter(gen, Counter.PASSED_ARP);
         gen.addJump(mCountAndPassLabel);
     }
 
@@ -970,7 +980,7 @@
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
             gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
-            maybeSetCounter(gen, Counter.PASSED_DHCP);
+            maybeSetupCounter(gen, Counter.PASSED_DHCP);
             gen.addJump(mCountAndPassLabel);
 
             // Drop all multicasts/broadcasts.
@@ -979,30 +989,30 @@
             // If IPv4 destination address is in multicast range, drop.
             gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
             gen.addAnd(0xf0);
-            maybeSetCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
+            maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
             gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
 
             // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
-            maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
+            maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
             gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
             if (mIPv4Address != null && mIPv4PrefixLength < 31) {
-                maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
+                maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
                 int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
                 gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
             }
 
             // If L2 broadcast packet, drop.
             // TODO: can we invert this condition to fall through to the common pass case below?
-            maybeSetCounter(gen, Counter.PASSED_IPV4_UNICAST);
+            maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
             gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
             gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-            maybeSetCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
+            maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
         }
 
         // Otherwise, pass
-        maybeSetCounter(gen, Counter.PASSED_IPV4);
+        maybeSetupCounter(gen, Counter.PASSED_IPV4);
         gen.addJump(mCountAndPassLabel);
     }
 
@@ -1050,16 +1060,16 @@
 
             // Drop all other packets sent to ff00::/8 (multicast prefix).
             gen.defineLabel(dropAllIPv6MulticastsLabel);
-            maybeSetCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
+            maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
             gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
             gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
             // Not multicast. Pass.
-            maybeSetCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
+            maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
             gen.addJump(mCountAndPassLabel);
             gen.defineLabel(skipIPv6MulticastFilterLabel);
         } else {
             // If not ICMPv6, pass.
-            maybeSetCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
+            maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
             gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
         }
 
@@ -1069,7 +1079,7 @@
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
         gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
         // Drop all router solicitations (b/32833400)
-        maybeSetCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
+        maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
         gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
         // If not neighbor announcements, skip filter.
         gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
@@ -1078,7 +1088,7 @@
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
                 skipUnsolicitedMulticastNALabel);
-        maybeSetCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
+        maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
         gen.addJump(mCountAndDropLabel);
         gen.defineLabel(skipUnsolicitedMulticastNALabel);
     }
@@ -1108,7 +1118,7 @@
 
         if (mApfCapabilities.hasDataAccess()) {
             // Increment TOTAL_PACKETS
-            maybeSetCounter(gen, Counter.TOTAL_PACKETS);
+            maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
             gen.addLoadData(Register.R0, 0);  // load counter
             gen.addAdd(1);
             gen.addStoreData(Register.R0, 0);  // write-back counter
@@ -1134,12 +1144,12 @@
 
         if (mDrop802_3Frames) {
             // drop 802.3 frames (ethtype < 0x0600)
-            maybeSetCounter(gen, Counter.DROPPED_802_3_FRAME);
+            maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
             gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
         }
 
         // Handle ether-type black list
-        maybeSetCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
+        maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
         for (int p : mEthTypeBlackList) {
             gen.addJumpIfR0Equals(p, mCountAndDropLabel);
         }
@@ -1168,9 +1178,9 @@
 
         // Drop non-IP non-ARP broadcasts, pass the rest
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetCounter(gen, Counter.PASSED_NON_IP_UNICAST);
+        maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
         gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
-        maybeSetCounter(gen, Counter.DROPPED_ETH_BROADCAST);
+        maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
         gen.addJump(mCountAndDropLabel);
 
         // Add IPv6 filters:
@@ -1193,7 +1203,7 @@
 
         // Execution will reach the bottom of the program if none of the filters match,
         // which will pass the packet to the application processor.
-        maybeSetCounter(gen, Counter.PASSED_IPV6_ICMP);
+        maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
 
         // Append the count & pass trampoline, which increments the counter at the data address
         // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 6f10ed5..0c9c85a 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -27,6 +27,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     bmgrlib \
+    bu \
     services.backup \
     services.core \
     services.net
diff --git a/services/robotests/src/com/android/commands/bu/AdbBackupTest.java b/services/robotests/src/com/android/commands/bu/AdbBackupTest.java
new file mode 100644
index 0000000..6869f56
--- /dev/null
+++ b/services/robotests/src/com/android/commands/bu/AdbBackupTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.commands.bu;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.IBackupManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowParcelFileDescriptor;
+
+/** Unit tests for {@link com.android.commands.bu.Backup}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowParcelFileDescriptor.class})
+@Presubmit
+public class AdbBackupTest {
+    @Mock private IBackupManager mBackupManager;
+    private Backup mBackup;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mBackup = new Backup(mBackupManager);
+    }
+
+    @Test
+    public void testRun_whenUserNotSpecified_callsAdbBackupAsSystemUser() throws Exception {
+        mBackup.run(new String[] {"backup", "-all"});
+
+        verify(mBackupManager).isBackupServiceActive(UserHandle.USER_SYSTEM);
+    }
+
+    @Test
+    public void testRun_whenUserSpecified_callsBackupManagerAsSpecifiedUser() throws Exception {
+        mBackup.run(new String[] {"backup", "-user", "10", "-all"});
+
+        verify(mBackupManager).isBackupServiceActive(10);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index 96ef0ce..58bce1c 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -65,6 +65,8 @@
     private static final String TEST_PACKAGE = "package";
     private static final String TEST_TRANSPORT = "transport";
 
+    private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE};
+
     private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
 
     private ShadowContextWrapper mShadowContext;
@@ -555,16 +557,47 @@
         verify(mUserBackupManagerService).hasBackupPassword();
     }
 
-    /** Test that the backup service routes methods correctly to the user that requests it. */
+    /**
+     * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
+     * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} throws a
+     * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+     */
     @Test
-    public void testAdbBackup_callsAdbBackupForUser() throws Exception {
-        File testFile = new File(mContext.getFilesDir(), "test");
-        testFile.createNewFile();
-        ParcelFileDescriptor parcelFileDescriptor =
-                ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
-        String[] packages = {TEST_PACKAGE};
+    public void testAdbBackup_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(SecurityException.class,
+                () ->
+                        mBackupManagerService.adbBackup(
+                                /* userId */ mUserId,
+                                /* parcelFileDescriptor*/ null,
+                                /* includeApks */ true,
+                                /* includeObbs */ true,
+                                /* includeShared */ true,
+                                /* doWidgets */ true,
+                                /* doAllApps */ true,
+                                /* includeSystem */ true,
+                                /* doCompress */ true,
+                                /* doKeyValue */ true,
+                                null));
+
+    }
+
+    /**
+     * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
+     * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} does not require
+     * the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is the
+     * same as the target user id.
+     */
+    @Test
+    public void testAdbBackup_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
+        ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
 
         mBackupManagerService.adbBackup(
+                /* userId */ mUserId,
                 parcelFileDescriptor,
                 /* includeApks */ true,
                 /* includeObbs */ true,
@@ -574,7 +607,7 @@
                 /* includeSystem */ true,
                 /* doCompress */ true,
                 /* doKeyValue */ true,
-                packages);
+                ADB_TEST_PACKAGES);
 
         verify(mUserBackupManagerService)
                 .adbBackup(
@@ -587,18 +620,82 @@
                         /* includeSystem */ true,
                         /* doCompress */ true,
                         /* doKeyValue */ true,
-                        packages);
+                        ADB_TEST_PACKAGES);
+    }
+
+    /** Test that the backup service routes methods correctly to the user that requests it. */
+    @Test
+    public void testAdbBackup_callsAdbBackupForUser() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
+
+        mBackupManagerService.adbBackup(
+                /* userId */ mUserId,
+                parcelFileDescriptor,
+                /* includeApks */ true,
+                /* includeObbs */ true,
+                /* includeShared */ true,
+                /* doWidgets */ true,
+                /* doAllApps */ true,
+                /* includeSystem */ true,
+                /* doCompress */ true,
+                /* doKeyValue */ true,
+                ADB_TEST_PACKAGES);
+
+        verify(mUserBackupManagerService)
+                .adbBackup(
+                        parcelFileDescriptor,
+                        /* includeApks */ true,
+                        /* includeObbs */ true,
+                        /* includeShared */ true,
+                        /* doWidgets */ true,
+                        /* doAllApps */ true,
+                        /* includeSystem */ true,
+                        /* doCompress */ true,
+                        /* doKeyValue */ true,
+                        ADB_TEST_PACKAGES);
+    }
+
+    /**
+     * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} throws
+     * a {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL
+     * permission.
+     */
+    @Test
+    public void testAdbRestore_withoutPermission_throwsSecurityException() {
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        expectThrows(SecurityException.class,
+                () -> mBackupManagerService.adbRestore(mUserId, null));
+
+    }
+
+    /**
+     * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} does
+     * not require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id
+     * is the same as the target user id.
+     */
+    @Test
+    public void testAdbRestore_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
+        ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
+        mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+        ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
+
+        mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
+
+        verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
     @Test
     public void testAdbRestore_callsAdbRestoreForUser() throws Exception {
-        File testFile = new File(mContext.getFilesDir(), "test");
-        testFile.createNewFile();
-        ParcelFileDescriptor parcelFileDescriptor =
-                ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
+        mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
 
-        mBackupManagerService.adbRestore(parcelFileDescriptor);
+        ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
+
+        mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
 
         verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
     }
@@ -638,4 +735,10 @@
 
         verify(mUserBackupManagerService).dump(fileDescriptor, printWriter, args);
     }
+
+    private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception {
+        File testFile = new File(mContext.getFilesDir(), "test");
+        testFile.createNewFile();
+        return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 3979a8e..148faad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -25,7 +25,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -42,7 +41,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -66,7 +64,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -96,6 +93,8 @@
     @Mock
     private ContentResolver mMockResolver;
     @Mock
+    private Context mMockContext;
+    @Mock
     private IActivityManager mIActivityManager;
     @Mock
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
@@ -221,17 +220,16 @@
                 .thenReturn(STANDBY_BUCKET_ACTIVE);
         doReturn(Looper.getMainLooper()).when(Looper::myLooper);
 
-        final Context context = spy(InstrumentationRegistry.getTargetContext());
-        when(context.getContentResolver()).thenReturn(mMockResolver);
-        doNothing().when(mMockResolver).registerContentObserver(any(), anyBoolean(), any());
+        when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
         doReturn("min_futurity=0").when(() ->
                 Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS));
-        mInjector = new Injector(context);
-        mService = new AlarmManagerService(context, mInjector);
+        mInjector = new Injector(mMockContext);
+        mService = new AlarmManagerService(mMockContext, mInjector);
         spyOn(mService);
         doNothing().when(mService).publishBinderService(any(), any());
         mService.onStart();
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        spyOn(mService.mHandler);
 
         assertEquals(0, mService.mConstants.MIN_FUTURITY);
         assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
@@ -273,7 +271,7 @@
 
         final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
                 ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
-        verify(alarmPi).send(any(Context.class), eq(0), any(Intent.class),
+        verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
                 onFinishedCaptor.capture(), any(Handler.class), isNull(), any());
         verify(mWakeLock).acquire();
         onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null);
@@ -423,11 +421,23 @@
         assertNotNull(restrictedAlarms.get(TEST_CALLING_UID));
 
         listenerArgumentCaptor.getValue().unblockAlarmsForUid(TEST_CALLING_UID);
-        verify(alarmPi).send(any(Context.class), eq(0), any(Intent.class), any(),
+        verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(),
                 any(Handler.class), isNull(), any());
         assertNull(restrictedAlarms.get(TEST_CALLING_UID));
     }
 
+    @Test
+    public void sendsTimeTickOnInteractive() {
+        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        // Stubbing so the handler doesn't actually run the runnable.
+        doReturn(true).when(mService.mHandler).post(runnableCaptor.capture());
+        // change interactive state: false -> true
+        mService.interactiveStateChangedLocked(false);
+        mService.interactiveStateChangedLocked(true);
+        runnableCaptor.getValue().run();
+        verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL);
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 04a8408..cff0521 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -203,6 +203,8 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(LocalServices.class)
                 .startMocking();
+        spyOn(getContext());
+        doReturn(null).when(getContext()).registerReceiver(any(), any());
         doReturn(mock(ActivityManagerInternal.class))
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(ActivityTaskManagerInternal.class))
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 71aec23..f1cd0cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -30,6 +30,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -62,6 +63,7 @@
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.controllers.QuotaController.ExecutionStats;
 import com.android.server.job.controllers.QuotaController.TimingSession;
 
 import org.junit.After;
@@ -131,13 +133,18 @@
         doReturn(mock(PackageManagerInternal.class))
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
 
-        // Freeze the clocks at this moment in time
+        // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+        // in the past, and QuotaController sometimes floors values at 0, so if the test time
+        // causes sessions with negative timestamps, they will fail.
         JobSchedulerService.sSystemClock =
-                Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
-        JobSchedulerService.sUptimeMillisClock =
-                Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
-        JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+                getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
+                        24 * HOUR_IN_MILLIS);
+        JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
+                Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
+                24 * HOUR_IN_MILLIS);
+        JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
+                24 * HOUR_IN_MILLIS);
 
         // Initialize real objects.
         // Capture the listeners.
@@ -270,7 +277,77 @@
     }
 
     @Test
-    public void testGetTrailingExecutionTimeLocked_NoTimer() {
+    public void testOnAppRemovedLocked() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test.remove",
+                createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test.remove",
+                createTimingSession(
+                        now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test.remove",
+                createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
+        // Test that another app isn't affected.
+        TimingSession one = createTimingSession(
+                now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
+        TimingSession two = createTimingSession(
+                now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
+        List<TimingSession> expected = new ArrayList<>();
+        // Added in correct (chronological) order.
+        expected.add(two);
+        expected.add(one);
+        mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
+        mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
+
+        ExecutionStats expectedStats = new ExecutionStats();
+        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+
+        mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+        assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
+        assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
+        assertNotEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
+    }
+
+    @Test
+    public void testOnUserRemovedLocked() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(
+                        now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
+        // Test that another user isn't affected.
+        TimingSession one = createTimingSession(
+                now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
+        TimingSession two = createTimingSession(
+                now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
+        List<TimingSession> expected = new ArrayList<>();
+        // Added in correct (chronological) order.
+        expected.add(two);
+        expected.add(one);
+        mQuotaController.saveTimingSession(10, "com.android.test", two);
+        mQuotaController.saveTimingSession(10, "com.android.test", one);
+
+        ExecutionStats expectedStats = new ExecutionStats();
+        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+
+        mQuotaController.onUserRemovedLocked(0);
+        assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
+        assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+        assertNotEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
+    }
+
+    @Test
+    public void testUpdateExecutionStatsLocked_NoTimer() {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Added in chronological order.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -286,32 +363,288 @@
         mQuotaController.saveTimingSession(0, "com.android.test",
                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
 
-        assertEquals(0, mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                MINUTE_IN_MILLIS));
-        assertEquals(2 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        3 * MINUTE_IN_MILLIS));
-        assertEquals(4 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        5 * MINUTE_IN_MILLIS));
-        assertEquals(4 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        49 * MINUTE_IN_MILLIS));
-        assertEquals(5 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        50 * MINUTE_IN_MILLIS));
-        assertEquals(6 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        HOUR_IN_MILLIS));
-        assertEquals(11 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        2 * HOUR_IN_MILLIS));
-        assertEquals(12 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        3 * HOUR_IN_MILLIS));
-        assertEquals(22 * MINUTE_IN_MILLIS,
-                mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
-                        6 * HOUR_IN_MILLIS));
+        // Test an app that hasn't had any activity.
+        ExecutionStats expectedStats = new ExecutionStats();
+        ExecutionStats inputStats = new ExecutionStats();
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+        // Invalid time is now +24 hours since there are no sessions at all for the app.
+        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
+        // Invalid time is now +18 hours since there are no sessions in the window but the earliest
+        // session is 6 hours ago.
+        expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 0;
+        expectedStats.bgJobCountInWindow = 0;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
+        // Invalid time is now since the session straddles the window cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 3;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
+        // Invalid time is now since the start of the session is at the very edge of the window
+        // cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 3;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
+        // Invalid time is now +44 minutes since the earliest session in the window is now-5
+        // minutes.
+        expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 3;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
+        // Invalid time is now since the session is at the very edge of the window cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 4;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+        // Invalid time is now since the start of the session is at the very edge of the window
+        // cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 5;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        // Invalid time is now since the session straddles the window cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 10;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
+        // Invalid time is now +59 minutes since the earliest session in the window is now-121
+        // minutes.
+        expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 10;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
+        // Invalid time is now since the start of the session is at the very edge of the window
+        // cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 15;
+        expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 15;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+        mQuotaController.getTimingSessions(0, "com.android.test")
+                .add(0,
+                        createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
+        // before the end of the max period cutoff time.
+        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 15;
+        expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 18;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        mQuotaController.getTimingSessions(0, "com.android.test")
+                .add(0,
+                        createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+                                2 * MINUTE_IN_MILLIS, 2));
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        // Invalid time is now since the earlist session straddles the max period cutoff time.
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 15;
+        expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 20;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+        assertEquals(expectedStats, inputStats);
+    }
+
+    /**
+     * Tests that getExecutionStatsLocked returns the correct stats.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Values() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+
+        ExecutionStats expectedStats = new ExecutionStats();
+
+        // Active
+        expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 5;
+        expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 20;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+
+        // Working
+        expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.invalidTimeElapsed = now;
+        expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 10;
+        expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 20;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+
+        // Frequent
+        expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 15;
+        expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 20;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
+
+        // Rare
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 20;
+        expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 20;
+        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+                + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+    }
+
+    /**
+     * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Caching() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", ACTIVE_INDEX);
+        final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", WORKING_INDEX);
+        final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", FREQUENT_INDEX);
+        final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", RARE_INDEX);
+
+        // Advance clock so that the working stats shouldn't be the same.
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+        // Change frequent bucket size so that the stats need to be recalculated.
+        mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
+        mQuotaController.onConstantsUpdatedLocked();
+
+        ExecutionStats expectedStats = new ExecutionStats();
+        expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
+        expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+        expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
+        expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
+        expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
+        expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
+        expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
+        final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", ACTIVE_INDEX);
+        // Stats for the same bucket should use the same object.
+        assertTrue(originalStatsActive == newStatsActive);
+        assertEquals(expectedStats, newStatsActive);
+
+        expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
+        expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+        expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
+        expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
+        expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
+        final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", WORKING_INDEX);
+        assertTrue(originalStatsWorking == newStatsWorking);
+        assertNotEquals(expectedStats, newStatsWorking);
+
+        expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
+        expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+        expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
+        expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
+        expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
+        final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", FREQUENT_INDEX);
+        assertTrue(originalStatsFrequent == newStatsFrequent);
+        assertNotEquals(expectedStats, newStatsFrequent);
+
+        expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
+        expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+        expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
+        expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
+        expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
+        final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
+                "com.android.test", RARE_INDEX);
+        assertTrue(originalStatsRare == newStatsRare);
+        assertEquals(expectedStats, newStatsRare);
     }
 
     @Test
@@ -340,6 +673,56 @@
     }
 
     @Test
+    public void testMaybeScheduleStartAlarmLocked_Active() {
+        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaController);
+        doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+        // Active window size is 10 minutes.
+        final int standbyBucket = ACTIVE_INDEX;
+
+        // No sessions saved yet.
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        // Test with timing sessions out of window but still under max execution limit.
+        final long expectedAlarmTime =
+                (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+        mQuotaController.prepareForExecutionLocked(jobStatus);
+        advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+        // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
+        // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
+        // longer has quota.
+        assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
+                any(), any());
+    }
+
+    @Test
     public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
@@ -525,8 +908,8 @@
 
         // Start in ACTIVE bucket.
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
-        inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
-                any());
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
         inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
 
         // And down from there.
@@ -566,6 +949,124 @@
         inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
+
+    /**
+     * Tests that the start alarm is properly rescheduled if the earliest session that contributes
+     * to the app being out of quota contributes less than the quota buffer time.
+     */
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
+        // Use the default values
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+        mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+    }
+
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
+        // Make sure any new value is used correctly.
+        mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
+        mQuotaController.onConstantsUpdatedLocked();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+        mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+    }
+
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
+        // Make sure any new value is used correctly.
+        mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
+        mQuotaController.onConstantsUpdatedLocked();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+        mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+    }
+
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
+        // Make sure any new value is used correctly.
+        mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
+        mQuotaController.onConstantsUpdatedLocked();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+        mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+    }
+
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
+        // Make sure any new value is used correctly.
+        mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
+        mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
+        mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
+        mQuotaController.onConstantsUpdatedLocked();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+        mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+        runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+    }
+
+    private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
+        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaController);
+        doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        // Working set window size is 2 hours.
+        final int standbyBucket = WORKING_INDEX;
+        final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
+        final long remainingTimeMs =
+                mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
+
+        // Session straddles edge of bucket window. Only the contribution should be counted towards
+        // the quota.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
+                        3 * MINUTE_IN_MILLIS + contributionMs, 3));
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
+        // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
+        // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
+        final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
+                + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+    }
+
+
+    private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
+        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaController);
+        doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        // Working set window size is 2 hours.
+        final int standbyBucket = WORKING_INDEX;
+        final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
+        final long remainingTimeMs =
+                mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
+
+        // Session straddles edge of 24 hour window. Only the contribution should be counted towards
+        // the quota.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
+                        3 * MINUTE_IN_MILLIS + contributionMs, 3));
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
+        // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
+        // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
+        final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
+                //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
+                + 24 * HOUR_IN_MILLIS
+                + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
+        mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+                standbyBucket);
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+    }
+
     /** Tests that QuotaController doesn't throttle if throttling is turned off. */
     @Test
     public void testThrottleToggling() throws Exception {
@@ -598,6 +1099,7 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -608,6 +1110,7 @@
         assertEquals(45 * MINUTE_IN_MILLIS,
                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+        assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
     }
 
     @Test
@@ -619,6 +1122,7 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -628,6 +1132,7 @@
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+        assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
 
         // Test larger than a day. Controller should cap at one day.
         mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -636,6 +1141,7 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -645,6 +1151,7 @@
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
     }
 
     /** Tests that TimingSessions aren't saved when the device is charging. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
new file mode 100644
index 0000000..494677d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -0,0 +1,886 @@
+/*
+ * 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 com.android.server.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeControllerTest {
+    private static final long SECOND_IN_MILLIS = 1000L;
+    private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+    private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+    private static final String TAG_DEADLINE = "*job.deadline*";
+    private static final String TAG_DELAY = "*job.delay*";
+    private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+    private static final int SOURCE_USER_ID = 0;
+
+    private Constants mConstants;
+    private TimeController mTimeController;
+
+    private MockitoSession mMockingSession;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private JobSchedulerService mJobSchedulerService;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+        // Use default constants for now.
+        mConstants = new Constants();
+
+        // Called in StateController constructor.
+        when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+        when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+        when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+        // Called in TimeController constructor.
+        when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        // Used in JobStatus.
+        doReturn(mock(PackageManagerInternal.class))
+                .when(() -> LocalServices.getService(PackageManagerInternal.class));
+
+        // Freeze the clocks at this moment in time
+        JobSchedulerService.sSystemClock =
+                Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+        JobSchedulerService.sUptimeMillisClock =
+                Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+        // Initialize real objects.
+        mTimeController = new TimeController(mJobSchedulerService);
+        spyOn(mTimeController);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private Clock getAdvancedClock(Clock clock, long incrementMs) {
+        return Clock.offset(clock, Duration.ofMillis(incrementMs));
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+                JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+    }
+
+    private static JobInfo.Builder createJob() {
+        return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+        JobInfo jobInfo = job.build();
+        return JobStatus.createFromJobInfo(
+                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_AlreadySatisfied() {
+        JobStatus delaySatisfied = createJobStatus(
+                "testMaybeStartTrackingJobLocked_AlreadySatisfied",
+                createJob().setMinimumLatency(1));
+        JobStatus deadlineSatisfied = createJobStatus(
+                "testMaybeStartTrackingJobLocked_AlreadySatisfied",
+                createJob().setOverrideDeadline(1));
+
+        advanceElapsedClock(5);
+
+        mTimeController.maybeStartTrackingJobLocked(delaySatisfied, null);
+        mTimeController.maybeStartTrackingJobLocked(deadlineSatisfied, null);
+        verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayInOrder_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestMaybeStartTrackingJobLocked_DelayInOrder();
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestMaybeStartTrackingJobLocked_DelayInOrder();
+    }
+
+    private void runTestMaybeStartTrackingJobLocked_DelayInOrder() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayReverseOrder_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestMaybeStartTrackingJobLocked_DelayReverseOrder();
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestMaybeStartTrackingJobLocked_DelayReverseOrder();
+    }
+
+    private void runTestMaybeStartTrackingJobLocked_DelayReverseOrder() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+                        any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+                        any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        // Middle alarm shouldn't be set since it won't be ready.
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineInOrder_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestMaybeStartTrackingJobLocked_DeadlineInOrder();
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestMaybeStartTrackingJobLocked_DeadlineInOrder();
+    }
+
+    private void runTestMaybeStartTrackingJobLocked_DeadlineInOrder() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder();
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder();
+    }
+
+    private void runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+                        any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+    }
+
+    @Test
+    public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+                        any(), any(), any());
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        // Middle alarm should be skipped since the job wouldn't be ready.
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(),
+                        any());
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+    }
+
+    @Test
+    public void testJobSkipToggling() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus(
+                "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        // Starting off with the skipping off, we should still set an alarm for the earlier job.
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+
+        // Turn it on, use alarm for later job.
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+                        any(), any(), any());
+
+        // Back off, use alarm for earlier job.
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+    }
+
+    @Test
+    public void testCheckExpiredDelaysAndResetAlarm_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestCheckExpiredDelaysAndResetAlarm();
+    }
+
+    @Test
+    public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestCheckExpiredDelaysAndResetAlarm();
+    }
+
+    private void runTestCheckExpiredDelaysAndResetAlarm() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDelaysAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDelaysAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+                        any(), any());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDelaysAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDelaysAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        // Middle job wouldn't be ready, so its alarm should be skipped.
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+                        any(), any());
+
+        advanceElapsedClock(55 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDelaysAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testCheckExpiredDeadlinesAndResetAlarm_NoSkipping() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+
+        runTestCheckExpiredDeadlinesAndResetAlarm();
+    }
+
+    @Test
+    public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_AllReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+
+        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+        runTestCheckExpiredDeadlinesAndResetAlarm();
+    }
+
+    private void runTestCheckExpiredDeadlinesAndResetAlarm() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDeadlinesAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDeadlinesAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+                        any(), any(), any());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDeadlinesAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_SomeNotReady() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDeadlinesAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        // Middle job wouldn't be ready, so its alarm should be skipped.
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+                        any(), any(), any());
+
+        advanceElapsedClock(55 * MINUTE_IN_MILLIS);
+
+        mTimeController.checkExpiredDeadlinesAndResetAlarm();
+        assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testEvaluateStateLocked_SkippingOff() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+        mTimeController.onConstantsUpdatedLocked();
+        JobStatus job = createJobStatus("testEvaluateStateLocked_SkippingOff",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+
+        mTimeController.evaluateStateLocked(job);
+        verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+    }
+
+    @Test
+    public void testEvaluateStateLocked_SkippingOn_Delay() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+                createJob().setMinimumLatency(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+                createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+                createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+
+        // Test evaluating something after the current deadline.
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        mTimeController.evaluateStateLocked(jobLatest);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+
+        // Test evaluating something before the current deadline.
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+                        any(), any(), any());
+        // Job goes back to not being ready. Middle is still true, so use that alarm.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DELAY), any(), any(), any());
+        // Turn middle off. Latest is true, so use that alarm.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        mTimeController.evaluateStateLocked(jobMiddle);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DELAY), any(), any(), any());
+    }
+
+    @Test
+    public void testEvaluateStateLocked_SkippingOn_Deadline() {
+        mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        mTimeController.onConstantsUpdatedLocked();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+        JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+                createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+        JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+                createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+        JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+                createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+
+        // Test evaluating something after the current deadline.
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+        mTimeController.evaluateStateLocked(jobLatest);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+
+        // Test evaluating something before the current deadline.
+        doReturn(true).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+        // Job goes back to not being ready. Middle is still true, so use that alarm.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+        mTimeController.evaluateStateLocked(jobEarliest);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+        // Turn middle off. Latest is true, so use that alarm.
+        doReturn(false).when(mTimeController)
+                .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+        mTimeController.evaluateStateLocked(jobMiddle);
+        inOrder.verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+                        eq(TAG_DEADLINE), any(), any(), any());
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d7b1cb4..4ee9551 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -70,7 +70,7 @@
         "liblzma",
         "libnativehelper",
         "libui",
-        "libunwind",
+        "libunwindstack",
         "libutils",
         "netd_aidl_interface-cpp",
     ],
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 57da0af..50ccd1f 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -32,4 +32,6 @@
     <string name="config_batterySaverDeviceSpecificConfig_1"></string>
     <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
     <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
+    <string name="module_1_name" translatable="false">module_1_name</string>
+    <string name="module_2_name" translatable="false">module_2_name</string>
 </resources>
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata1.xml b/services/tests/servicestests/res/xml/unparseable_metadata1.xml
new file mode 100644
index 0000000..73967f1
--- /dev/null
+++ b/services/tests/servicestests/res/xml/unparseable_metadata1.xml
@@ -0,0 +1,4 @@
+<not-module-metadata>
+  <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+  <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</not-module-metadata>
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata2.xml b/services/tests/servicestests/res/xml/unparseable_metadata2.xml
new file mode 100644
index 0000000..bb5a1b2
--- /dev/null
+++ b/services/tests/servicestests/res/xml/unparseable_metadata2.xml
@@ -0,0 +1,4 @@
+<module-metadata>
+  <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+  <not-module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/res/xml/well_formed_metadata.xml b/services/tests/servicestests/res/xml/well_formed_metadata.xml
new file mode 100644
index 0000000..17cc369
--- /dev/null
+++ b/services/tests/servicestests/res/xml/well_formed_metadata.xml
@@ -0,0 +1,4 @@
+<module-metadata>
+  <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+  <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/res/xml/well_formed_metadata2.xml b/services/tests/servicestests/res/xml/well_formed_metadata2.xml
new file mode 100644
index 0000000..47279e6
--- /dev/null
+++ b/services/tests/servicestests/res/xml/well_formed_metadata2.xml
@@ -0,0 +1,5 @@
+<module-metadata>
+  <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"
+    attribute1="attribute1" attribute2="attribute2" />
+  <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
index 89c7b71..93cac08 100644
--- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -20,6 +20,7 @@
 import static com.android.server.am.MemoryStatUtil.JIFFY_NANOS;
 import static com.android.server.am.MemoryStatUtil.MemoryStat;
 import static com.android.server.am.MemoryStatUtil.PAGE_SIZE;
+import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
 import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs;
@@ -31,6 +32,7 @@
 
 import org.junit.Test;
 
+import java.io.ByteArrayOutputStream;
 import java.util.Collections;
 
 /**
@@ -232,4 +234,41 @@
 
         assertEquals(0, parseVmHWMFromProcfs(null));
     }
+
+    @Test
+    public void testParseCmdlineFromProcfs_invalidValue() {
+        byte[] nothing = new byte[] {0x00, 0x74, 0x65, 0x73, 0x74}; // \0test
+
+        assertEquals("", parseCmdlineFromProcfs(bytesToString(nothing)));
+    }
+
+    @Test
+    public void testParseCmdlineFromProcfs_correctValue_noNullBytes() {
+        assertEquals("com.google.app", parseCmdlineFromProcfs("com.google.app"));
+    }
+
+    @Test
+    public void testParseCmdlineFromProcfs_correctValue_withNullBytes() {
+        byte[] trailing = new byte[] {0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00}; // test\0\0\0
+
+        assertEquals("test", parseCmdlineFromProcfs(bytesToString(trailing)));
+
+        // test\0\0test
+        byte[] inside = new byte[] {0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74};
+
+        assertEquals("test", parseCmdlineFromProcfs(bytesToString(trailing)));
+    }
+
+    @Test
+    public void testParseCmdlineFromProcfs_emptyContents() {
+        assertEquals("", parseCmdlineFromProcfs(""));
+
+        assertEquals("", parseCmdlineFromProcfs(null));
+    }
+
+    private static String bytesToString(byte[] bytes) {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        output.write(bytes, 0, bytes.length);
+        return output.toString();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index 751ed9b..d7a398e 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -487,8 +487,8 @@
 
     @Test
     public void adbBackup_calledBeforeInitialize_ignored() throws RemoteException {
-        mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
-                true,
+        mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
+                true, true, true, true, true, true,
                 PACKAGE_NAMES);
         verifyNoMoreInteractions(mBackupManagerServiceMock);
     }
@@ -496,12 +496,11 @@
     @Test
     public void adbBackup_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
-        mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
-                true,
+        mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
+                true, true, true, true, true, true,
                 PACKAGE_NAMES);
-        verify(mBackupManagerServiceMock).adbBackup(mParcelFileDescriptorMock, true, true, true,
-                true,
-                true, true, true, true, PACKAGE_NAMES);
+        verify(mBackupManagerServiceMock).adbBackup(mUserId, mParcelFileDescriptorMock, true,
+                true, true, true, true, true, true, true, PACKAGE_NAMES);
     }
 
     @Test
@@ -519,15 +518,15 @@
 
     @Test
     public void adbRestore_calledBeforeInitialize_ignored() throws RemoteException {
-        mTrampoline.adbRestore(mParcelFileDescriptorMock);
+        mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
         verifyNoMoreInteractions(mBackupManagerServiceMock);
     }
 
     @Test
     public void adbRestore_forwarded() throws RemoteException {
         mTrampoline.initializeService(UserHandle.USER_SYSTEM);
-        mTrampoline.adbRestore(mParcelFileDescriptorMock);
-        verify(mBackupManagerServiceMock).adbRestore(mParcelFileDescriptorMock);
+        mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
+        verify(mBackupManagerServiceMock).adbRestore(mUserId, mParcelFileDescriptorMock);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java
new file mode 100644
index 0000000..b24bca8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.server.devicepolicy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyEventLogger;
+import android.content.ComponentName;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DevicePolicyEventLogger}.
+ * <p/>
+ * Run with <code>atest DevicePolicyEventLoggerTest</code>.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DevicePolicyEventLoggerTest {
+    @Test
+    public void testAllFields() {
+        final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
+                .createEvent(5)
+                .setBoolean(true)
+                .setStrings("string1", "string2", "string3")
+                .setAdmin(new ComponentName("com.test.package", ".TestAdmin"))
+                .setInt(4321)
+                .setTimePeriod(1234L);
+        assertThat(eventLogger.getEventId()).isEqualTo(5);
+        assertThat(eventLogger.getBoolean()).isTrue();
+        assertThat(eventLogger.getStringArray())
+                .isEqualTo(new String[] {"string1", "string2", "string3"});
+        assertThat(eventLogger.getAdminPackageName()).isEqualTo("com.test.package");
+        assertThat(eventLogger.getInt()).isEqualTo(4321);
+        assertThat(eventLogger.getTimePeriod()).isEqualTo(1234L);
+    }
+
+    @Test
+    public void testStrings() {
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings("string1", "string2", "string3").getStringArray())
+                .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings("string1", new String[] {"string2", "string3"}).getStringArray())
+                .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings("string1", "string2", new String[] {"string3"}).getStringArray())
+                .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings((String) null).getStringArray())
+                .isEqualTo(new String[] {null});
+
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings((String[]) null).getStringArray())
+                .isEqualTo(null);
+
+        assertThrows(NullPointerException.class, () -> DevicePolicyEventLogger
+                .createEvent(0)
+                .setStrings("string1", "string2", null));
+    }
+
+    @Test
+    public void testAdmins() {
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setAdmin("com.package.name")
+                .getAdminPackageName())
+                .isEqualTo("com.package.name");
+
+        assertThat(DevicePolicyEventLogger
+                .createEvent(0)
+                .setAdmin(new ComponentName("com.package.name", ".TestAdmin"))
+                .getAdminPackageName())
+                .isEqualTo("com.package.name");
+    }
+
+    @Test
+    public void testDefaultValues() {
+        final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
+                .createEvent(0);
+        assertThat(eventLogger.getEventId()).isEqualTo(0);
+        assertThat(eventLogger.getBoolean()).isFalse();
+        assertThat(eventLogger.getStringArray()).isEqualTo(null);
+        assertThat(eventLogger.getAdminPackageName()).isEqualTo(null);
+        assertThat(eventLogger.getInt()).isEqualTo(0);
+        assertThat(eventLogger.getTimePeriod()).isEqualTo(0L);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 729fac5..38e8ac2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -72,7 +72,6 @@
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.net.Uri;
-import android.net.wifi.WifiInfo;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.Process;
@@ -1989,17 +1988,16 @@
         // Test 4, Caller is DO now.
         assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
 
-        // 4-1.  But no WifiInfo.
+        // 4-1.  But WifiManager is not ready.
         assertNull(dpm.getWifiMacAddress(admin1));
 
-        // 4-2.  Returns WifiInfo, but with the default MAC.
-        when(getServices().wifiManager.getConnectionInfo()).thenReturn(new WifiInfo());
+        // 4-2.  When WifiManager returns an empty array, dpm should also output null.
+        when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(new String[0]);
         assertNull(dpm.getWifiMacAddress(admin1));
 
         // 4-3. With a real MAC address.
-        final WifiInfo wi = new WifiInfo();
-        wi.setMacAddress("11:22:33:44:55:66");
-        when(getServices().wifiManager.getConnectionInfo()).thenReturn(wi);
+        final String[] macAddresses = new String[]{"11:22:33:44:55:66"};
+        when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(macAddresses);
         assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
new file mode 100644
index 0000000..bd3d9ab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.ModuleInfo;
+import android.test.InstrumentationTestCase;
+
+import com.android.frameworks.servicestests.R;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ModuleInfoProviderTest extends InstrumentationTestCase {
+    public void testSuccessfulParse() {
+        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+        List<ModuleInfo> mi = provider.getInstalledModules(0);
+        assertEquals(2, mi.size());
+
+        Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
+                m1.getPackageName().compareTo(m1.getPackageName()));
+        assertEquals("com.android.module1", mi.get(0).getPackageName());
+        assertEquals("com.android.module2", mi.get(1).getPackageName());
+
+        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+        assertEquals("com.android.module1", mi1.getPackageName());
+        assertEquals("module_1_name", mi1.getName());
+        assertEquals(false, mi1.isHidden());
+
+        ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0);
+        assertEquals("com.android.module2", mi2.getPackageName());
+        assertEquals("module_2_name", mi2.getName());
+        assertEquals(true, mi2.isHidden());
+    }
+
+    public void testParseFailure_incorrectTopLevelElement() {
+        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
+        assertEquals(0, provider.getInstalledModules(0).size());
+    }
+
+    public void testParseFailure_incorrectModuleElement() {
+        ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
+        assertEquals(0, provider.getInstalledModules(0).size());
+    }
+
+    public void testParse_unknownAttributesIgnored() {
+        ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+        List<ModuleInfo> mi = provider.getInstalledModules(0);
+        assertEquals(2, mi.size());
+
+        ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+        assertEquals("com.android.module1", mi1.getPackageName());
+        assertEquals("module_1_name", mi1.getName());
+        assertEquals(false, mi1.isHidden());
+    }
+
+    /**
+     * Constructs an {@code ModuleInfoProvider} using the test package resources.
+     */
+    private ModuleInfoProvider getProvider(int resourceId) {
+        final Context ctx = getInstrumentation().getContext();
+        return new ModuleInfoProvider(ctx.getResources().getXml(resourceId), ctx.getResources());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index eb4cc4e..f4cdc8c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -16,9 +16,12 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.MAX_FILES_PER_OWNER;
 import static com.android.server.pm.dex.PackageDynamicCodeLoading.escape;
 import static com.android.server.pm.dex.PackageDynamicCodeLoading.unescape;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -119,6 +122,24 @@
     }
 
     @Test
+    public void testRecord_tooManyFiles_ignored() {
+        PackageDynamicCodeLoading info = new PackageDynamicCodeLoading();
+        int tooManyFiles = MAX_FILES_PER_OWNER + 1;
+        for (int i = 1; i <= tooManyFiles; i++) {
+            Entry entry = new Entry("owning.package", "/path/file" + i, 'D', 10, "loading.package");
+            boolean added = record(info, entry);
+            Set<Entry> entries = entriesFrom(info);
+            if (i < tooManyFiles) {
+                assertThat(entries).contains(entry);
+                assertTrue(added);
+            } else {
+                assertThat(entries).doesNotContain(entry);
+                assertFalse(added);
+            }
+        }
+    }
+
+    @Test
     public void testClear() {
         Entry[] entries = {
                 new Entry("owner1", "file1", 'D', 10, "loader1"),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 41d5a1c..afbe6bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -27,6 +27,8 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -417,7 +419,7 @@
 
         verifyLights();
         assertTrue(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -430,7 +432,7 @@
         verifyNeverVibrate();
         verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -441,7 +443,7 @@
 
         verifyBeep();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -452,7 +454,7 @@
 
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -490,7 +492,7 @@
         verifyNeverBeep();
         verifyNeverVibrate();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -503,7 +505,7 @@
         verifyNeverBeep();
         verifyNeverVibrate();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -520,7 +522,7 @@
         verifyBeepLooped();
         verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -549,7 +551,7 @@
 
         verifyNeverStopAudio();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -563,9 +565,9 @@
 
         verifyNeverStopAudio();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     /**
@@ -602,7 +604,7 @@
         mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
         verifyNeverStopAudio();
         assertTrue(other.isInterruptive());
-        assertTrue(other.getAudiblyAlerted());
+        assertNotEquals(-1, other.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -628,14 +630,14 @@
         // set up internal state
         mService.buzzBeepBlinkLocked(r);
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         Mockito.reset(mRingtonePlayer);
 
         // quiet update should stop making noise
         mService.buzzBeepBlinkLocked(s);
         verifyStopAudio();
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -647,14 +649,14 @@
         // set up internal state
         mService.buzzBeepBlinkLocked(r);
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         Mockito.reset(mRingtonePlayer);
 
         // stop making noise - this is a weird corner case, but quiet should override once
         mService.buzzBeepBlinkLocked(s);
         verifyStopAudio();
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -671,7 +673,7 @@
         verify(mService, times(1)).playInCallNotification();
         verifyNeverBeep(); // doesn't play normal beep
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -691,7 +693,7 @@
                 eq(effect), anyString(),
                 (AudioAttributes) anyObject());
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -709,7 +711,7 @@
         verifyNeverVibrate();
         verifyBeepLooped();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -729,7 +731,7 @@
         verify(mRingtonePlayer, never()).playAsync
                 (anyObject(), anyObject(), anyBoolean(), anyObject());
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -746,7 +748,7 @@
 
         verifyDelayedVibrateLooped();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -758,7 +760,7 @@
         verifyNeverBeep();
         verifyVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -768,7 +770,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyVibrateLooped();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -784,7 +786,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -795,7 +797,7 @@
 
         verifyNeverBeep();
         assertFalse(child.isInterruptive());
-        assertFalse(child.getAudiblyAlerted());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -808,7 +810,7 @@
         verifyBeepLooped();
         // summaries are never interruptive for notification counts
         assertFalse(summary.isInterruptive());
-        assertTrue(summary.getAudiblyAlerted());
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -819,7 +821,7 @@
 
         verifyBeepLooped();
         assertTrue(nonGroup.isInterruptive());
-        assertTrue(nonGroup.getAudiblyAlerted());
+        assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -831,7 +833,7 @@
 
         verifyNeverBeep();
         assertFalse(summary.isInterruptive());
-        assertFalse(summary.getAudiblyAlerted());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -842,7 +844,7 @@
 
         verifyBeepLooped();
         assertTrue(child.isInterruptive());
-        assertTrue(child.getAudiblyAlerted());
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -853,7 +855,7 @@
 
         verifyBeepLooped();
         assertTrue(nonGroup.isInterruptive());
-        assertTrue(nonGroup.getAudiblyAlerted());
+        assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -864,7 +866,7 @@
 
         verifyBeepLooped();
         assertTrue(group.isInterruptive());
-        assertTrue(group.getAudiblyAlerted());
+        assertNotEquals(-1, group.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -877,13 +879,13 @@
         mService.buzzBeepBlinkLocked(r);
         Mockito.reset(mVibrator);
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
 
         // update should not beep
         mService.buzzBeepBlinkLocked(s);
         verifyNeverVibrate();
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -896,7 +898,7 @@
 
         verifyNeverStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -910,9 +912,9 @@
 
         verifyNeverStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -931,11 +933,11 @@
         mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
         verifyNeverStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertTrue(other.isInterruptive());
-        assertTrue(other.getAudiblyAlerted());
+        assertNotEquals(-1, other.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -951,7 +953,7 @@
         mService.buzzBeepBlinkLocked(other);
         verifyNeverStopVibrate();
         assertFalse(other.isInterruptive());
-        assertFalse(other.getAudiblyAlerted());
+        assertEquals(-1, other.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -968,9 +970,9 @@
         mService.buzzBeepBlinkLocked(s);
         verifyStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -987,9 +989,9 @@
         mService.buzzBeepBlinkLocked(s);
         verifyStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1007,9 +1009,9 @@
         mService.buzzBeepBlinkLocked(s);
         verifyStopVibrate();
         assertTrue(r.isInterruptive());
-        assertTrue(r.getAudiblyAlerted());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
         assertFalse(s.isInterruptive());
-        assertFalse(s.getAudiblyAlerted());
+        assertEquals(-1, s.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1027,7 +1029,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1039,7 +1041,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1082,7 +1084,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1116,7 +1118,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1126,7 +1128,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1135,7 +1137,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyLights();
         assertTrue(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
 
         r = getLightsOnceNotification();
         r.isUpdate = true;
@@ -1143,7 +1145,7 @@
         // checks that lights happened once, i.e. this new call didn't trigger them again
         verifyLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1153,7 +1155,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1162,7 +1164,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1172,7 +1174,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1182,7 +1184,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1192,7 +1194,7 @@
         mService.buzzBeepBlinkLocked(r);
         verifyNeverLights();
         assertFalse(r.isInterruptive());
-        assertFalse(r.getAudiblyAlerted());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1203,7 +1205,7 @@
 
         verifyNeverLights();
         assertFalse(child.isInterruptive());
-        assertFalse(child.getAudiblyAlerted());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1216,7 +1218,7 @@
         verifyLights();
         // summaries should never count for interruptiveness counts
         assertFalse(summary.isInterruptive());
-        assertFalse(summary.getAudiblyAlerted());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1227,7 +1229,7 @@
 
         verifyLights();
         assertTrue(nonGroup.isInterruptive());
-        assertFalse(nonGroup.getAudiblyAlerted());
+        assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1239,7 +1241,7 @@
 
         verifyNeverLights();
         assertFalse(summary.isInterruptive());
-        assertFalse(summary.getAudiblyAlerted());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1250,7 +1252,7 @@
 
         verifyLights();
         assertTrue(child.isInterruptive());
-        assertFalse(child.getAudiblyAlerted());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1261,7 +1263,7 @@
 
         verifyLights();
         assertTrue(nonGroup.isInterruptive());
-        assertFalse(nonGroup.getAudiblyAlerted());
+        assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
 
     @Test
@@ -1272,7 +1274,7 @@
 
         verifyLights();
         assertTrue(group.isInterruptive());
-        assertFalse(group.getAudiblyAlerted());
+        assertEquals(-1, group.getLastAudiblyAlertedMs());
     }
 
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index bcba15d..daca9cb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -16,12 +16,9 @@
 
 package com.android.server.notification;
 
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_NEGATIVE;
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_NEUTRAL;
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_POSITIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -92,7 +89,7 @@
             assertEquals(getShowBadge(i), ranking.canShowBadge());
             assertEquals(getUserSentiment(i), ranking.getUserSentiment());
             assertEquals(getHidden(i), ranking.isSuspended());
-            assertEquals(audiblyAlerted(i), ranking.audiblyAlerted());
+            assertEquals(lastAudiblyAlerted(i), ranking.getLastAudiblyAlertedMillis());
             assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
             assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
         }
@@ -113,7 +110,7 @@
         Bundle mHidden = new Bundle();
         Bundle smartActions = new Bundle();
         Bundle smartReplies = new Bundle();
-        Bundle audiblyAlerted = new Bundle();
+        Bundle lastAudiblyAlerted = new Bundle();
         Bundle noisy = new Bundle();
 
         for (int i = 0; i < mKeys.length; i++) {
@@ -134,14 +131,14 @@
             mHidden.putBoolean(key, getHidden(i));
             smartActions.putParcelableArrayList(key, getSmartActions(key, i));
             smartReplies.putCharSequenceArrayList(key, getSmartReplies(key, i));
-            audiblyAlerted.putBoolean(key, audiblyAlerted(i));
+            lastAudiblyAlerted.putLong(key, lastAudiblyAlerted(i));
             noisy.putBoolean(key, getNoisy(i));
         }
         NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
                 interceptedKeys.toArray(new String[0]), visibilityOverrides,
                 suppressedVisualEffects, importance, explanation, overrideGroupKeys,
                 channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
-                smartActions, smartReplies, audiblyAlerted, noisy);
+                smartActions, smartReplies, lastAudiblyAlerted, noisy);
         return update;
     }
 
@@ -193,8 +190,8 @@
         return index % 2 == 0;
     }
 
-    private boolean audiblyAlerted(int index) {
-        return index < 2;
+    private long lastAudiblyAlerted(int index) {
+        return index * 2000;
     }
 
     private boolean getNoisy(int index) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 0eeeeed..65e640f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -19,12 +19,9 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_NEGATIVE;
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_NEUTRAL;
-import static android.service.notification.NotificationListenerService.Ranking
-        .USER_SENTIMENT_POSITIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -806,6 +803,18 @@
     }
 
     @Test
+    public void testSetDidNotAudiblyAlert() {
+        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        record.setAudiblyAlerted(false);
+
+        assertEquals(-1, record.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testSetAudiblyAlerted() {
         StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
@@ -814,6 +823,6 @@
 
         record.setAudiblyAlerted(true);
 
-        assertTrue(record.getAudiblyAlerted());
+        assertNotEquals(-1, record.getLastAudiblyAlertedMs());
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 9bd3f26..68d3e4c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -17,20 +17,33 @@
 package com.android.server.notification;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
 
 import android.app.NotificationManager.Policy;
+import android.content.ComponentName;
 import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenPolicy;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
 
+import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -138,6 +151,54 @@
         assertEquals(event, eventParsed);
     }
 
+    @Test
+    public void testRuleXml() throws Exception {
+        String tag = "tag";
+
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("a", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "id";
+        rule.zenMode = Settings.Global.ZEN_MODE_ALARMS;
+        rule.modified = true;
+        rule.name = "name";
+        rule.snoozing = true;
+
+        XmlSerializer out = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        out.setOutput(new BufferedOutputStream(baos), "utf-8");
+        out.startDocument(null, true);
+        out.startTag(null, tag);
+        ZenModeConfig.writeRuleXml(rule, out);
+        out.endTag(null, tag);
+        out.endDocument();
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
+        // read from backing component
+        assertEquals("a", fromXml.pkg);
+        // always resets on reboot
+        assertFalse(fromXml.snoozing);
+        //should all match original
+        assertEquals(rule.component, fromXml.component);
+        assertEquals(rule.configurationActivity, fromXml.configurationActivity);
+        assertNull(fromXml.enabler);
+        assertEquals(rule.condition, fromXml.condition);
+        assertEquals(rule.enabled, fromXml.enabled);
+        assertEquals(rule.creationTime, fromXml.creationTime);
+        assertEquals(rule.modified, fromXml.modified);
+        assertEquals(rule.conditionId, fromXml.conditionId);
+        assertEquals(rule.name, fromXml.name);
+        assertEquals(rule.zenMode, fromXml.zenMode);
+    }
+
     private ZenModeConfig getMutedNotificationsConfig() {
         ZenModeConfig config = new ZenModeConfig();
         // Allow alarms, media, and system
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6c7ede3..dc3287e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -633,8 +633,8 @@
         mZenModeHelperSpy.mConfig.manualRule.zenMode =
                 Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a");
+        mZenModeHelperSpy.mConfig.manualRule.pkg = "a";
         mZenModeHelperSpy.mConfig.manualRule.enabled = true;
-        mZenModeHelperSpy.mConfig.manualRule.snoozing = true;
 
         ZenModeConfig expected = mZenModeHelperSpy.mConfig.copy();
 
@@ -645,7 +645,8 @@
         parser.nextTag();
         mZenModeHelperSpy.readXml(parser, false);
 
-        assertEquals(expected, mZenModeHelperSpy.mConfig);
+        assertEquals("Config mismatch: current vs expected: "
+                + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
     }
 
     @Test
@@ -662,7 +663,9 @@
         customRule.name = "Custom Rule";
         customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
-        customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+        customRule.configurationActivity
+                = new ComponentName("android", "ScheduleConditionProvider");
+        customRule.pkg = customRule.configurationActivity.getPackageName();
         automaticRules.put("customRule", customRule);
         mZenModeHelperSpy.mConfig.automaticRules = automaticRules;
 
@@ -674,7 +677,8 @@
                 new ByteArrayInputStream(baos.toByteArray())), null);
         parser.nextTag();
         mZenModeHelperSpy.readXml(parser, true);
-        assertEquals(original, mZenModeHelperSpy.mConfig);
+        assertEquals("Config mismatch: current vs original: "
+                + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
         assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 85410f5..b2a2869 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -797,7 +797,7 @@
         public boolean mChanged = false;
 
         @Override
-        public void onStackOrderChanged() {
+        public void onStackOrderChanged(ActivityStack stack) {
             mChanged = true;
         }
     }
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 7c43cf3..61e968d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -414,10 +414,10 @@
                 .setActivityOptions(new SafeActivityOptions(options))
                 .execute();
 
-        // verify that values are passed to the modifier. Values are passed twice -- once for
+        // verify that values are passed to the modifier. Values are passed thrice -- two for
         // setting initial state, another when task is created.
-        verify(modifier, times(2)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options),
-                any(), any());
+        verify(modifier, times(3)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options),
+                anyInt(), any(), any());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7c83ecc..8430616 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -19,6 +19,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
@@ -309,9 +311,27 @@
 
     @Test
     public void testFocusedWindowMultipleDisplays() {
+        doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
+    }
+
+    @Test
+    public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabled() {
+        doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, Q);
+    }
+
+    @Test
+    public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabledLegacyApp() {
+        doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, P);
+    }
+
+    private void doTestFocusedWindowMultipleDisplays(boolean perDisplayFocusEnabled,
+            int targetSdk) {
+        mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
+
         // Create a focusable window and check that focus is calculated correctly
         final WindowState window1 =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
+        window1.mAppToken.mTargetSdk = targetSdk;
         updateFocusedWindow();
         assertTrue(window1.isFocused());
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
@@ -324,16 +344,17 @@
 
         // Add a window to the second display, and it should be focused
         final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+        window2.mAppToken.mTargetSdk = targetSdk;
         updateFocusedWindow();
-        assertTrue(window1.isFocused());
         assertTrue(window2.isFocused());
+        assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window1.isFocused());
         assertEquals(window2, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
-        // Move the first window to the to including parents, and make sure focus is updated
+        // Move the first window to top including parents, and make sure focus is updated
         window1.getParent().positionChildAt(POSITION_TOP, window1, true);
         updateFocusedWindow();
         assertTrue(window1.isFocused());
-        assertTrue(window2.isFocused());
+        assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window2.isFocused());
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java
new file mode 100644
index 0000000..ce22788
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/KeyguardDisableHandlerTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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 com.android.server.wm;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.SparseBooleanArray;
+
+import com.android.server.wm.LockTaskController.LockTaskToken;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+
+public class KeyguardDisableHandlerTest {
+
+    private KeyguardDisableHandler mKeyguardDisable;
+
+    private boolean mKeyguardEnabled;
+    private SparseBooleanArray mKeyguardSecure = new SparseBooleanArray();
+    private SparseBooleanArray mDpmRequiresPassword = new SparseBooleanArray();
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyguardEnabled = true;
+
+        mKeyguardDisable = new KeyguardDisableHandler(new KeyguardDisableHandler.Injector() {
+            @Override
+            public boolean dpmRequiresPassword(int userId) {
+                return mDpmRequiresPassword.get(userId);
+            }
+
+            @Override
+            public boolean isKeyguardSecure(int userId) {
+                return mKeyguardSecure.get(userId);
+            }
+
+            @Override
+            public int getProfileParentId(int userId) {
+                return userId;
+            }
+
+            @Override
+            public void enableKeyguard(boolean enabled) {
+                mKeyguardEnabled = enabled;
+            }
+        }, mock(Handler.class)) {
+            @Override
+            public void disableKeyguard(IBinder token, String tag, int callingUid, int userId) {
+                super.disableKeyguard(token, tag, callingUid, userId);
+                // In the actual code, the update is posted to the handler thread. Eagerly update
+                // here to simplify the test.
+                updateKeyguardEnabled(userId);
+            }
+
+            @Override
+            public void reenableKeyguard(IBinder token, int callingUid, int userId) {
+                super.reenableKeyguard(token, callingUid, userId);
+                // In the actual code, the update is posted to the handler thread. Eagerly update
+                // here to simplify the test.
+                updateKeyguardEnabled(userId);
+            }
+        };
+    }
+
+    @Test
+    public void starts_enabled() {
+        assertTrue(mKeyguardEnabled);
+        mKeyguardDisable.updateKeyguardEnabled(USER_ALL);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromApp_disables() {
+        mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM);
+        assertFalse(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromApp_secondaryUser_disables() {
+        mKeyguardDisable.setCurrentUser(1);
+        mKeyguardDisable.disableKeyguard(new Binder(), "Tag",
+                UserHandle.getUid(1, FIRST_APPLICATION_UID), 1);
+        assertFalse(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromSystem_LockTask_disables() {
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM);
+        assertFalse(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromSystem_genericToken_fails() {
+        try {
+            mKeyguardDisable.disableKeyguard(new Binder(), "Tag", SYSTEM_UID, USER_SYSTEM);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), containsString("Only apps can use the KeyguardLock API"));
+        }
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromNonApp_genericToken_fails() {
+        try {
+            mKeyguardDisable.disableKeyguard(new Binder(), "Tag", NFC_UID, USER_SYSTEM);
+            fail("Expected exception not thrown");
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), containsString("Only apps can use the KeyguardLock API"));
+        }
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromApp_secure_staysEnabled() {
+        configureIsSecure(true, USER_SYSTEM);
+        mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromApp_dpmRequiresPassword_staysEnabled() {
+        configureDpmRequiresPassword(true, USER_SYSTEM);
+        mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromSystem_LockTask_secure_disables() {
+        configureIsSecure(true, USER_SYSTEM);
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM);
+        assertFalse(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromSystem_LockTask_requiresDpm_staysEnabled() {
+        configureDpmRequiresPassword(true, USER_SYSTEM);
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromApp_thenSecure_reenables() {
+        mKeyguardDisable.disableKeyguard(new Binder(), "Tag", FIRST_APPLICATION_UID, USER_SYSTEM);
+        configureIsSecure(true, USER_SYSTEM);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void disable_fromSystem_LockTask_thenRequiresDpm_reenables() {
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM);
+        configureDpmRequiresPassword(true, USER_SYSTEM);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void user_switch_to_enabledUser_applies_enabled() {
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag", SYSTEM_UID, USER_SYSTEM);
+        assertFalse("test setup failed", mKeyguardEnabled);
+        mKeyguardDisable.setCurrentUser(1);
+        assertTrue(mKeyguardEnabled);
+    }
+
+    @Test
+    public void user_switch_to_disabledUser_applies_disabled() {
+        mKeyguardDisable.disableKeyguard(createLockTaskToken(), "Tag",
+                SYSTEM_UID, 1);
+        assertTrue("test setup failed", mKeyguardEnabled);
+        mKeyguardDisable.setCurrentUser(1);
+        assertFalse(mKeyguardEnabled);
+    }
+
+    private void configureIsSecure(boolean secure, int userId) {
+        mKeyguardSecure.put(userId, secure);
+        mKeyguardDisable.updateKeyguardEnabled(userId);
+    }
+
+    private void configureDpmRequiresPassword(boolean requiresPassword, int userId) {
+        mDpmRequiresPassword.put(userId, requiresPassword);
+        mKeyguardDisable.updateKeyguardEnabled(userId);
+    }
+
+    private LockTaskToken createLockTaskToken() {
+        try {
+            final Constructor<LockTaskToken> constructor =
+                    LockTaskToken.class.getDeclaredConstructor();
+            constructor.setAccessible(true);
+            return constructor.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 3720c85..8c3dec7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -31,6 +31,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -89,10 +90,10 @@
         final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
         final ActivityOptions options = mock(ActivityOptions.class);
 
-        mController.calculate(record.getTaskRecord(), layout, record, source, options,
+        mController.calculate(record.getTaskRecord(), layout, record, source, options, PHASE_BOUNDS,
                 new LaunchParams());
         verify(positioner, times(1)).onCalculate(eq(record.getTaskRecord()), eq(layout), eq(record),
-                eq(source), eq(options), any(), any());
+                eq(source), eq(options), anyInt(), any(), any());
     }
 
     /**
@@ -115,9 +116,9 @@
         mPersister.putLaunchParams(userId, name, expected);
 
         mController.calculate(activity.getTaskRecord(), null /*layout*/, activity, null /*source*/,
-                null /*options*/, new LaunchParams());
-        verify(positioner, times(1)).onCalculate(any(), any(), any(), any(), any(), eq(expected),
-                any());
+                null /*options*/, PHASE_BOUNDS, new LaunchParams());
+        verify(positioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+                eq(expected), any());
     }
 
     /**
@@ -128,14 +129,15 @@
         final LaunchParamsModifier
                 ignoredPositioner = mock(LaunchParamsModifier.class);
         final LaunchParamsModifier earlyExitPositioner =
-                (task, layout, activity, source, options, currentParams, outParams) -> RESULT_DONE;
+                (task, layout, activity, source, options, phase, currentParams, outParams)
+                        -> RESULT_DONE;
 
         mController.registerModifier(ignoredPositioner);
         mController.registerModifier(earlyExitPositioner);
 
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
-                null /*source*/, null /*options*/, new LaunchParams());
-        verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(),
+                null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+        verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(), anyInt(),
                 any(), any());
     }
 
@@ -152,20 +154,20 @@
         mController.registerModifier(firstPositioner);
 
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
-                null /*source*/, null /*options*/, new LaunchParams());
-        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
-                any());
+                null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+                any(), any());
 
         final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner);
 
         mController.registerModifier(secondPositioner);
 
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
-                null /*source*/, null /*options*/, new LaunchParams());
-        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
-                any());
-        verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
-                any());
+                null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+        verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+                any(), any());
+        verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+                any(), any());
     }
 
     /**
@@ -187,9 +189,9 @@
         mController.registerModifier(positioner2);
 
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
-                null /*options*/, new LaunchParams());
+                null /*options*/, PHASE_BOUNDS, new LaunchParams());
 
-        verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(),
+        verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
                 eq(positioner2.getLaunchParams()), any());
     }
 
@@ -213,7 +215,7 @@
         final LaunchParams result = new LaunchParams();
 
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
-                null /*options*/, result);
+                null /*options*/, PHASE_BOUNDS, result);
 
         assertEquals(result, positioner2.getLaunchParams());
     }
@@ -232,21 +234,42 @@
 
         // VR activities should always land on default display.
         mController.calculate(null /*task*/, null /*layout*/, vrActivity /*activity*/,
-                null /*source*/, null /*options*/, result);
+                null /*source*/, null /*options*/, PHASE_BOUNDS, result);
         assertEquals(DEFAULT_DISPLAY, result.mPreferredDisplayId);
 
         // Otherwise, always lands on VR 2D display.
         final ActivityRecord vr2dActivity = new ActivityBuilder(mService).build();
         mController.calculate(null /*task*/, null /*layout*/, vr2dActivity /*activity*/,
-                null /*source*/, null /*options*/, result);
+                null /*source*/, null /*options*/, PHASE_BOUNDS, result);
         assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
         mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
-                null /*options*/, result);
+                null /*options*/, PHASE_BOUNDS, result);
         assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
 
         mService.mVr2dDisplayId = INVALID_DISPLAY;
     }
 
+
+    /**
+     * Ensures that {@link LaunchParamsController} calculates to {@link PHASE_BOUNDS} phase by
+     * default.
+     */
+    @Test
+    public void testCalculatePhase() {
+        final LaunchParamsModifier positioner = mock(LaunchParamsModifier.class);
+        mController.registerModifier(positioner);
+
+        final ActivityRecord record = new ActivityBuilder(mService).build();
+        final ActivityRecord source = new ActivityBuilder(mService).build();
+        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
+        final ActivityOptions options = mock(ActivityOptions.class);
+
+        mController.calculate(record.getTaskRecord(), layout, record, source, options, PHASE_BOUNDS,
+                new LaunchParams());
+        verify(positioner, times(1)).onCalculate(eq(record.getTaskRecord()), eq(layout), eq(record),
+                eq(source), eq(options), eq(PHASE_BOUNDS), any(), any());
+    }
+
     /**
      * Ensures that {@link LaunchParamsModifier} requests specifying display id during
      * layout are honored.
@@ -348,7 +371,7 @@
 
         @Override
         public int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
-                   ActivityRecord source, ActivityOptions options,
+                   ActivityRecord source, ActivityOptions options, int phase,
                    LaunchParams currentParams, LaunchParams outParams) {
             outParams.set(mParams);
             return mReturnVal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 6259fa6..a9f150b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -560,19 +560,20 @@
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN keyguard should be disabled
-        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString(), eq(TEST_USER_ID));
 
         // WHEN keyguard is enabled for lock task mode
         mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD);
 
         // THEN keyguard should be enabled
-        verify(mWindowManager).reenableKeyguard(any(IBinder.class));
+        verify(mWindowManager).reenableKeyguard(any(IBinder.class), eq(TEST_USER_ID));
 
         // WHEN keyguard is disabled again for lock task mode
         mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE);
 
         // THEN keyguard should be disabled
-        verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString());
+        verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString(),
+                eq(TEST_USER_ID));
     }
 
     @Test
@@ -653,7 +654,7 @@
 
     private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception {
         // THEN the keyguard should have been disabled
-        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString(), eq(TEST_USER_ID));
         // THEN the status bar should have been disabled
         verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class),
                 eq(mContext.getPackageName()));
@@ -668,7 +669,7 @@
 
     private void verifyLockTaskStopped(VerificationMode mode) throws Exception {
         // THEN the keyguard should have been disabled
-        verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class));
+        verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class), eq(TEST_USER_ID));
         // THEN the status bar should have been disabled
         verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE),
                 any(IBinder.class), eq(mContext.getPackageName()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 0ff67d7..5f3a290 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -24,7 +24,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -65,22 +64,26 @@
     }
 
     @Test
-    public void testCancelAnimationOnStackOrderChange() {
-        ActivityStack fullscreenStack =
-                mService.mRootActivityContainer.getDefaultDisplay().createStack(
-                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        ActivityStack recentsStack = mService.mRootActivityContainer.getDefaultDisplay().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        ActivityRecord recentsActivity = new ActivityBuilder(mService)
+    public void testCancelAnimationOnVisibleStackOrderChange() {
+        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        new ActivityBuilder(mService)
+                .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+                .setCreateTask(true)
+                .setStack(fullscreenStack)
+                .build();
+        ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_RECENTS, true /* onTop */);
+        new ActivityBuilder(mService)
                 .setComponent(mRecentsComponent)
                 .setCreateTask(true)
                 .setStack(recentsStack)
                 .build();
-        ActivityStack fullscreenStack2 =
-                mService.mRootActivityContainer.getDefaultDisplay().createStack(
-                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        ActivityRecord fsActivity = new ActivityBuilder(mService)
-                .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+        ActivityStack fullscreenStack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        new ActivityBuilder(mService)
+                .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
                 .setCreateTask(true)
                 .setStack(fullscreenStack2)
                 .build();
@@ -97,4 +100,42 @@
         verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
                 eq(REORDER_KEEP_IN_PLACE), any());
     }
+
+    @Test
+    public void testKeepAnimationOnHiddenStackOrderChange() {
+        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        new ActivityBuilder(mService)
+                .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+                .setCreateTask(true)
+                .setStack(fullscreenStack)
+                .build();
+        ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_RECENTS, true /* onTop */);
+        new ActivityBuilder(mService)
+                .setComponent(mRecentsComponent)
+                .setCreateTask(true)
+                .setStack(recentsStack)
+                .build();
+        ActivityStack fullscreenStack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        new ActivityBuilder(mService)
+                .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
+                .setCreateTask(true)
+                .setStack(fullscreenStack2)
+                .build();
+        doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
+
+        // Start the recents animation
+        Intent recentsIntent = new Intent();
+        recentsIntent.setComponent(mRecentsComponent);
+        mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class));
+
+        fullscreenStack.remove();
+
+        // Ensure that the recents animation was NOT canceled
+        verify(mService.mWindowManager, times(0)).cancelRecentsAnimationSynchronously(
+                eq(REORDER_KEEP_IN_PLACE), any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index fe632d4..0bd681b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -173,6 +173,23 @@
     }
 
     @Test
+    public void testUsesTasksDisplayIdPriorToSourceIfSet() {
+        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FREEFORM);
+        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FULLSCREEN);
+
+        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
+        ActivityRecord source = createSourceActivity(freeformDisplay);
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(reusableActivity.getTaskRecord(),
+                /* layout */ null, mActivity, source, /* options */ null, mCurrent, mResult));
+
+        assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
+    }
+
+    @Test
     public void testUsesTaskDisplayIdIfSet() {
         final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
                 WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 29738ff..c5df85c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -64,8 +64,7 @@
     }
 
     @Override
-    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient)
-            throws RemoteException {
+    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) throws RemoteException {
     }
 
     @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index ba81bd1..d1fe48a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -170,6 +170,10 @@
     }
 
     @Override
+    public void setTopFocusedDisplay(int displayId) {
+    }
+
+    @Override
     public void applyKeyguardPolicyLw(WindowState win, WindowState imeTarget) {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 65cde77..20fc5ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -30,6 +30,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.view.Display;
 import android.view.IApplicationToken;
@@ -161,6 +162,7 @@
                     return null;
                 }
             }, new ComponentName("", ""), false, dc, true /* fillsParent */);
+            mTargetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
         TestAppWindowToken(WindowManagerService service, IApplicationToken token,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index ec475bf..eddf8f9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -98,14 +98,14 @@
             stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute(
                     parser, LAST_TIME_VISIBLE_ATTR);
         } catch (IOException e) {
-            Log.e(TAG, "Failed to parse mLastTimeVisible", e);
+            Log.i(TAG, "Failed to parse mLastTimeVisible");
         }
 
         try {
             stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
                     parser, LAST_TIME_SERVICE_USED_ATTR);
         } catch (IOException e) {
-            Log.e(TAG, "Failed to parse mLastTimeForegroundServiceUsed", e);
+            Log.i(TAG, "Failed to parse mLastTimeForegroundServiceUsed");
         }
 
         stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
@@ -113,14 +113,14 @@
         try {
             stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR);
         } catch (IOException e) {
-            Log.e(TAG, "Failed to parse mTotalTimeVisible", e);
+            Log.i(TAG, "Failed to parse mTotalTimeVisible");
         }
 
         try {
             stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser,
                     TOTAL_TIME_SERVICE_USED_ATTR);
         } catch (IOException e) {
-            Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e);
+            Log.i(TAG, "Failed to parse mTotalTimeForegroundServiceUsed");
         }
 
         stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 99ad1f4..bbf3d45 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -356,7 +356,12 @@
             }
 
             // No voice interactor, we'll just set up a simple recognizer.
-            curRecognizer = findAvailRecognizer(null, userHandle);
+            initSimpleRecognizer(curInteractorInfo, userHandle);
+        }
+
+        public void initSimpleRecognizer(VoiceInteractionServiceInfo curInteractorInfo,
+                int userHandle) {
+            ComponentName curRecognizer = findAvailRecognizer(null, userHandle);
             if (curRecognizer != null) {
                 if (curInteractorInfo == null) {
                     setCurInteractor(null, userHandle);
@@ -1236,34 +1241,46 @@
                 int userHandle = UserHandle.getUserId(uid);
                 ComponentName curInteractor = getCurInteractor(userHandle);
                 ComponentName curRecognizer = getCurRecognizer(userHandle);
-                boolean hit = false;
+                boolean hitInt = false;
+                boolean hitRec = false;
                 for (String pkg : packages) {
                     if (curInteractor != null && pkg.equals(curInteractor.getPackageName())) {
-                        hit = true;
+                        hitInt = true;
                         break;
                     } else if (curRecognizer != null
                             && pkg.equals(curRecognizer.getPackageName())) {
-                        hit = true;
+                        hitRec = true;
                         break;
                     }
                 }
-                if (hit && doit) {
-                    // The user is force stopping our current interactor/recognizer.
+                if (hitInt && doit) {
+                    // The user is force stopping our current interactor.
                     // Clear the current settings and restore default state.
                     synchronized (VoiceInteractionManagerServiceStub.this) {
+                        Slog.i(TAG, "Force stopping current voice interactor: "
+                                + getCurInteractor(userHandle));
                         unloadAllKeyphraseModels();
                         if (mImpl != null) {
                             mImpl.shutdownLocked();
                             setImplLocked(null);
                         }
+
                         setCurInteractor(null, userHandle);
                         setCurRecognizer(null, userHandle);
                         resetCurAssistant(userHandle);
                         initForUser(userHandle);
                         switchImplementationIfNeededLocked(true);
                     }
+                } else if (hitRec && doit) {
+                    // We are just force-stopping the current recognizer, which is not
+                    // also the current interactor.
+                    synchronized (VoiceInteractionManagerServiceStub.this) {
+                        Slog.i(TAG, "Force stopping current voice recognizer: "
+                                + getCurRecognizer(userHandle));
+                        initSimpleRecognizer(null, userHandle);
+                    }
                 }
-                return hit;
+                return hitInt || hitRec;
             }
 
             @Override
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index de40e0d..91cec554 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -24,6 +24,9 @@
         "libdexfile",
         "slicer",
     ],
+    static_libs: [
+        "libtinyxml2",
+    ],
 }
 
 cc_library_host_static {
@@ -32,7 +35,9 @@
     srcs: [
         "dex_builder.cc",
         "java_lang_builder.cc",
+        "tinyxml_layout_parser.cc",
         "util.cc",
+        "layout_validation.cc",
     ],
 }
 
@@ -43,7 +48,6 @@
         "main.cc",
     ],
     static_libs: [
-        "libtinyxml2",
         "libgflags",
         "libviewcompiler",
     ],
@@ -54,6 +58,7 @@
     defaults: ["viewcompiler_defaults"],
     srcs: [
         "dex_builder_test.cc",
+        "layout_validation_test.cc",
         "util_test.cc",
     ],
     static_libs: [
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 906d64c..94879d0 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -61,18 +61,46 @@
     case Instruction::Op::kInvokeDirect:
       out << "kInvokeDirect";
       return out;
+    case Instruction::Op::kInvokeStatic:
+      out << "kInvokeStatic";
+      return out;
+    case Instruction::Op::kInvokeInterface:
+      out << "kInvokeInterface";
+      return out;
     case Instruction::Op::kBindLabel:
       out << "kBindLabel";
       return out;
     case Instruction::Op::kBranchEqz:
       out << "kBranchEqz";
       return out;
+    case Instruction::Op::kBranchNEqz:
+      out << "kBranchNEqz";
+      return out;
     case Instruction::Op::kNew:
       out << "kNew";
       return out;
   }
 }
 
+std::ostream& operator<<(std::ostream& out, const Value& value) {
+  if (value.is_register()) {
+    out << "Register(" << value.value() << ")";
+  } else if (value.is_parameter()) {
+    out << "Parameter(" << value.value() << ")";
+  } else if (value.is_immediate()) {
+    out << "Immediate(" << value.value() << ")";
+  } else if (value.is_string()) {
+    out << "String(" << value.value() << ")";
+  } else if (value.is_label()) {
+    out << "Label(" << value.value() << ")";
+  } else if (value.is_type()) {
+    out << "Type(" << value.value() << ")";
+  } else {
+    out << "UnknownValue";
+  }
+  return out;
+}
+
 void* TrackingAllocator::Allocate(size_t size) {
   std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size);
   void* raw_buffer = buffer.get();
@@ -289,10 +317,16 @@
       return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL);
     case Instruction::Op::kInvokeDirect:
       return EncodeInvoke(instruction, art::Instruction::INVOKE_DIRECT);
+    case Instruction::Op::kInvokeStatic:
+      return EncodeInvoke(instruction, art::Instruction::INVOKE_STATIC);
+    case Instruction::Op::kInvokeInterface:
+      return EncodeInvoke(instruction, art::Instruction::INVOKE_INTERFACE);
     case Instruction::Op::kBindLabel:
       return BindLabel(instruction.args()[0]);
     case Instruction::Op::kBranchEqz:
       return EncodeBranch(art::Instruction::IF_EQZ, instruction);
+    case Instruction::Op::kBranchNEqz:
+      return EncodeBranch(art::Instruction::IF_NEZ, instruction);
     case Instruction::Op::kNew:
       return EncodeNew(instruction);
   }
@@ -353,7 +387,9 @@
 
   // If there is a return value, add a move-result instruction
   if (instruction.dest().has_value()) {
-    Encode11x(art::Instruction::MOVE_RESULT, RegisterValue(*instruction.dest()));
+    Encode11x(instruction.result_is_object() ? art::Instruction::MOVE_RESULT_OBJECT
+                                             : art::Instruction::MOVE_RESULT,
+              RegisterValue(*instruction.dest()));
   }
 
   max_args_ = std::max(max_args_, instruction.args().size());
@@ -447,7 +483,7 @@
     auto& ir_node = dex_file_->methods_map[new_index];
     SLICER_CHECK(ir_node == nullptr);
     ir_node = decl;
-    decl->orig_index = new_index;
+    decl->orig_index = decl->index = new_index;
 
     entry = {id, decl};
   }
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index adf82bf..45596ac 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -147,8 +147,11 @@
     kMove,
     kInvokeVirtual,
     kInvokeDirect,
+    kInvokeStatic,
+    kInvokeInterface,
     kBindLabel,
     kBranchEqz,
+    kBranchNEqz,
     kNew
   };
 
@@ -163,19 +166,53 @@
   // For most instructions, which take some number of arguments and have an optional return value.
   template <typename... T>
   static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) {
-    return Instruction{opcode, /*method_id*/ 0, dest, args...};
+    return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...};
   }
   // For method calls.
   template <typename... T>
   static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest,
                                           Value this_arg, T... args) {
-    return Instruction{Op::kInvokeVirtual, method_id, dest, this_arg, args...};
+    return Instruction{
+        Op::kInvokeVirtual, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+  }
+  // Returns an object
+  template <typename... T>
+  static inline Instruction InvokeVirtualObject(size_t method_id, std::optional<const Value> dest,
+                                                Value this_arg, T... args) {
+    return Instruction{
+        Op::kInvokeVirtual, method_id, /*result_is_object=*/true, dest, this_arg, args...};
   }
   // For direct calls (basically, constructors).
   template <typename... T>
   static inline Instruction InvokeDirect(size_t method_id, std::optional<const Value> dest,
                                          Value this_arg, T... args) {
-    return Instruction{Op::kInvokeDirect, method_id, dest, this_arg, args...};
+    return Instruction{
+        Op::kInvokeDirect, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+  }
+  // Returns an object
+  template <typename... T>
+  static inline Instruction InvokeDirectObject(size_t method_id, std::optional<const Value> dest,
+                                               Value this_arg, T... args) {
+    return Instruction{
+        Op::kInvokeDirect, method_id, /*result_is_object=*/true, dest, this_arg, args...};
+  }
+  // For static calls.
+  template <typename... T>
+  static inline Instruction InvokeStatic(size_t method_id, std::optional<const Value> dest,
+                                         T... args) {
+    return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/false, dest, args...};
+  }
+  // Returns an object
+  template <typename... T>
+  static inline Instruction InvokeStaticObject(size_t method_id, std::optional<const Value> dest,
+                                               T... args) {
+    return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/true, dest, args...};
+  }
+  // For static calls.
+  template <typename... T>
+  static inline Instruction InvokeInterface(size_t method_id, std::optional<const Value> dest,
+                                            T... args) {
+    return Instruction{Op::kInvokeInterface, method_id, /*result_is_object=*/false, dest, args...};
   }
 
   ///////////////
@@ -184,21 +221,27 @@
 
   Op opcode() const { return opcode_; }
   size_t method_id() const { return method_id_; }
+  bool result_is_object() const { return result_is_object_; }
   const std::optional<const Value>& dest() const { return dest_; }
   const std::vector<const Value>& args() const { return args_; }
 
  private:
   inline Instruction(Op opcode, size_t method_id, std::optional<const Value> dest)
-      : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{} {}
+      : opcode_{opcode}, method_id_{method_id}, result_is_object_{false}, dest_{dest}, args_{} {}
 
   template <typename... T>
-  inline constexpr Instruction(Op opcode, size_t method_id, std::optional<const Value> dest,
-                               T... args)
-      : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{args...} {}
+  inline constexpr Instruction(Op opcode, size_t method_id, bool result_is_object,
+                               std::optional<const Value> dest, T... args)
+      : opcode_{opcode},
+        method_id_{method_id},
+        result_is_object_{result_is_object},
+        dest_{dest},
+        args_{args...} {}
 
   const Op opcode_;
   // The index of the method to invoke, for kInvokeVirtual and similar opcodes.
   const size_t method_id_{0};
+  const bool result_is_object_;
   const std::optional<const Value> dest_;
   const std::vector<const Value> args_;
 };
@@ -244,6 +287,8 @@
 
   // TODO: add builders for more instructions
 
+  DexBuilder* dex_file() const { return dex_; }
+
  private:
   void EncodeInstructions();
   void EncodeInstruction(const Instruction& instruction);
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index e20f3a9..1508ad9e 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -84,6 +84,15 @@
   }
 
   @Test
+  public void returnIfNotZero() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("returnIfNotZero", int.class);
+    Assert.assertEquals(3, method.invoke(null, 0));
+    Assert.assertEquals(5, method.invoke(null, 17));
+  }
+
+  @Test
   public void backwardsBranch() throws Exception {
     ClassLoader loader = loadDexFile("simple.dex");
     Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
@@ -124,4 +133,22 @@
     Assert.assertEquals("b", method.invoke(null, 0));
     Assert.assertEquals("a", method.invoke(null, 1));
   }
+
+  @Test
+  public void invokeStaticReturnObject() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("invokeStaticReturnObject", int.class, int.class);
+    Assert.assertEquals("10", method.invoke(null, 10, 10));
+    Assert.assertEquals("a", method.invoke(null, 10, 16));
+    Assert.assertEquals("5", method.invoke(null, 5, 16));
+  }
+
+  @Test
+  public void invokeVirtualReturnObject() throws Exception {
+    ClassLoader loader = loadDexFile("simple.dex");
+    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+    Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class);
+    Assert.assertEquals("bc", method.invoke(null, "abc", 1));
+  }
 }
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index e2bf43bc..2781aa5 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -108,6 +108,27 @@
   }
   returnIfZero.Encode();
 
+  // int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } }
+  MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
+      "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
+  {
+    Value resultIfNotZero{returnIfNotZero.MakeRegister()};
+    Value else_target{returnIfNotZero.MakeLabel()};
+    returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
+        Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+    // else branch
+    returnIfNotZero.BuildConst4(resultIfNotZero, 3);
+    returnIfNotZero.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
+    // then branch
+    returnIfNotZero.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+    returnIfNotZero.BuildConst4(resultIfNotZero, 5);
+    returnIfNotZero.AddInstruction(
+        Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
+  }
+  returnIfNotZero.Encode();
+
   // Make sure backwards branches work too.
   //
   // Pseudo code for test:
@@ -216,6 +237,38 @@
     method.Encode();
   }(returnStringIfZeroBA);
 
+  // Make sure we can invoke static methods that return an object
+  // String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n,
+  // radix); }
+  MethodBuilder invokeStaticReturnObject{
+      cbuilder.CreateMethod("invokeStaticReturnObject",
+                            Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
+  [&](MethodBuilder& method) {
+    Value result{method.MakeRegister()};
+    MethodDeclData to_string{dex_file.GetOrDeclareMethod(
+        TypeDescriptor::FromClassname("java.lang.Integer"),
+        "toString",
+        Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
+    method.AddInstruction(Instruction::InvokeStaticObject(
+        to_string.id, result, Value::Parameter(0), Value::Parameter(1)));
+    method.BuildReturn(result, /*is_object=*/true);
+    method.Encode();
+  }(invokeStaticReturnObject);
+
+  // Make sure we can invoke virtual methods that return an object
+  // String invokeVirtualReturnObject(String s, int n) { return s.substring(n); }
+  MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
+      "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
+  [&](MethodBuilder& method) {
+    Value result{method.MakeRegister()};
+    MethodDeclData substring{dex_file.GetOrDeclareMethod(
+        string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
+    method.AddInstruction(Instruction::InvokeVirtualObject(
+        substring.id, result, Value::Parameter(0), Value::Parameter(1)));
+    method.BuildReturn(result, /*is_object=*/true);
+    method.Encode();
+  }(invokeVirtualReturnObject);
+
   slicer::MemView image{dex_file.CreateImage()};
   std::ofstream out_file(outdir + "/simple.dex");
   out_file.write(image.ptr<const char>(), image.size());
diff --git a/startop/view_compiler/layout_validation.cc b/startop/view_compiler/layout_validation.cc
new file mode 100644
index 0000000..8c77377
--- /dev/null
+++ b/startop/view_compiler/layout_validation.cc
@@ -0,0 +1,42 @@
+/*
+ * 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 "layout_validation.h"
+
+#include "android-base/stringprintf.h"
+
+namespace startop {
+
+void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
+  if (0 == name.compare(u"merge")) {
+    message_ = "Merge tags are not supported";
+    can_compile_ = false;
+  }
+  if (0 == name.compare(u"include")) {
+    message_ = "Include tags are not supported";
+    can_compile_ = false;
+  }
+  if (0 == name.compare(u"view")) {
+    message_ = "View tags are not supported";
+    can_compile_ = false;
+  }
+  if (0 == name.compare(u"fragment")) {
+    message_ = "Fragment tags are not supported";
+    can_compile_ = false;
+  }
+}
+
+}  // namespace startop
\ No newline at end of file
diff --git a/startop/view_compiler/layout_validation.h b/startop/view_compiler/layout_validation.h
new file mode 100644
index 0000000..bed34bb
--- /dev/null
+++ b/startop/view_compiler/layout_validation.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef LAYOUT_VALIDATION_H_
+#define LAYOUT_VALIDATION_H_
+
+#include "dex_builder.h"
+
+#include <string>
+
+namespace startop {
+
+// This visitor determines whether a layout can be compiled. Since we do not currently support all
+// features, such as includes and merges, we need to pre-validate the layout before we start
+// compiling.
+class LayoutValidationVisitor {
+ public:
+  void VisitStartDocument() const {}
+  void VisitEndDocument() const {}
+  void VisitStartTag(const std::u16string& name);
+  void VisitEndTag() const {}
+
+  const std::string& message() const { return message_; }
+  bool can_compile() const { return can_compile_; }
+
+ private:
+  std::string message_{"Okay"};
+  bool can_compile_{true};
+};
+
+}  // namespace startop
+
+#endif  // LAYOUT_VALIDATION_H_
diff --git a/startop/view_compiler/layout_validation_test.cc b/startop/view_compiler/layout_validation_test.cc
new file mode 100644
index 0000000..b74cdae
--- /dev/null
+++ b/startop/view_compiler/layout_validation_test.cc
@@ -0,0 +1,163 @@
+/*
+ * 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 "tinyxml_layout_parser.h"
+
+#include "gtest/gtest.h"
+
+using startop::CanCompileLayout;
+using std::string;
+
+namespace {
+void ValidateXmlText(const string& xml, bool expected) {
+  tinyxml2::XMLDocument doc;
+  doc.Parse(xml.c_str());
+  EXPECT_EQ(CanCompileLayout(doc), expected);
+}
+}  // namespace
+
+TEST(LayoutValidationTest, SingleButtonLayout) {
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:text="Hello, World!">
+
+</Button>)";
+  ValidateXmlText(xml, /*expected=*/true);
+}
+
+TEST(LayoutValidationTest, SmallConstraintLayout) {
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/button6"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="16dp"
+        android:text="Button"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
+    <Button
+        android:id="@+id/button7"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="16dp"
+        android:text="Button2"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/button6" />
+
+    <Button
+        android:id="@+id/button8"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="16dp"
+        android:text="Button1"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/button7" />
+</android.support.constraint.ConstraintLayout>)";
+  ValidateXmlText(xml, /*expected=*/true);
+}
+
+TEST(LayoutValidationTest, MergeNode) {
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+        android:id="@+id/textView3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView" />
+
+    <Button
+        android:id="@+id/button9"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+</merge>)";
+  ValidateXmlText(xml, /*expected=*/false);
+}
+
+TEST(LayoutValidationTest, IncludeLayout) {
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include
+        layout="@layout/single_button_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</android.support.constraint.ConstraintLayout>)";
+  ValidateXmlText(xml, /*expected=*/false);
+}
+
+TEST(LayoutValidationTest, ViewNode) {
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <view
+        class="android.support.design.button.MaterialButton"
+        id="@+id/view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</android.support.constraint.ConstraintLayout>)";
+  ValidateXmlText(xml, /*expected=*/false);
+}
+
+TEST(LayoutValidationTest, FragmentNode) {
+  // This test case is from https://developer.android.com/guide/components/fragments
+  const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <fragment android:name="com.example.news.ArticleListFragment"
+            android:id="@+id/list"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="match_parent" />
+    <fragment android:name="com.example.news.ArticleReaderFragment"
+            android:id="@+id/viewer"
+            android:layout_weight="2"
+            android:layout_width="0dp"
+            android:layout_height="match_parent" />
+</LinearLayout>)";
+  ValidateXmlText(xml, /*expected=*/false);
+}
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
index 7d791c2..9351dc3 100644
--- a/startop/view_compiler/main.cc
+++ b/startop/view_compiler/main.cc
@@ -18,6 +18,7 @@
 
 #include "dex_builder.h"
 #include "java_lang_builder.h"
+#include "tinyxml_layout_parser.h"
 #include "util.h"
 
 #include "tinyxml2.h"
@@ -100,6 +101,12 @@
   XMLDocument xml;
   xml.LoadFile(filename);
 
+  string message{};
+  if (!startop::CanCompileLayout(xml, &message)) {
+    LOG(ERROR) << "Layout not supported: " << message;
+    return 1;
+  }
+
   std::ofstream outfile;
   if (FLAGS_out != kStdoutFilename) {
     outfile.open(FLAGS_out);
diff --git a/startop/view_compiler/tinyxml_layout_parser.cc b/startop/view_compiler/tinyxml_layout_parser.cc
new file mode 100644
index 0000000..1b3a81f
--- /dev/null
+++ b/startop/view_compiler/tinyxml_layout_parser.cc
@@ -0,0 +1,34 @@
+/*
+ * 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 "tinyxml_layout_parser.h"
+
+#include "layout_validation.h"
+
+namespace startop {
+
+bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) {
+  LayoutValidationVisitor validator;
+  TinyXmlVisitorAdapter adapter{&validator};
+  xml.Accept(&adapter);
+
+  if (message != nullptr) {
+    *message = validator.message();
+  }
+
+  return validator.can_compile();
+}
+
+}  // namespace startop
diff --git a/startop/view_compiler/tinyxml_layout_parser.h b/startop/view_compiler/tinyxml_layout_parser.h
new file mode 100644
index 0000000..8f714a2
--- /dev/null
+++ b/startop/view_compiler/tinyxml_layout_parser.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+#ifndef TINYXML_LAYOUT_PARSER_H_
+#define TINYXML_LAYOUT_PARSER_H_
+
+#include "tinyxml2.h"
+
+#include <codecvt>
+#include <locale>
+#include <string>
+
+namespace startop {
+
+template <typename Visitor>
+class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor {
+ public:
+  explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
+
+  bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override {
+    visitor_->VisitStartDocument();
+    return true;
+  }
+
+  bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override {
+    visitor_->VisitEndDocument();
+    return true;
+  }
+
+  bool VisitEnter(const tinyxml2::XMLElement& element,
+                  const tinyxml2::XMLAttribute* /*firstAttribute*/) override {
+    visitor_->VisitStartTag(
+        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
+            element.Name()));
+    return true;
+  }
+
+  bool VisitExit(const tinyxml2::XMLElement& /*element*/) override {
+    visitor_->VisitEndTag();
+    return true;
+  }
+
+ private:
+  Visitor* visitor_;
+};
+
+// Returns whether a layout resource represented by a TinyXML document is supported by the layout
+// compiler.
+bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr);
+
+}  // namespace startop
+
+#endif  // TINYXML_LAYOUT_PARSER_H_
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 36d0188..2820836 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -518,6 +518,7 @@
         private final Bundle mExtras;
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
+        private final CallIdentification mCallIdentification;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -699,6 +700,12 @@
         }
 
         /**
+         * The display name for the caller.
+         * <p>
+         * This is the name as reported by the {@link ConnectionService} associated with this call.
+         * The name reported by a {@link CallScreeningService} can be retrieved using
+         * {@link CallIdentification#getName()}.
+         *
          * @return The display name for the caller.
          */
         public String getCallerDisplayName() {
@@ -814,6 +821,23 @@
             return mCreationTimeMillis;
         }
 
+        /**
+         * Returns {@link CallIdentification} information provided by a
+         * {@link CallScreeningService} for this call.
+         * <p>
+         * {@link InCallService} implementations should display the {@link CallIdentification} for
+         * calls.  The name of the call screening service is provided in
+         * {@link CallIdentification#getCallScreeningAppName()} and should be used to attribute the
+         * call identification information.
+         *
+         * @return The {@link CallIdentification} if it was provided by a
+         * {@link CallScreeningService}, or {@code null} if no {@link CallScreeningService} has
+         * provided {@link CallIdentification} information for the call.
+         */
+        public @Nullable CallIdentification getCallIdentification() {
+            return mCallIdentification;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
@@ -834,7 +858,8 @@
                         Objects.equals(mStatusHints, d.mStatusHints) &&
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
-                        Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis);
+                        Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+                        Objects.equals(mCallIdentification, d.mCallIdentification);
             }
             return false;
         }
@@ -855,7 +880,8 @@
                             mStatusHints,
                             mExtras,
                             mIntentExtras,
-                            mCreationTimeMillis);
+                            mCreationTimeMillis,
+                            mCallIdentification);
         }
 
         /** {@hide} */
@@ -875,7 +901,8 @@
                 StatusHints statusHints,
                 Bundle extras,
                 Bundle intentExtras,
-                long creationTimeMillis) {
+                long creationTimeMillis,
+                CallIdentification callIdentification) {
             mTelecomCallId = telecomCallId;
             mHandle = handle;
             mHandlePresentation = handlePresentation;
@@ -892,6 +919,7 @@
             mExtras = extras;
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
+            mCallIdentification = callIdentification;
         }
 
         /** {@hide} */
@@ -912,7 +940,8 @@
                     parcelableCall.getStatusHints(),
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
-                    parcelableCall.getCreationTimeMillis());
+                    parcelableCall.getCreationTimeMillis(),
+                    parcelableCall.getCallIdentification());
         }
 
         @Override
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/telecomm/java/android/telecom/CallIdentification.aidl
similarity index 81%
copy from core/java/android/service/contentcapture/InteractionContext.aidl
copy to telecomm/java/android/telecom/CallIdentification.aidl
index 982e095..532535c 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/telecomm/java/android/telecom/CallIdentification.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
+/*
+ * Copyright 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.
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.service.contentcapture;
+package android.telecom;
 
-parcelable InteractionContext;
+/**
+ * {@hide}
+ */
+parcelable CallIdentification;
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
new file mode 100644
index 0000000..97af06c
--- /dev/null
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -0,0 +1,433 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Encapsulates information about an incoming or outgoing {@link Call} provided by a
+ * {@link CallScreeningService}.
+ * <p>
+ * Call identified information is consumed by the {@link InCallService dialer} app to provide the
+ * user with more information about a call.  This can include information such as the name of the
+ * caller, address, etc.  Call identification information is persisted to the
+ * {@link android.provider.CallLog}.
+ */
+public final class CallIdentification implements Parcelable {
+    /**
+     * Builder for {@link CallIdentification} instances.
+     * <p>
+     * A {@link CallScreeningService} uses this class to create new instances of
+     * {@link CallIdentification} for a screened call.
+     */
+    public static class Builder {
+        private String mName;
+        private String mDescription;
+        private String mDetails;
+        private Icon mPhoto;
+        private int mNuisanceConfidence = CallIdentification.CONFIDENCE_UNKNOWN;
+        private String mPackageName;
+        private String mAppName;
+
+        /**
+         * Default builder constructor.
+         */
+        public Builder() {
+            // Default constructor
+        }
+
+        /**
+         * Create instance of call identification with specified package/app name.
+         *
+         * @param callIdPackageName The package name.
+         * @param callIdAppName The app name.
+         * @hide
+         */
+        public Builder(String callIdPackageName, String callIdAppName) {
+            mPackageName = callIdPackageName;
+            mAppName = callIdAppName;
+        }
+
+        /**
+         * Sets the name associated with the {@link CallIdentification} being built.
+         * <p>
+         * Could be a business name, for example.
+         *
+         * @param name The name associated with the call, or {@code null} if none is provided.
+         * @return Builder instance.
+         */
+        public Builder setName(@Nullable String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Sets the description associated with the {@link CallIdentification} being built.
+         * <p>
+         * A description of the call as identified by a {@link CallScreeningService}.  The
+         * description is typically presented by Dialer apps after the
+         * {@link CallIdentification#getName() name} to provide a short piece of relevant
+         * information about the call.  This could include a location, address, or a message
+         * regarding the potential nature of the call (e.g. potential telemarketer).
+         *
+         * @param description The call description, or {@code null} if none is provided.
+         * @return Builder instance.
+         */
+        public Builder setDescription(@Nullable String description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets the details associated with the {@link CallIdentification} being built.
+         * <p>
+         * The details is typically presented by Dialer apps after the
+         * {@link CallIdentification#getName() name} and
+         * {@link CallIdentification#getDescription() description} to provide further clarifying
+         * information about the call. This could include, for example, the opening hours of a
+         * business, or a stats about the number of times a call has been reported as spam.
+         *
+         * @param details The call details, or {@code null} if none is provided.
+         * @return Builder instance.
+         */
+        public Builder setDetails(@Nullable String details) {
+            mDetails = details;
+            return this;
+        }
+
+        /**
+         * Sets the photo associated with the {@link CallIdentification} being built.
+         * <p>
+         * This could be, for example, a business logo, or a photo of the caller.
+         *
+         * @param photo The photo associated with the call, or {@code null} if none was provided.
+         * @return Builder instance.
+         */
+        public Builder setPhoto(@Nullable Icon photo) {
+            mPhoto = photo;
+            return this;
+        }
+
+        /**
+         * Sets the nuisance confidence with the {@link CallIdentification} being built.
+         * <p>
+         * This can be used to specify how confident the {@link CallScreeningService} is that a call
+         * is or is not a nuisance call.
+         *
+         * @param nuisanceConfidence The nuisance confidence.
+         * @return The builder.
+         */
+        public Builder setNuisanceConfidence(@NuisanceConfidence int nuisanceConfidence) {
+            mNuisanceConfidence = nuisanceConfidence;
+            return this;
+        }
+
+        /**
+         * Creates a new instance of {@link CallIdentification} based on the parameters set in this
+         * builder.
+         *
+         * @return {@link CallIdentification} instance.
+         */
+        public CallIdentification build() {
+            return new CallIdentification(mName, mDescription, mDetails, mPhoto,
+                    mNuisanceConfidence, mPackageName, mAppName);
+        }
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = { "CONFIDENCE_" },
+            value = {CONFIDENCE_NUISANCE, CONFIDENCE_LIKELY_NUISANCE, CONFIDENCE_UNKNOWN,
+                    CONFIDENCE_LIKELY_NOT_NUISANCE, CONFIDENCE_NOT_NUISANCE})
+    public @interface NuisanceConfidence {}
+
+    /**
+     * Call has been identified as a nuisance call.
+     * <p>
+     * Returned from {@link #getNuisanceConfidence()} to indicate that a
+     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
+     * nuisance call.
+     */
+    public static final int CONFIDENCE_NUISANCE = 2;
+
+    /**
+     * Call has been identified as a likely nuisance call.
+     * <p>
+     * Returned from {@link #getNuisanceConfidence()} to indicate that a
+     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
+     * nuisance call.
+     */
+    public static final int CONFIDENCE_LIKELY_NUISANCE = 1;
+
+    /**
+     * Call could not be classified as nuisance or non-nuisance.
+     * <p>
+     * Returned from {@link #getNuisanceConfidence()} to indicate that a
+     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
+     * nuisance call.
+     */
+    public static final int CONFIDENCE_UNKNOWN = 0;
+
+    /**
+     * Call has been identified as not likely to be a nuisance call.
+     * <p>
+     * Returned from {@link #getNuisanceConfidence()} to indicate that a
+     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
+     * nuisance call.
+     */
+    public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1;
+
+    /**
+     * Call has been identified as not a nuisance call.
+     * <p>
+     * Returned from {@link #getNuisanceConfidence()} to indicate that a
+     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
+     * nuisance call.
+     */
+    public static final int CONFIDENCE_NOT_NUISANCE = -2;
+
+    /**
+     * Default constructor for {@link CallIdentification}.
+     *
+     * @param name The name.
+     * @param description The description.
+     * @param details The details.
+     * @param photo The photo.
+     * @param nuisanceConfidence Confidence that this is a nuisance call.
+     * @hide
+     */
+    private CallIdentification(@Nullable String name, @Nullable String description,
+            @Nullable String details, @Nullable Icon photo,
+            @NuisanceConfidence int nuisanceConfidence) {
+        this(name, description, details, photo, nuisanceConfidence, null, null);
+    }
+
+    /**
+     * Default constructor for {@link CallIdentification}.
+     *
+     * @param name The name.
+     * @param description The description.
+     * @param details The details.
+     * @param photo The photo.
+     * @param nuisanceConfidence Confidence that this is a nuisance call.
+     * @param callScreeningPackageName Package name of the {@link CallScreeningService} which
+     *                                 provided the call identification.
+     * @param callScreeningAppName App name of the {@link CallScreeningService} which provided the
+     *                             call identification.
+     * @hide
+     */
+    private CallIdentification(@Nullable String name, @Nullable String description,
+            @Nullable String details, @Nullable Icon photo,
+            @NuisanceConfidence int nuisanceConfidence, @NonNull String callScreeningPackageName,
+            @NonNull String callScreeningAppName) {
+        mName = name;
+        mDescription = description;
+        mDetails = details;
+        mPhoto = photo;
+        mNuisanceConfidence = nuisanceConfidence;
+        mCallScreeningAppName = callScreeningPackageName;
+        mCallScreeningPackageName = callScreeningAppName;
+    }
+
+    private String mName;
+    private String mDescription;
+    private String mDetails;
+    private Icon mPhoto;
+    private int mNuisanceConfidence;
+    private String mCallScreeningPackageName;
+    private String mCallScreeningAppName;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        parcel.writeString(mName);
+        parcel.writeString(mDescription);
+        parcel.writeString(mDetails);
+        parcel.writeParcelable(mPhoto, 0);
+        parcel.writeInt(mNuisanceConfidence);
+        parcel.writeString(mCallScreeningPackageName);
+        parcel.writeString(mCallScreeningAppName);
+    }
+
+    /**
+     * Responsible for creating CallIdentification objects for deserialized Parcels.
+     */
+    public static final Parcelable.Creator<CallIdentification> CREATOR =
+            new Parcelable.Creator<CallIdentification> () {
+
+                @Override
+                public CallIdentification createFromParcel(Parcel source) {
+                    String name = source.readString();
+                    String description = source.readString();
+                    String details = source.readString();
+                    Icon photo = source.readParcelable(ClassLoader.getSystemClassLoader());
+                    int nuisanceConfidence = source.readInt();
+                    String callScreeningPackageName = source.readString();
+                    String callScreeningAppName = source.readString();
+                    return new CallIdentification(name, description, details, photo,
+                            nuisanceConfidence, callScreeningPackageName, callScreeningAppName);
+                }
+
+                @Override
+                public CallIdentification[] newArray(int size) {
+                    return new CallIdentification[size];
+                }
+            };
+
+    /**
+     * The name associated with the number.
+     * <p>
+     * The name of the call as identified by a {@link CallScreeningService}.  Could be a business
+     * name, for example.
+     *
+     * @return The name associated with the number, or {@code null} if none was provided.
+     */
+    public final @Nullable String getName() {
+        return mName;
+    }
+
+    /**
+     * Description of the call.
+     * <p>
+     * A description of the call as identified by a {@link CallScreeningService}.  The description
+     * is typically presented by Dialer apps after the {@link #getName() name} to provide a short
+     * piece of relevant information about the call.  This could include a location, address, or a
+     * message regarding the potential nature of the call (e.g. potential telemarketer).
+     *
+     * @return The call description, or {@code null} if none was provided.
+     */
+    public final @Nullable String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Details of the call.
+     * <p>
+     * Details of the call as identified by a {@link CallScreeningService}.  The details
+     * are typically presented by Dialer apps after the {@link #getName() name} and
+     * {@link #getDescription() description} to provide further clarifying information about the
+     * call. This could include, for example, the opening hours of a business, or stats about
+     * the number of times a call has been reported as spam.
+     *
+     * @return The call details, or {@code null} if none was provided.
+     */
+    public final @Nullable String getDetails() {
+        return mDetails;
+    }
+
+    /**
+     * Photo associated with the call.
+     * <p>
+     * A photo associated with the call as identified by a {@link CallScreeningService}.  This
+     * could be, for example, a business logo, or a photo of the caller.
+     *
+     * @return The photo associated with the call, or {@code null} if none was provided.
+     */
+    public final @Nullable Icon getPhoto() {
+        return mPhoto;
+    }
+
+    /**
+     * Indicates the likelihood that this call is a nuisance call.
+     * <p>
+     * How likely the call is a nuisance call, as identified by a {@link CallScreeningService}.
+     *
+     * @return The nuisance confidence.
+     */
+    public final @NuisanceConfidence
+    int getNuisanceConfidence() {
+        return mNuisanceConfidence;
+    }
+
+    /**
+     * The package name of the {@link CallScreeningService} which provided the
+     * {@link CallIdentification}.
+     * <p>
+     * A {@link CallScreeningService} may not set this property; it is set by the system.
+     * @return the package name
+     */
+    public final @NonNull String getCallScreeningPackageName() {
+        return mCallScreeningPackageName;
+    }
+
+    /**
+     * The {@link android.content.pm.PackageManager#getApplicationLabel(ApplicationInfo) name} of
+     * the {@link CallScreeningService} which provided the {@link CallIdentification}.
+     * <p>
+     * A {@link CallScreeningService} may not set this property; it is set by the system.
+     *
+     * @return The name of the app.
+     */
+    public final @NonNull String getCallScreeningAppName() {
+        return mCallScreeningAppName;
+    }
+
+    /**
+     * Set the package name of the {@link CallScreeningService} which provided this information.
+     *
+     * @param callScreeningPackageName The package name.
+     * @hide
+     */
+    public void setCallScreeningPackageName(@NonNull String callScreeningPackageName) {
+        mCallScreeningPackageName = callScreeningPackageName;
+    }
+
+    /**
+     * Set the app name of the {@link CallScreeningService} which provided this information.
+     *
+     * @param callScreeningAppName The app name.
+     * @hide
+     */
+    public void setCallScreeningAppName(@NonNull String callScreeningAppName) {
+        mCallScreeningAppName = callScreeningAppName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        CallIdentification that = (CallIdentification) o;
+        // Note: mPhoto purposely omit as no good comparison exists.
+        return mNuisanceConfidence == that.mNuisanceConfidence
+                && Objects.equals(mName, that.mName)
+                && Objects.equals(mDescription, that.mDescription)
+                && Objects.equals(mDetails, that.mDetails)
+                && Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName)
+                && Objects.equals(mCallScreeningPackageName, that.mCallScreeningPackageName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
+                mCallScreeningAppName, mCallScreeningPackageName);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 6628743..be96b3c 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.ComponentName;
@@ -46,6 +47,15 @@
  * </service>
  * }
  * </pre>
+ * <p>
+ * A CallScreeningService performs two functions:
+ * <ol>
+ *     <li>Call blocking/screening - the service can choose which calls will ring on the user's
+ *     device, and which will be silently sent to voicemail.</li>
+ *     <li>Call identification - the service can optionally provide {@link CallIdentification}
+ *     information about a {@link Call.Details call} which will be shown to the user in the
+ *     Dialer app.</li>
+ * </ol>
  */
 public abstract class CallScreeningService extends Service {
     /**
@@ -229,16 +239,23 @@
      *
      * @param callDetails Information about a new incoming call, see {@link Call.Details}.
      */
-    public abstract void onScreenCall(Call.Details callDetails);
+    public abstract void onScreenCall(@NonNull Call.Details callDetails);
 
     /**
      * Responds to the given call, either allowing it or disallowing it.
+     * <p>
+     * The {@link CallScreeningService} calls this method to inform the system whether the call
+     * should be silently blocked or not.
      *
      * @param callDetails The call to allow.
+     *                    <p>
+     *                    Must be the same {@link Call.Details call} which was provided to the
+     *                    {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}.
      * @param response The {@link CallScreeningService.CallResponse} which contains information
      * about how to respond to a call.
      */
-    public final void respondToCall(Call.Details callDetails, CallResponse response) {
+    public final void respondToCall(@NonNull Call.Details callDetails,
+            @NonNull CallResponse response) {
         try {
             if (response.getDisallowCall()) {
                 mCallScreeningAdapter.disallowCall(
@@ -253,4 +270,32 @@
         } catch (RemoteException e) {
         }
     }
+
+    /**
+     * Provide {@link CallIdentification} information about a {@link Call.Details call}.
+     * <p>
+     * The {@link CallScreeningService} calls this method to provide information it has identified
+     * about a {@link Call.Details call}.  This information will potentially be shown to the user
+     * in the {@link InCallService dialer} app.  It will be logged to the
+     * {@link android.provider.CallLog}.
+     * <p>
+     * A {@link CallScreeningService} should only call this method for calls for which it is able to
+     * provide some {@link CallIdentification} for.  {@link CallIdentification} instances with no
+     * fields set will be ignored by the system.
+     *
+     * @param callDetails The call to provide information for.
+     *                    <p>
+     *                    Must be the same {@link Call.Details call} which was provided to the
+     *                    {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}.
+     * @param identification An instance of {@link CallIdentification} with information about the
+     *                       {@link Call.Details call}.
+     */
+    public final void provideCallIdentification(@NonNull Call.Details callDetails,
+            @NonNull CallIdentification identification) {
+        try {
+            mCallScreeningAdapter.provideCallIdentification(callDetails.getTelecomCallId(),
+                    identification);
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java
index 1806aee..2680af7 100644
--- a/telecomm/java/android/telecom/DefaultDialerManager.java
+++ b/telecomm/java/android/telecom/DefaultDialerManager.java
@@ -74,7 +74,7 @@
         }
 
         // Only make the change if the new package belongs to a valid phone application
-        List<String> packageNames = getInstalledDialerApplications(context);
+        List<String> packageNames = getInstalledDialerApplications(context, user);
 
         if (packageNames.contains(packageName)) {
             // Update the secure setting.
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 3ad0f0c..911786e 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
@@ -62,6 +63,7 @@
     private final Bundle mIntentExtras;
     private final Bundle mExtras;
     private final long mCreationTimeMillis;
+    private final CallIdentification mCallIdentification;
 
     public ParcelableCall(
             String id,
@@ -89,7 +91,8 @@
             List<String> conferenceableCallIds,
             Bundle intentExtras,
             Bundle extras,
-            long creationTimeMillis) {
+            long creationTimeMillis,
+            CallIdentification callIdentification) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -116,6 +119,7 @@
         mIntentExtras = intentExtras;
         mExtras = extras;
         mCreationTimeMillis = creationTimeMillis;
+        mCallIdentification = callIdentification;
     }
 
     /** The unique ID of the call. */
@@ -133,7 +137,7 @@
      * Reason for disconnection, as described by {@link android.telecomm.DisconnectCause}. Valid
      * when call state is {@link CallState#DISCONNECTED}.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public DisconnectCause getDisconnectCause() {
         return mDisconnectCause;
     }
@@ -159,13 +163,13 @@
     }
 
     /** The time that the call switched to the active state. */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public long getConnectTimeMillis() {
         return mConnectTimeMillis;
     }
 
     /** The endpoint to which the call is connected. */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public Uri getHandle() {
         return mHandle;
     }
@@ -305,8 +309,17 @@
         return mCreationTimeMillis;
     }
 
+    /**
+     * Contains call identification information returned by a {@link CallScreeningService}.
+     * @return The {@link CallIdentification} for this call, or {@code null} if a
+     * {@link CallScreeningService} did not provide information.
+     */
+    public @Nullable CallIdentification getCallIdentification() {
+        return mCallIdentification;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final Parcelable.Creator<ParcelableCall> CREATOR =
             new Parcelable.Creator<ParcelableCall> () {
         @Override
@@ -342,6 +355,7 @@
             boolean isRttCallChanged = source.readByte() == 1;
             ParcelableRttCall rttCall = source.readParcelable(classLoader);
             long creationTimeMillis = source.readLong();
+            CallIdentification callIdentification = source.readParcelable(classLoader);
             return new ParcelableCall(
                     id,
                     state,
@@ -368,7 +382,8 @@
                     conferenceableCallIds,
                     intentExtras,
                     extras,
-                    creationTimeMillis);
+                    creationTimeMillis,
+                    callIdentification);
         }
 
         @Override
@@ -413,6 +428,7 @@
         destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0));
         destination.writeParcelable(mRttCall, 0);
         destination.writeLong(mCreationTimeMillis);
+        destination.writeParcelable(mCallIdentification, 0);
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index fb371c1..6c4b1af8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UnsupportedAppUsage;
+import android.app.role.RoleManagerCallback;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -31,6 +32,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -42,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Provides access to information about active calls and registration/call-management functionality.
@@ -1188,8 +1191,12 @@
      * Requires permission: {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
      *
      * @hide
+     * @deprecated Use
+     * {@link android.app.role.RoleManager#addRoleHolderAsUser(String, String, UserHandle, Executor,
+     * RoleManagerCallback)} instead.
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(allOf = {
             android.Manifest.permission.MODIFY_PHONE_STATE,
             android.Manifest.permission.WRITE_SECURE_SETTINGS})
@@ -1221,79 +1228,6 @@
     }
 
     /**
-     * Used to trigger display of the ChangeDefaultCallScreeningApp activity to prompt the user to
-     * change the call screening app.
-     *
-     * A {@link SecurityException} will be thrown if calling package name doesn't match the package
-     * of the passed {@link ComponentName}
-     *
-     * @param componentName to verify that the calling package name matches the package of the
-     * passed ComponentName.
-     */
-    public void requestChangeDefaultCallScreeningApp(@NonNull ComponentName componentName) {
-        try {
-            if (isServiceConnected()) {
-                getTelecomService().requestChangeDefaultCallScreeningApp(componentName, mContext
-                    .getOpPackageName());
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG,
-                "RemoteException calling ITelecomService#requestChangeDefaultCallScreeningApp.",
-                e);
-        }
-    }
-
-    /**
-     * Used to verify that the passed ComponentName is default call screening app.
-     *
-     * @param componentName to verify that the package of the passed ComponentName matched the default
-     * call screening packageName.
-     *
-     * @return {@code true} if the passed componentName matches the default call screening's, {@code
-     * false} if the passed componentName is null, or it doesn't match default call screening's.
-     */
-    public boolean isDefaultCallScreeningApp(ComponentName componentName) {
-        try {
-            if (isServiceConnected()) {
-                return getTelecomService().isDefaultCallScreeningApp(componentName);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG,
-                "RemoteException calling ITelecomService#isDefaultCallScreeningApp.",
-                e);
-        }
-        return false;
-    }
-
-    /**
-     * Used to set the default call screening package.
-     *
-     * Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} Requires
-     * permission: {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
-     *
-     * A {@link IllegalArgumentException} will be thrown if the specified package and component name
-     * of {@link ComponentName} does't exist, or the specified component of {@link ComponentName}
-     * does't have {@link android.Manifest.permission#BIND_SCREENING_SERVICE}.
-     *
-     * @param componentName to set the default call screening to.
-     * @hide
-     */
-    @RequiresPermission(anyOf = {
-        android.Manifest.permission.MODIFY_PHONE_STATE,
-        android.Manifest.permission.WRITE_SECURE_SETTINGS
-    })
-    public void setDefaultCallScreeningApp(ComponentName componentName) {
-        try {
-            if (isServiceConnected()) {
-                getTelecomService().setDefaultCallScreeningApp(componentName);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG,
-                "RemoteException calling ITelecomService#setDefaultCallScreeningApp.", e);
-        }
-    }
-
-    /**
      * Return whether a given phone number is the configured voicemail number for a
      * particular phone account.
      *
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index bbac8eb..7b23061 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -369,16 +369,13 @@
         }
 
         /**
-         * Create a call camera capabilities instance that optionally
-         * supports zoom.
+         * Create a call camera capabilities instance that optionally supports zoom.
          *
          * @param width The width of the camera video (in pixels).
          * @param height The height of the camera video (in pixels).
          * @param zoomSupported True when camera supports zoom.
          * @param maxZoom Maximum zoom supported by camera.
-         * @hide
          */
-        @UnsupportedAppUsage
         public CameraCapabilities(int width, int height, boolean zoomSupported, float maxZoom) {
             mWidth = width;
             mHeight = height;
@@ -455,16 +452,14 @@
         }
 
         /**
-         * Whether the camera supports zoom.
-         * @hide
+         * Returns {@code true} is zoom is supported, {@code false} otherwise.
          */
         public boolean isZoomSupported() {
             return mZoomSupported;
         }
 
         /**
-         * The maximum zoom supported by the camera.
-         * @hide
+         * Returns the maximum zoom supported by the camera.
          */
         public float getMaxZoom() {
             return mMaxZoom;
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index d255ed1..a86c830 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
+import android.telecom.CallIdentification;
 
 /**
  * Internal remote callback interface for call screening services.
@@ -34,4 +35,8 @@
             boolean shouldAddToCallLog,
             boolean shouldShowNotification,
             in ComponentName componentName);
+
+    void provideCallIdentification(
+            String callId,
+            in CallIdentification callIdentification);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index d64efea..50204e7 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -256,21 +256,6 @@
     boolean setDefaultDialer(in String packageName);
 
     /**
-     * @see TelecomServiceImpl#requestChangeDefaultCallScreeningApp
-     */
-    void requestChangeDefaultCallScreeningApp(in ComponentName componentNamem, String callingPackage);
-
-    /**
-     * @see TelecomServiceImpl#isDefaultCallScreeningApp
-     */
-    boolean isDefaultCallScreeningApp(in ComponentName componentName);
-
-    /**
-     * @see TelecomServiceImpl#setDefaultCallScreeningApp
-     */
-    void setDefaultCallScreeningApp(in ComponentName componentName);
-
-    /**
     * @see TelecomServiceImpl#createManageBlockedNumbersIntent
     **/
     Intent createManageBlockedNumbersIntent();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 388b5fb..fd14916 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2658,12 +2658,9 @@
                 //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
                 //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
                 });
-        sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE,
-                new String[] {
-                        String.valueOf(false) + ": 7",
-                        //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
-                        String.valueOf(true) + ": 8"
-                        //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
+        sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE, new String[] {
+                String.valueOf(false) + ": 7", //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+                String.valueOf(true) + ": 8"  //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
                 });
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
 
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 4354314..4bca404 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,7 +16,6 @@
 
 package android.telephony;
 
-import android.annotation.CallSuper;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
@@ -53,7 +52,6 @@
     private final String TAG = NetworkService.class.getSimpleName();
 
     public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
-    public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
 
     private static final int NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER                 = 1;
     private static final int NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER                 = 2;
@@ -81,7 +79,7 @@
      * must extend this class to support network connection. Note that each instance of network
      * service is associated with one physical SIM slot.
      */
-    public class NetworkServiceProvider {
+    public abstract class NetworkServiceProvider implements AutoCloseable {
         private final int mSlotId;
 
         private final List<INetworkServiceCallback>
@@ -137,12 +135,12 @@
         }
 
         /**
-         * Called when the instance of network service is destroyed (e.g. got unbind or binder died).
+         * Called when the instance of network service is destroyed (e.g. got unbind or binder died)
+         * or when the network service provider is removed. The extended class should implement this
+         * method to perform cleanup works.
          */
-        @CallSuper
-        protected void onDestroy() {
-            mNetworkRegistrationStateChangedCallbacks.clear();
-        }
+        @Override
+        public abstract void close();
     }
 
     private class NetworkServiceHandler extends Handler {
@@ -168,7 +166,7 @@
                 case NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER:
                     // If the service provider doesn't exist yet, we try to create it.
                     if (serviceProvider != null) {
-                        serviceProvider.onDestroy();
+                        serviceProvider.close();
                         mServiceMap.remove(slotId);
                     }
                     break;
@@ -176,7 +174,7 @@
                     for (int i = 0; i < mServiceMap.size(); i++) {
                         serviceProvider = mServiceMap.get(i);
                         if (serviceProvider != null) {
-                            serviceProvider.onDestroy();
+                            serviceProvider.close();
                         }
                     }
                     mServiceMap.clear();
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index c95837e..4dcb410 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -20,7 +20,6 @@
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
-import com.android.i18n.phonenumbers.ShortNumberInfo;
 
 import android.annotation.IntDef;
 import android.annotation.UnsupportedAppUsage;
@@ -2185,101 +2184,6 @@
     }
 
     /**
-     * Back-up old logics for {@link #isEmergencyNumberInternal} for legacy and deprecate purpose.
-     *
-     * @hide
-     */
-    public static boolean isEmergencyNumberInternal(String number, boolean useExactMatch,
-                                                    String defaultCountryIso) {
-        // If the number passed in is null, just return false:
-        if (number == null) return false;
-
-        // If the number passed in is a SIP address, return false, since the
-        // concept of "emergency numbers" is only meaningful for calls placed
-        // over the cell network.
-        // (Be sure to do this check *before* calling extractNetworkPortionAlt(),
-        // since the whole point of extractNetworkPortionAlt() is to filter out
-        // any non-dialable characters (which would turn 'abc911def@example.com'
-        // into '911', for example.))
-        if (PhoneNumberUtils.isUriNumber(number)) {
-            return false;
-        }
-
-        // Strip the separators from the number before comparing it
-        // to the list.
-        number = PhoneNumberUtils.extractNetworkPortionAlt(number);
-
-        String emergencyNumbers = "";
-        int slotId = SubscriptionManager.getSlotIndex(getDefaultVoiceSubId());
-
-        // retrieve the list of emergency numbers
-        // check read-write ecclist property first
-        String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
-
-        emergencyNumbers = SystemProperties.get(ecclist, "");
-
-        Rlog.d(LOG_TAG, "slotId:" + slotId + " country:"
-                + defaultCountryIso + " emergencyNumbers: " +  emergencyNumbers);
-
-        if (TextUtils.isEmpty(emergencyNumbers)) {
-            // then read-only ecclist property since old RIL only uses this
-            emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
-        }
-
-        if (!TextUtils.isEmpty(emergencyNumbers)) {
-            // searches through the comma-separated list for a match,
-            // return true if one is found.
-            for (String emergencyNum : emergencyNumbers.split(",")) {
-                // It is not possible to append additional digits to an emergency number to dial
-                // the number in Brazil - it won't connect.
-                if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) {
-                    if (number.equals(emergencyNum)) {
-                        return true;
-                    }
-                } else {
-                    if (number.startsWith(emergencyNum)) {
-                        return true;
-                    }
-                }
-            }
-            // no matches found against the list!
-            return false;
-        }
-
-        Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers."
-                + " Use embedded logic for determining ones.");
-
-        // If slot id is invalid, means that there is no sim card.
-        // According spec 3GPP TS22.101, the following numbers should be
-        // ECC numbers when SIM/USIM is not present.
-        emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
-
-        for (String emergencyNum : emergencyNumbers.split(",")) {
-            if (useExactMatch) {
-                if (number.equals(emergencyNum)) {
-                    return true;
-                }
-            } else {
-                if (number.startsWith(emergencyNum)) {
-                    return true;
-                }
-            }
-        }
-
-        // No ecclist system property, so use our own list.
-        if (defaultCountryIso != null) {
-            ShortNumberInfo info = ShortNumberInfo.getInstance();
-            if (useExactMatch) {
-                return info.isEmergencyNumber(number, defaultCountryIso);
-            } else {
-                return info.connectsToEmergencyNumber(number, defaultCountryIso);
-            }
-        }
-
-        return false;
-    }
-
-    /**
      * isVoiceMailNumber: checks a given number against the voicemail
      *   number provided by the RIL and SIM card. The caller must have
      *   the READ_PHONE_STATE credential.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index dacc5d8..a7e8e8a 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -80,6 +80,12 @@
     private CharSequence mCarrierName;
 
     /**
+     * The subscription carrier id.
+     * @see TelephonyManager#getSimCarrierId()
+     */
+    private int mCarrierId;
+
+    /**
      * The source of the name, NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE,
      * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT.
      */
@@ -171,7 +177,7 @@
             @Nullable UiccAccessRule[] accessRules, String cardId) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
-                false, null, true);
+                false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
     }
 
     /**
@@ -181,10 +187,10 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
-            @Nullable String groupUUID, boolean isMetered) {
+            @Nullable String groupUUID, boolean isMetered, int carrierId) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
-                isOpportunistic, groupUUID, isMetered, false);
+                isOpportunistic, groupUUID, isMetered, false, carrierId);
     }
     /**
      * @hide
@@ -193,7 +199,7 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
-            @Nullable String groupUUID, boolean isMetered, boolean isGroupDisabled) {
+            @Nullable String groupUUID, boolean isMetered, boolean isGroupDisabled, int carrierid) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -214,6 +220,7 @@
         this.mGroupUUID = groupUUID;
         this.mIsMetered = isMetered;
         this.mIsGroupDisabled = isGroupDisabled;
+        this.mCarrierId = carrierid;
     }
 
 
@@ -239,6 +246,14 @@
     }
 
     /**
+     * @return the carrier id of this Subscription carrier.
+     * @see TelephonyManager#getSimCarrierId()
+     */
+    public int getCarrierId() {
+        return this.mCarrierId;
+    }
+
+    /**
      * @return the name displayed to the user that identifies this subscription
      */
     public CharSequence getDisplayName() {
@@ -554,11 +569,12 @@
             String groupUUID = source.readString();
             boolean isMetered = source.readBoolean();
             boolean isGroupDisabled = source.readBoolean();
+            int carrierid = source.readInt();
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                     nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                     isEmbedded, accessRules, cardId, isOpportunistic, groupUUID, isMetered,
-                    isGroupDisabled);
+                    isGroupDisabled, carrierid);
         }
 
         @Override
@@ -589,6 +605,7 @@
         dest.writeString(mGroupUUID);
         dest.writeBoolean(mIsMetered);
         dest.writeBoolean(mIsGroupDisabled);
+        dest.writeInt(mCarrierId);
     }
 
     @Override
@@ -616,8 +633,9 @@
         String iccIdToPrint = givePrintableIccid(mIccId);
         String cardIdToPrint = givePrintableIccid(mCardId);
         return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
-                + " displayName=" + mDisplayName + " carrierName=" + mCarrierName
-                + " nameSource=" + mNameSource + " iconTint=" + mIconTint + " mNumber=" + mNumber
+                + " carrierId=" + mCarrierId + " displayName=" + mDisplayName
+                + " carrierName=" + mCarrierName + " nameSource=" + mNameSource
+                + " iconTint=" + mIconTint + " mNumber=" + mNumber
                 + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
                 + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
                 + " accessRules " + Arrays.toString(mAccessRules)
@@ -630,7 +648,8 @@
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
-                mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled);
+                mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled,
+                mCarrierId);
     }
 
     @Override
@@ -653,8 +672,9 @@
                 && mIsEmbedded == toCompare.mIsEmbedded
                 && mIsOpportunistic == toCompare.mIsOpportunistic
                 && mIsGroupDisabled == toCompare.mIsGroupDisabled
-                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+                && mCarrierId == toCompare.mCarrierId
                 && mIsMetered == toCompare.mIsMetered
+                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
                 && Objects.equals(mIccId, toCompare.mIccId)
                 && Objects.equals(mNumber, toCompare.mNumber)
                 && Objects.equals(mMcc, toCompare.mMcc)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 387453f..b61e99b 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -382,6 +382,14 @@
     public static final int SIM_PROVISIONED = 0;
 
     /**
+     * TelephonyProvider column name for subscription carrier id.
+     * @see TelephonyManager#getSimCarrierId()
+     * <p>Type: INTEGER (int) </p>
+     * @hide
+     */
+    public static final String CARRIER_ID = "carrier_id";
+
+    /**
      * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
      * <P>Type: TEXT (String)</P>
      * @hide
@@ -572,7 +580,6 @@
      * TelephonyProvider column name for whether a subscription is opportunistic, that is,
      * whether the network it connects to is limited in functionality or coverage.
      * For example, CBRS.
-     * IS_EMBEDDED should always be true.
      * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
      * @hide
      */
@@ -1191,7 +1198,8 @@
     }
 
     /**
-     * Request a refresh of the platform cache of profile information.
+     * Request a refresh of the platform cache of profile information for the eUICC which
+     * corresponds to the card ID returned by {@link TelephonyManager#getCardIdForDefaultEuicc()}.
      *
      * <p>Should be called by the EuiccService implementation whenever this information changes due
      * to an operation done outside the scope of a request initiated by the platform to the
@@ -1199,17 +1207,50 @@
      * were made through the EuiccService.
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+     *
      * @hide
      */
     @SystemApi
     public void requestEmbeddedSubscriptionInfoListRefresh() {
+        int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc();
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                iSub.requestEmbeddedSubscriptionInfoListRefresh();
+                iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
             }
         } catch (RemoteException ex) {
-            // ignore it
+            logd("requestEmbeddedSubscriptionInfoListFresh for card = " + cardId + " failed.");
+        }
+    }
+
+    /**
+     * Request a refresh of the platform cache of profile information for the eUICC with the given
+     * {@code cardId}.
+     *
+     * <p>Should be called by the EuiccService implementation whenever this information changes due
+     * to an operation done outside the scope of a request initiated by the platform to the
+     * EuiccService. There is no need to refresh for downloads, deletes, or other operations that
+     * were made through the EuiccService.
+     *
+     * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @param cardId the card ID of the eUICC.
+     *
+     * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
+            }
+        } catch (RemoteException ex) {
+            logd("requestEmbeddedSubscriptionInfoListFresh for card = " + cardId + " failed.");
         }
     }
 
@@ -2379,18 +2420,32 @@
     }
 
     /**
-     * Set opportunistic by simInfo index
+     * Set whether a subscription is opportunistic, that is, whether the network it connects
+     * to has limited coverage. For example, CBRS. Setting a subscription opportunistic has
+     * following impacts:
+     *  1) Even if it's active, it will be dormant most of the time. The modem will not try
+     *     to scan or camp until it knows an available network is nearby to save power.
+     *  2) Telephony relies on system app or carrier input to notify nearby available networks.
+     *     See {@link TelephonyManager#updateAvailableNetworks(List)} for more information.
+     *  3) In multi-SIM devices, when the network is nearby and camped, system may automatically
+     *     switch internet data between it and default data subscription, based on carrier
+     *     recommendation and its signal strength and metered-ness, etc.
+     *
+     *
+     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
+     * privilege permission of the subscription.
      *
      * @param opportunistic whether it’s opportunistic subscription.
      * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
-     * @hide
+     * @return {@code true} if the operation is succeed, {@code false} otherwise.
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public int setOpportunistic(boolean opportunistic, int subId) {
+    public boolean setOpportunistic(boolean opportunistic, int subId) {
         if (VDBG) logd("[setOpportunistic]+ opportunistic:" + opportunistic + " subId:" + subId);
         return setSubscriptionPropertyHelper(subId, "setOpportunistic",
-                (iSub)-> iSub.setOpportunistic(opportunistic, subId));
+                (iSub)-> iSub.setOpportunistic(
+                        opportunistic, subId, mContext.getOpPackageName())) == 1;
     }
 
     /**
@@ -2510,18 +2565,26 @@
     }
 
     /**
-     * Set metered by simInfo index
+     * Set if a subscription is metered or not. Similar to Wi-Fi, metered means
+     * user may be charged more if more data is used.
+     *
+     * By default all Cellular networks are considered metered. System or carrier privileged apps
+     * can set a subscription un-metered which will be considered when system switches data between
+     * primary subscription and opportunistic subscription.
+     *
+     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
+     * privilege permission of the subscription.
      *
      * @param isMetered whether it’s a metered subscription.
      * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
-     * @hide
+     * @return {@code true} if the operation is succeed, {@code false} otherwise.
      */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public int setMetered(boolean isMetered, int subId) {
+    public boolean setMetered(boolean isMetered, int subId) {
         if (VDBG) logd("[setIsMetered]+ isMetered:" + isMetered + " subId:" + subId);
         return setSubscriptionPropertyHelper(subId, "setIsMetered",
-                (iSub)-> iSub.setMetered(isMetered, subId));
+                (iSub)-> iSub.setMetered(isMetered, subId, mContext.getOpPackageName())) == 1;
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 348ab2a..e0632b1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5629,7 +5629,7 @@
         if (value == null) {
             value = "";
         }
-
+        value.replace(',', ' ');
         if (prop != null) {
             p = prop.split(",");
         }
@@ -5655,7 +5655,13 @@
             }
         }
 
-        if (propVal.length() > SystemProperties.PROP_VALUE_MAX) {
+        int propValLen = propVal.length();
+        try {
+            propValLen = propVal.getBytes("utf-8").length;
+        } catch (java.io.UnsupportedEncodingException e) {
+            Rlog.d(TAG, "setTelephonyProperty: utf-8 not supported");
+        }
+        if (propValLen > SystemProperties.PROP_VALUE_MAX) {
             Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
                     " property=" + property + " value: " + value + " propVal=" + propVal);
             return;
@@ -6665,8 +6671,8 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getCarrierPrivilegeStatus(subId) ==
-                    CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+                return telephony.getCarrierPrivilegeStatus(subId)
+                        == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex);
@@ -8950,6 +8956,23 @@
     }
 
     /**
+     * Action set from carrier signalling broadcast receivers to reset all carrier actions
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @hide
+     */
+    public void carrierActionResetAll(int subId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.carrierActionResetAll(subId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#carrierActionResetAll", e);
+        }
+    }
+
+    /**
      * Get aggregated video call data usage since boot.
      * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
      *
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 1db5850..74d1e83 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -16,7 +16,6 @@
 
 package android.telephony.data;
 
-import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,7 +59,6 @@
     private static final String TAG = DataService.class.getSimpleName();
 
     public static final String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService";
-    public static final String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID";
 
     /** {@hide} */
     @IntDef(prefix = "REQUEST_REASON_", value = {
@@ -116,7 +114,7 @@
      * must extend this class to support data connection. Note that each instance of data service
      * provider is associated with one physical SIM slot.
      */
-    public class DataServiceProvider {
+    public abstract class DataServiceProvider implements AutoCloseable {
 
         private final int mSlotId;
 
@@ -250,12 +248,12 @@
         }
 
         /**
-         * Called when the instance of data service is destroyed (e.g. got unbind or binder died).
+         * Called when the instance of data service is destroyed (e.g. got unbind or binder died)
+         * or when the data service provider is removed. The extended class should implement this
+         * method to perform cleanup works.
          */
-        @CallSuper
-        protected void onDestroy() {
-            mDataCallListChangedCallbacks.clear();
-        }
+        @Override
+        public abstract void close();
     }
 
     private static final class SetupDataCallRequest {
@@ -345,7 +343,7 @@
                     break;
                 case DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER:
                     if (serviceProvider != null) {
-                        serviceProvider.onDestroy();
+                        serviceProvider.close();
                         mServiceMap.remove(slotId);
                     }
                     break;
@@ -353,7 +351,7 @@
                     for (int i = 0; i < mServiceMap.size(); i++) {
                         serviceProvider = mServiceMap.get(i);
                         if (serviceProvider != null) {
-                            serviceProvider.onDestroy();
+                            serviceProvider.close();
                         }
                     }
                     mServiceMap.clear();
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 57d9cce..45b4849 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -151,7 +151,7 @@
 
         /**
          * Called when the qualified networks updater is removed. The extended class should
-         * implement this method to perform clean up works.
+         * implement this method to perform cleanup works.
          */
         @Override
         public abstract void close();
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index f124595..4d95e55 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -331,7 +331,80 @@
      */
     public static final int CODE_SIP_USER_MARKED_UNWANTED = 365;
 
-    /*
+    /**
+     * SIP Response : 405
+     * Method not allowed for the address in the Request URI
+     */
+    public static final int CODE_SIP_METHOD_NOT_ALLOWED = 366;
+
+    /**
+     * SIP Response : 407
+     * The request requires user authentication
+     */
+    public static final int CODE_SIP_PROXY_AUTHENTICATION_REQUIRED = 367;
+
+    /**
+     * SIP Response : 413
+     * Request body too large
+     */
+    public static final int CODE_SIP_REQUEST_ENTITY_TOO_LARGE = 368;
+
+    /**
+     * SIP Response : 414
+     * Request-URI too large
+     */
+    public static final int CODE_SIP_REQUEST_URI_TOO_LARGE = 369;
+
+    /**
+     * SIP Response : 421
+     * Specific extension is required, which is not present in the HEADER
+     */
+    public static final int CODE_SIP_EXTENSION_REQUIRED = 370;
+
+    /**
+     * SIP Response : 422
+     * The session expiration field too small
+     */
+    public static final int CODE_SIP_INTERVAL_TOO_BRIEF = 371;
+
+    /**
+     * SIP Response : 481
+     * Request received by the server does not match any dialog or transaction
+     */
+    public static final int CODE_SIP_CALL_OR_TRANS_DOES_NOT_EXIST = 372;
+
+    /**
+     * SIP Response : 482
+     * Server has detected a loop
+     */
+    public static final int CODE_SIP_LOOP_DETECTED = 373;
+
+    /**
+     * SIP Response : 483
+     * Max-Forwards value reached
+     */
+    public static final int CODE_SIP_TOO_MANY_HOPS = 374;
+
+    /**
+     * SIP Response : 485
+     * Request-URI is ambiguous
+     *
+     */
+    public static final int CODE_SIP_AMBIGUOUS = 376;
+
+    /**
+     * SIP Response : 491
+     * Server has pending request for same dialog
+     */
+    public static final int CODE_SIP_REQUEST_PENDING = 377;
+
+    /**
+     * SIP Response : 493
+     * The request cannot be decrypted by recipient
+     */
+    public static final int CODE_SIP_UNDECIPHERABLE = 378;
+
+    /**
      * MEDIA (IMS -> Telephony)
      */
     /**
@@ -384,6 +457,24 @@
      * The call has been terminated by the network or remote user.
      */
     public static final int CODE_USER_TERMINATED_BY_REMOTE = 510;
+    /**
+    * Upgrade Downgrade request rejected by
+    * Remote user if the request is MO initiated
+    * Local user if the request is MT initiated
+    */
+    public static final int CODE_USER_REJECTED_SESSION_MODIFICATION = 511;
+
+    /**
+    * Upgrade Downgrade request cacncelled by the user who initiated it
+    */
+    public static final int CODE_USER_CANCELLED_SESSION_MODIFICATION = 512;
+
+    /**
+     * UPGRADE DOWNGRADE operation failed
+     * This can happen due to failure from SIP/RTP/SDP generation or a Call end is
+     * triggered/received while Reinvite is in progress.
+     */
+    public static final int CODE_SESSION_MODIFICATION_FAILED = 1517;
 
     /*
      * UT
@@ -484,6 +575,16 @@
     public static final int CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 1100;
 
     /**
+     * For MultiEndPoint - Call was rejected elsewhere
+     */
+    public static final int CODE_REJECTED_ELSEWHERE = 1017;
+
+    /**
+     * Supplementary services (HOLD/RESUME) failure error codes.
+     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
+     */
+
+    /**
      * Supplementary Services (HOLD/RESUME) - the command failed.
      */
     public static final int CODE_SUPP_SVC_FAILED = 1201;
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index b0c875e..0fdca5d 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -79,10 +79,7 @@
     public static final int EVENT_PS_RESTRICT_DISABLED = BASE + 23;
     public static final int EVENT_CLEAN_UP_CONNECTION = BASE + 24;
     public static final int EVENT_RESTART_RADIO = BASE + 26;
-    public static final int EVENT_SET_INTERNAL_DATA_ENABLE = BASE + 27;
     public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 29;
-    public static final int CMD_SET_USER_DATA_ENABLE = BASE + 30;
-    public static final int CMD_SET_POLICY_DATA_ENABLE = BASE + 32;
     public static final int EVENT_ICC_CHANGED = BASE + 33;
     public static final int EVENT_DISCONNECT_DC_RETRYING = BASE + 34;
     public static final int EVENT_DATA_SETUP_COMPLETE_ERROR = BASE + 35;
@@ -93,14 +90,12 @@
     public static final int CMD_NET_STAT_POLL = BASE + 40;
     public static final int EVENT_DATA_RAT_CHANGED = BASE + 41;
     public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42;
-    public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 43;
     public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
     public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
-    public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46;
+    public static final int EVENT_DATA_ENABLED_CHANGED = BASE + 46;
     public static final int EVENT_DATA_RECONNECT = BASE + 47;
     public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48;
     public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
-    public static final int EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE = BASE + 50;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 65d1a920..d169b7d 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -104,7 +104,7 @@
     /**
      * @see android.telephony.SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh
      */
-    oneway void requestEmbeddedSubscriptionInfoListRefresh();
+    oneway void requestEmbeddedSubscriptionInfoListRefresh(int cardId);
 
     /**
      * Add a new SubscriptionInfo to subinfo database if needed
@@ -162,7 +162,7 @@
      * @param subId the unique SubscriptionInfo index in database
      * @return the number of records updated
      */
-    int setOpportunistic(boolean opportunistic, int subId);
+    int setOpportunistic(boolean opportunistic, int subId, String callingPackage);
 
     /**
      * Inform SubscriptionManager that subscriptions in the list are bundled
@@ -190,7 +190,7 @@
      * @param subId the unique SubscriptionInfo index in database
      * @return the number of records updated
      */
-    int setMetered(boolean isMetered, int subId);
+    int setMetered(boolean isMetered, int subId, String callingPackage);
 
     /**
      * Set which subscription is preferred for cellular data. It's
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 46366d6..399e255 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1408,6 +1408,14 @@
     void carrierActionReportDefaultNetworkStatus(int subId, boolean report);
 
     /**
+     * Action set from carrier signalling broadcast receivers to reset all carrier actions.
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * @param subId the subscription ID that this action applies to.
+     * @hide
+     */
+    void carrierActionResetAll(int subId);
+
+    /**
      * Get aggregated video call data usage since boot.
      * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
      *
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index c934317..2ebe870 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -421,6 +421,7 @@
     int RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA = 202;
     int RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA = 203;
     int RIL_REQUEST_SET_PREFERRED_DATA_MODEM = 204;
+    int RIL_REQUEST_EMERGENCY_DIAL = 205;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index eed8ae7..5ea8ff1 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -39,7 +39,6 @@
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -840,6 +839,7 @@
                 /* SAMPLE OUTPUT : Cold launch
                 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
                 Status: ok
+                LaunchState: COLD
                 Activity: com.google.android.calculator/com.android.calculator2.Calculator
                 TotalTime: 357
                 WaitTime: 377
@@ -848,6 +848,7 @@
                 Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
                 Warning: Activity not started, its current task has been brought to the front
                 Status: ok
+                LaunchState: HOT
                 Activity: com.google.android.calculator/com.android.calculator2.CalculatorGoogle
                 TotalTime: 60
                 WaitTime: 67
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 436dd85..151b559 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -1048,12 +1048,17 @@
             4,    // Protocol size: 4
             0, 2  // Opcode: reply (2)
     };
-    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+    private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
 
     private static final byte[] MOCK_IPV4_ADDR           = {10, 0, 0, 1};
     private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
     private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
     private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
+    private static final byte[] IPV4_SOURCE_ADDR         = {10, 0, 0, 3};
+    private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
+    private static final byte[] BUG_PROBE_SOURCE_ADDR1   = {0, 0, 1, 2};
+    private static final byte[] BUG_PROBE_SOURCE_ADDR2   = {3, 4, 0, 0};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
     // Helper to initialize a default apfFilter.
@@ -1399,10 +1404,16 @@
         assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
         assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
 
+        // Verify ARP reply packets from different source ip
+        assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
+        assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+        assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
+        assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
+
         // Verify unicast ARP reply packet is always accepted.
-        assertPass(program, arpReplyUnicast(MOCK_IPV4_ADDR));
-        assertPass(program, arpReplyUnicast(ANOTHER_IPV4_ADDR));
-        assertPass(program, arpReplyUnicast(IPV4_ANY_HOST_ADDR));
+        assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
+        assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
+        assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
 
         // Verify GARP reply packets are always filtered
         assertDrop(program, garpReply());
@@ -1431,19 +1442,20 @@
         apfFilter.shutdown();
     }
 
-    private static byte[] arpRequestBroadcast(byte[] tip) {
+    private static byte[] arpReply(byte[] sip, byte[] tip) {
         ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
         packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
-        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
         put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+        put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
         put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
         return packet.array();
     }
 
-    private static byte[] arpReplyUnicast(byte[] tip) {
+    private static byte[] arpRequestBroadcast(byte[] tip) {
         ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
         packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
-        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
         put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
         return packet.array();
     }
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 8081812..bca9be7 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -71,7 +71,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkRequest;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
@@ -130,10 +129,6 @@
     private static final String TEST_USB_IFNAME = "test_rndis0";
     private static final String TEST_WLAN_IFNAME = "test_wlan0";
 
-    // Actual contents of the request don't matter for this test. The lack of
-    // any specific TRANSPORT_* is sufficient to identify this request.
-    private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
-
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
     @Mock private INetworkManagementService mNMService;
@@ -257,11 +252,6 @@
             isTetheringSupportedCalls++;
             return true;
         }
-
-        @Override
-        public NetworkRequest getDefaultNetworkRequest() {
-            return mDefaultRequest;
-        }
     }
 
     private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
@@ -496,7 +486,7 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
-        verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
+        verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
         assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
 
@@ -730,7 +720,7 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
-        verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
+        verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // In tethering mode, in the default configuration, an explicit request
         // for a mobile network is also made.
         verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index a22cbd4..0afd607 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -24,6 +24,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -53,7 +54,6 @@
 import android.net.NetworkRequest;
 import android.net.NetworkState;
 import android.net.util.SharedLog;
-
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -65,7 +65,6 @@
 import org.junit.runner.RunWith;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -126,7 +125,7 @@
     }
 
     @Test
-    public void testDoesNothingBeforeStarted() {
+    public void testDoesNothingBeforeTrackDefaultAndStarted() throws Exception {
         assertTrue(mCM.hasNoCallbacks());
         assertFalse(mUNM.mobileNetworkRequested());
 
@@ -138,37 +137,40 @@
 
     @Test
     public void testDefaultNetworkIsTracked() throws Exception {
-        assertEquals(0, mCM.trackingDefault.size());
+        assertTrue(mCM.hasNoCallbacks());
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         assertEquals(1, mCM.trackingDefault.size());
 
         mUNM.stop();
-        assertTrue(mCM.hasNoCallbacks());
+        assertTrue(mCM.onlyHasDefaultCallbacks());
     }
 
     @Test
     public void testListensForAllNetworks() throws Exception {
         assertTrue(mCM.listening.isEmpty());
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         assertFalse(mCM.listening.isEmpty());
         assertTrue(mCM.isListeningForAll());
 
         mUNM.stop();
-        assertTrue(mCM.hasNoCallbacks());
+        assertTrue(mCM.onlyHasDefaultCallbacks());
     }
 
     @Test
     public void testCallbacksRegistered() {
-        mUNM.start(mDefaultRequest);
-        verify(mCM, times(1)).registerNetworkCallback(
-                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
         verify(mCM, times(1)).requestNetwork(
                 eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
+        mUNM.startObserveAllNetworks();
+        verify(mCM, times(1)).registerNetworkCallback(
+                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
 
         mUNM.stop();
-        verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mCM, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
     }
 
     @Test
@@ -176,7 +178,7 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -199,11 +201,9 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         verify(mCM, times(1)).registerNetworkCallback(
                 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
-        verify(mCM, times(1)).requestNetwork(
-                eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -227,7 +227,7 @@
         assertTrue(mCM.isDunRequested());
 
         mUNM.stop();
-        verify(mCM, times(3)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
 
         verifyNoMoreInteractions(mCM);
     }
@@ -237,7 +237,7 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -257,7 +257,7 @@
 
     @Test
     public void testUpdateMobileRequiresDun() throws Exception {
-        mUNM.start(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
 
         // Test going from no-DUN to DUN correctly re-registers callbacks.
         mUNM.updateMobileRequiresDun(false);
@@ -285,7 +285,8 @@
         final Collection<Integer> preferredTypes = new ArrayList<>();
         preferredTypes.add(TYPE_WIFI);
 
-        mUNM.start(mDefaultRequest);
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         // There are no networks, so there is nothing to select.
         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
 
@@ -350,7 +351,8 @@
 
     @Test
     public void testGetCurrentPreferredUpstream() throws Exception {
-        mUNM.start(mDefaultRequest);
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
         mUNM.updateMobileRequiresDun(false);
 
         // [0] Mobile connects, DUN not required -> mobile selected.
@@ -389,7 +391,8 @@
 
     @Test
     public void testLocalPrefixes() throws Exception {
-        mUNM.start(mDefaultRequest);
+        mUNM.startTrackDefaultNetwork(mDefaultRequest);
+        mUNM.startObserveAllNetworks();
 
         // [0] Test minimum set of local prefixes.
         Set<IpPrefix> local = mUNM.getLocalPrefixes();
@@ -521,11 +524,19 @@
         }
 
         boolean hasNoCallbacks() {
-            return allCallbacks.isEmpty() &&
-                   trackingDefault.isEmpty() &&
-                   listening.isEmpty() &&
-                   requested.isEmpty() &&
-                   legacyTypeMap.isEmpty();
+            return allCallbacks.isEmpty()
+                    && trackingDefault.isEmpty()
+                    && listening.isEmpty()
+                    && requested.isEmpty()
+                    && legacyTypeMap.isEmpty();
+        }
+
+        boolean onlyHasDefaultCallbacks() {
+            return (allCallbacks.size() == 1)
+                    && (trackingDefault.size() == 1)
+                    && listening.isEmpty()
+                    && requested.isEmpty()
+                    && legacyTypeMap.isEmpty();
         }
 
         boolean isListeningForAll() {
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 14312cf..369a002 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -20,6 +20,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.IWindowManager;
 import junit.framework.TestCase;
@@ -107,7 +108,7 @@
     public void testDISABLE_KEYGUARD() {
         Binder token = new Binder();
         try {
-            mWm.disableKeyguard(token, "foo");
+            mWm.disableKeyguard(token, "foo", UserHandle.myUserId());
             fail("IWindowManager.disableKeyguard did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
@@ -117,7 +118,7 @@
         }
 
         try {
-            mWm.reenableKeyguard(token);
+            mWm.reenableKeyguard(token, UserHandle.myUserId());
             fail("IWindowManager.reenableKeyguard did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9587704..c91134c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -78,7 +78,7 @@
 
 static uint32_t ParseFormatAttribute(const StringPiece& str) {
   uint32_t mask = 0;
-  for (StringPiece part : util::Tokenize(str, '|')) {
+  for (const StringPiece& part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
     uint32_t type = ParseFormatType(trimmed_part);
     if (type == 0) {
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index da22e88..c6f9152 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -362,7 +362,7 @@
     return util::make_unique<BinaryPrimitive>(flags);
   }
 
-  for (StringPiece part : util::Tokenize(str, '|')) {
+  for (const StringPiece& part : util::Tokenize(str, '|')) {
     StringPiece trimmed_part = util::TrimWhitespace(part);
 
     bool flag_set = false;
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index fc9514a..f63a074 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -433,7 +433,7 @@
     }
 
     Printer r_txt_printer(&fout_text);
-    for (const auto res : xmlres->file.exported_symbols) {
+    for (const auto& res : xmlres->file.exported_symbols) {
       r_txt_printer.Print("default int id ");
       r_txt_printer.Println(res.name.entry);
     }
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 8d91b00..a4610b2 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -113,7 +113,7 @@
 void AnnotationProcessor::Print(Printer* printer) const {
   if (has_comments_) {
     std::string result = comment_.str();
-    for (StringPiece line : util::Tokenize(result, '\n')) {
+    for (const StringPiece& line : util::Tokenize(result, '\n')) {
       printer->Println(line);
     }
     printer->Println(" */");
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 73105e16..5cfbbf2 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -182,7 +182,7 @@
 
 std::string PackageToPath(const StringPiece& package) {
   std::string out_path;
-  for (StringPiece part : util::Tokenize(package, '.')) {
+  for (const StringPiece& part : util::Tokenize(package, '.')) {
     AppendPath(&out_path, part);
   }
   return out_path;
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index cb8fef9..b5a990e 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -183,6 +183,11 @@
 
         self.name = self.fullname[self.fullname.rindex(".")+1:]
 
+    def merge_from(self, other):
+        self.ctors.extend(other.ctors)
+        self.fields.extend(other.fields)
+        self.methods.extend(other.methods)
+
     def __hash__(self):
         return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
 
@@ -204,9 +209,28 @@
         return self.raw
 
 
-def _parse_stream(f, clazz_cb=None):
-    line = 0
+def _parse_stream(f, clazz_cb=None, base_f=None):
     api = {}
+
+    if base_f:
+        base_classes = _parse_stream_to_generator(base_f)
+    else:
+        base_classes = []
+
+    for clazz in _parse_stream_to_generator(f):
+        base_class = _parse_to_matching_class(base_classes, clazz)
+        if base_class:
+            clazz.merge_from(base_class)
+
+        if clazz_cb:
+            clazz_cb(clazz)
+        else: # In callback mode, don't keep track of the full API
+            api[clazz.fullname] = clazz
+
+    return api
+
+def _parse_stream_to_generator(f):
+    line = 0
     pkg = None
     clazz = None
     blame = None
@@ -225,26 +249,41 @@
         if raw.startswith("package"):
             pkg = Package(line, raw, blame)
         elif raw.startswith("  ") and raw.endswith("{"):
-            # When provided with class callback, we treat as incremental
-            # parse and don't build up entire API
-            if clazz and clazz_cb:
-                clazz_cb(clazz)
             clazz = Class(pkg, line, raw, blame)
-            if not clazz_cb:
-                api[clazz.fullname] = clazz
         elif raw.startswith("    ctor"):
             clazz.ctors.append(Method(clazz, line, raw, blame))
         elif raw.startswith("    method"):
             clazz.methods.append(Method(clazz, line, raw, blame))
         elif raw.startswith("    field"):
             clazz.fields.append(Field(clazz, line, raw, blame))
+        elif raw.startswith("  }") and clazz:
+            while True:
+                retry = yield clazz
+                if not retry:
+                    break
+                # send() was called, asking us to redeliver clazz on next(). Still need to yield
+                # a dummy value to the send() first though.
+                if (yield "Returning clazz on next()"):
+                        raise TypeError("send() must be followed by next(), not send()")
 
-    # Handle last trailing class
-    if clazz and clazz_cb:
-        clazz_cb(clazz)
 
-    return api
+def _parse_to_matching_class(classes, needle):
+    """Takes a classes generator and parses it until it returns the class we're looking for
 
+    This relies on classes being sorted by package and class name."""
+
+    for clazz in classes:
+        if clazz.pkg.name < needle.pkg.name:
+            # We haven't reached the right package yet
+            continue
+        if clazz.name < needle.name:
+            # We haven't reached the right class yet
+            continue
+        if clazz.fullname == needle.fullname:
+            return clazz
+        # We ran past the right class. Send it back into the generator, then report failure.
+        classes.send(clazz)
+        return None
 
 class Failure():
     def __init__(self, sig, clazz, detail, error, rule, msg):
@@ -1504,12 +1543,12 @@
     verify_singleton(clazz)
 
 
-def examine_stream(stream):
+def examine_stream(stream, base_stream=None):
     """Find all style issues in the given API stream."""
     global failures, noticed
     failures = {}
     noticed = {}
-    _parse_stream(stream, examine_clazz)
+    _parse_stream(stream, examine_clazz, base_f=base_stream)
     return (failures, noticed)
 
 
@@ -1650,6 +1689,12 @@
     parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
     parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
             help="previous.txt")
+    parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
+            help="The base current.txt to use when examining system-current.txt or"
+                 " test-current.txt")
+    parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
+            help="The base previous.txt to use when examining system-previous.txt or"
+                 " test-previous.txt")
     parser.add_argument("--no-color", action='store_const', const=True,
             help="Disable terminal colors")
     parser.add_argument("--allow-google", action='store_const', const=True,
@@ -1669,7 +1714,9 @@
         ALLOW_GOOGLE = True
 
     current_file = args['current.txt']
+    base_current_file = args['base_current']
     previous_file = args['previous.txt']
+    base_previous_file = args['base_previous']
 
     if args['show_deprecations_at_birth']:
         with current_file as f:
@@ -1688,10 +1735,18 @@
         sys.exit()
 
     with current_file as f:
-        cur_fail, cur_noticed = examine_stream(f)
+        if base_current_file:
+            with base_current_file as base_f:
+                cur_fail, cur_noticed = examine_stream(f, base_f)
+        else:
+            cur_fail, cur_noticed = examine_stream(f)
     if not previous_file is None:
         with previous_file as f:
-            prev_fail, prev_noticed = examine_stream(f)
+            if base_previous_file:
+                with base_previous_file as base_f:
+                    prev_fail, prev_noticed = examine_stream(f, base_f)
+            else:
+                prev_fail, prev_noticed = examine_stream(f)
 
         # ignore errors from previous API level
         for p in prev_fail:
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 21d6b94..0362a1b 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,6 +66,8 @@
 
     List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
 
+    Map getMatchingPasspointConfigsForOsuProviders(in List<OsuProvider> osuProviders);
+
     int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
 
     boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
@@ -195,5 +197,7 @@
     int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
 
     String[] getFactoryMacAddresses();
+
+    void setDeviceMobilityState(int state);
 }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 57c97ea..a7c2ff0 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -57,8 +57,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -1230,6 +1233,30 @@
     }
 
     /**
+     * Returns the matching Passpoint R2 configurations for given OSU (Online Sign-Up) providers.
+     *
+     * Given a list of OSU providers, this only returns OSU providers that already have Passpoint R2
+     * configurations in the device.
+     * An empty map will be returned when there is no matching Passpoint R2 configuration for the
+     * given OsuProviders.
+     *
+     * @param osuProviders a set of {@link OsuProvider}
+     * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
+     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
+            @NonNull Set<OsuProvider> osuProviders) {
+        try {
+            return mService.getMatchingPasspointConfigsForOsuProviders(
+                    new ArrayList<>(osuProviders));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Add a new network description to the set of configured networks.
      * The {@code networkId} field of the supplied configuration object
      * is ignored.
@@ -4449,4 +4476,69 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"DEVICE_MOBILITY_STATE_"}, value = {
+            DEVICE_MOBILITY_STATE_UNKNOWN,
+            DEVICE_MOBILITY_STATE_HIGH_MVMT,
+            DEVICE_MOBILITY_STATE_LOW_MVMT,
+            DEVICE_MOBILITY_STATE_STATIONARY})
+    public @interface DeviceMobilityState {}
+
+    /**
+     * Unknown device mobility state
+     *
+     * @see #setDeviceMobilityState(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0;
+
+    /**
+     * High movement device mobility state
+     *
+     * @see #setDeviceMobilityState(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1;
+
+    /**
+     * Low movement device mobility state
+     *
+     * @see #setDeviceMobilityState(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2;
+
+    /**
+     * Stationary device mobility state
+     *
+     * @see #setDeviceMobilityState(int)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3;
+
+    /**
+     * Updates the device mobility state. Wifi uses this information to adjust the interval between
+     * Wifi scans in order to balance power consumption with scan accuracy.
+     * @param state the updated device mobility state
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE)
+    public void setDeviceMobilityState(@DeviceMobilityState int state) {
+        try {
+            mService.setDeviceMobilityState(state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 699f54c..a47e70b 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -34,11 +34,11 @@
  * {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
  * class provides functionality common to both publish and subscribe discovery sessions:
  * <ul>
- *     <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
- *     <li>Creating a network-specifier when requesting a Aware connection:
- *     {@link #createNetworkSpecifierOpen(PeerHandle)} or
- *     {@link #createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ *      <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
+ *      <li>Creating a network-specifier when requesting a Aware connection using
+ *      {@link WifiAwareManager.NetworkSpecifierBuilder}.
  * </ul>
+ * <p>
  * The {@link #close()} method must be called to destroy discovery sessions once they are
  * no longer needed.
  */
@@ -270,6 +270,7 @@
      * <p>
      * To set up an encrypted link use the
      * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API.
+     * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
      *
      * @param peerHandle The peer's handle obtained through
      * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
@@ -284,6 +285,7 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
+    @Deprecated
     public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) {
         if (mTerminated) {
             Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
@@ -318,6 +320,7 @@
      * <p>
      * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
      * and a Publisher is a RESPONDER.
+     * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
      *
      * @param peerHandle The peer's handle obtained through
      * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -336,6 +339,7 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
+    @Deprecated
     public NetworkSpecifier createNetworkSpecifierPassphrase(
             @NonNull PeerHandle peerHandle, @NonNull String passphrase) {
         if (!WifiAwareUtils.validatePassphrase(passphrase)) {
@@ -376,6 +380,7 @@
      * <p>
      * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
      * and a Publisher is a RESPONDER.
+     * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
      *
      * @param peerHandle The peer's handle obtained through
      * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -397,6 +402,7 @@
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle,
             @NonNull byte[] pmk) {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 8529a89..26a6c08 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -57,11 +58,7 @@
  * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
  * <li>Create a Aware network specifier to be used with
  * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
- * to set-up a Aware connection with a peer. Refer to
- * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)},
- * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)},
- * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, and
- * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
+ * to set-up a Aware connection with a peer. Refer to {@link NetworkSpecifierBuilder}.
  * </ul>
  * <p>
  *     Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
@@ -110,10 +107,7 @@
  *        <li>{@link NetworkRequest.Builder#addTransportType(int)} of
  *        {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
  *        <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
- *        {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])},
- *        {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)},
- *        {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, or
- *        {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ *        {@link NetworkSpecifierBuilder}.
  *    </ul>
  */
 @SystemService(Context.WIFI_AWARE_SERVICE)
@@ -145,8 +139,6 @@
      * Connection creation role is that of INITIATOR. Used to create a network specifier string
      * when requesting a Aware network.
      *
-     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
-     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
      * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
      * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
      */
@@ -156,8 +148,6 @@
      * Connection creation role is that of RESPONDER. Used to create a network specifier string
      * when requesting a Aware network.
      *
-     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
-     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
      * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
      * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
      */
@@ -415,6 +405,11 @@
                     + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
         }
 
+        if (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) {
+            throw new UnsupportedOperationException(
+                    "API not deprecated - use WifiAwareManager.NetworkSpecifierBuilder");
+        }
+
         if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                 && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
             throw new IllegalArgumentException(
@@ -813,4 +808,135 @@
             mOriginalCallback.onSessionTerminated();
         }
     }
+
+    /**
+     * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a
+     * peer.
+     * <p>
+     * Note that all Wi-Fi Aware connection specifier objects must call the
+     * {@link NetworkSpecifierBuilder#setDiscoverySession(DiscoverySession)} to specify the context
+     * within which the connection is created, and
+     * {@link NetworkSpecifierBuilder#setPeerHandle(PeerHandle)} to specify the peer to which the
+     * connection is created.
+     */
+    public static class NetworkSpecifierBuilder {
+        private DiscoverySession mDiscoverySession;
+        private PeerHandle mPeerHandle;
+        private String mPskPassphrase;
+        private byte[] mPmk;
+
+        /**
+         * Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession}
+         * discovery session in whose context the connection is created.
+         * <p>
+         * Note: this method must be called for any connection request!
+         *
+         * @param discoverySession A Wi-Fi Aware discovery session.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         */
+        public @NonNull NetworkSpecifierBuilder setDiscoverySession(
+                @NonNull DiscoverySession discoverySession) {
+            if (discoverySession == null) {
+                throw new IllegalArgumentException("Non-null discoverySession required");
+            }
+            mDiscoverySession = discoverySession;
+            return this;
+        }
+
+        /**
+         * Configure the {@link PeerHandle} of the peer to which the Wi-Fi Aware connection is
+         * requested. The peer is discovered through Wi-Fi Aware discovery,
+         * <p>
+         * Note: this method must be called for any connection request!
+         *
+         * @param peerHandle The peer's handle obtained through
+         * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+         *                   or
+         *                   {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         */
+        public @NonNull NetworkSpecifierBuilder setPeerHandle(@NonNull PeerHandle peerHandle) {
+            if (peerHandle == null) {
+                throw new IllegalArgumentException("Non-null peerHandle required");
+            }
+            mPeerHandle = peerHandle;
+            return this;
+        }
+
+        /**
+         * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method
+         * is optional - if not called, then an Open (unencrypted) connection will be created.
+         *
+         * @param pskPassphrase The (optional) passphrase to be used to encrypt the link.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         */
+        public @NonNull NetworkSpecifierBuilder setPskPassphrase(@NonNull String pskPassphrase) {
+            if (!WifiAwareUtils.validatePassphrase(pskPassphrase)) {
+                throw new IllegalArgumentException("Passphrase must meet length requirements");
+            }
+            mPskPassphrase = pskPassphrase;
+            return this;
+        }
+
+        /**
+         * Configure the PMK for the Wi-Fi Aware connection being requested. This method
+         * is optional - if not called, then an Open (unencrypted) connection will be created.
+         *
+         * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+         *            encrypting the data-path. Use the {@link #setPskPassphrase(String)} to
+         *            specify a Passphrase.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         * @hide
+         */
+        @SystemApi
+        public @NonNull NetworkSpecifierBuilder setPmk(@NonNull byte[] pmk) {
+            if (!WifiAwareUtils.validatePmk(pmk)) {
+                throw new IllegalArgumentException("PMK must 32 bytes");
+            }
+            mPmk = pmk;
+            return this;
+        }
+
+        /**
+         * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
+         * for a WiFi Aware connection (link) to the specified peer. The
+         * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+         * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+         * <p> The default builder constructor will initialize a NetworkSpecifier which requests an
+         * open (non-encrypted) link. To request an encrypted link use the
+         * {@link #setPskPassphrase(String)} builder method.
+         *
+         * @return A {@link NetworkSpecifier} to be used to construct
+         * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass
+         * to {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+         * android.net.ConnectivityManager.NetworkCallback)}
+         * [or other varieties of that API].
+         */
+        public @NonNull NetworkSpecifier build() {
+            if (mDiscoverySession == null) {
+                throw new IllegalStateException("Null discovery session!?");
+            }
+            if (mPskPassphrase != null & mPmk != null) {
+                throw new IllegalStateException(
+                        "Can only specify a Passphrase or a PMK - not both!");
+            }
+
+            int role = mDiscoverySession instanceof SubscribeDiscoverySession
+                    ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                    : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+            if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) {
+                throw new IllegalStateException("Null peerHandle!?");
+            }
+
+            return new WifiAwareNetworkSpecifier(
+                    WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
+                    mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
+                    null, mPmk, mPskPassphrase, Process.myUid());
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
new file mode 100644
index 0000000..0f29e08
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -0,0 +1,135 @@
+/*
+ * 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.net.wifi.aware;
+
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.Inet6Address;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * Wi-Fi Aware-specific network information. The information can be extracted from the
+ * {@link android.net.NetworkCapabilities} of the network using
+ * {@link NetworkCapabilities#getTransportInfo()}.
+ * The {@link NetworkCapabilities} is provided by the connectivity service to apps, e.g. received
+ * through the
+ * {@link android.net.ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network,
+ * android.net.NetworkCapabilities)} callback.
+ * <p>
+ * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address
+ * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a
+ * {@link java.net.Socket} connection to the peer.
+ */
+public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable {
+    private Inet6Address mIpv6Addr;
+
+    /** @hide */
+    public WifiAwareNetworkInfo(Inet6Address ipv6Addr) {
+        mIpv6Addr = ipv6Addr;
+    }
+
+    /**
+     * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!).
+     *
+     * @return An IPv6 address.
+     */
+    @Nullable
+    public Inet6Address getPeerIpv6Addr() {
+        return mIpv6Addr;
+    }
+
+    // parcelable methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mIpv6Addr.getAddress());
+        NetworkInterface ni = mIpv6Addr.getScopedInterface();
+        dest.writeString(ni == null ? null : ni.getName());
+    }
+
+    public static final Creator<WifiAwareNetworkInfo> CREATOR =
+            new Creator<WifiAwareNetworkInfo>() {
+                @Override
+                public WifiAwareNetworkInfo createFromParcel(Parcel in) {
+                    Inet6Address ipv6Addr;
+                    try {
+                        byte[] addr = in.createByteArray();
+                        String interfaceName = in.readString();
+                        NetworkInterface ni = null;
+                        if (interfaceName != null) {
+                            try {
+                                ni = NetworkInterface.getByName(interfaceName);
+                            } catch (SocketException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        ipv6Addr = Inet6Address.getByAddress(null, addr, ni);
+                    } catch (UnknownHostException e) {
+                        e.printStackTrace();
+                        return null;
+                    }
+
+                    return new WifiAwareNetworkInfo(ipv6Addr);
+                }
+
+                @Override
+                public WifiAwareNetworkInfo[] newArray(int size) {
+                    return new WifiAwareNetworkInfo[size];
+                }
+            };
+
+
+    // object methods
+
+    @Override
+    public String toString() {
+        return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString();
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof WifiAwareNetworkInfo)) {
+            return false;
+        }
+
+        WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj;
+        return Objects.equals(mIpv6Addr, lhs.mIpv6Addr);
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIpv6Addr);
+    }
+}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 3219653..5f8841c 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -213,7 +213,7 @@
      *     This API is targeted for applications which can obtain the peer MAC address using OOB
      *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
      *     when using Aware discovery use the alternative network specifier method -
-     *     {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}.
+     *     {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
      * <p>
      * To set up an encrypted link use the
      * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
@@ -254,7 +254,7 @@
      *     This API is targeted for applications which can obtain the peer MAC address using OOB
      *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
      *     when using Aware discovery use the alternative network specifier method -
-     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+     *     {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
      *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
@@ -300,7 +300,7 @@
      *     This API is targeted for applications which can obtain the peer MAC address using OOB
      *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
      *     when using Aware discovery use the alternative network specifier method -
-     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+     *     {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
      *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
index 893b19c..6d82ca1 100644
--- a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -19,13 +19,16 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.net.wifi.WifiSsid;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -52,9 +55,9 @@
     private WifiSsid mOsuSsid;
 
     /**
-     * Friendly name of the OSU provider.
+     * Map of friendly names expressed as different language for the OSU provider.
      */
-    private final String mFriendlyName;
+    private final Map<String, String> mFriendlyNames;
 
     /**
      * Description of the OSU provider.
@@ -81,10 +84,11 @@
      */
     private final Icon mIcon;
 
-    public OsuProvider(WifiSsid osuSsid, String friendlyName, String serviceDescription,
-            Uri serverUri, String nai, List<Integer> methodList, Icon icon) {
+    public OsuProvider(WifiSsid osuSsid, Map<String, String> friendlyNames,
+            String serviceDescription, Uri serverUri, String nai, List<Integer> methodList,
+            Icon icon) {
         mOsuSsid = osuSsid;
-        mFriendlyName = friendlyName;
+        mFriendlyNames = friendlyNames;
         mServiceDescription = serviceDescription;
         mServerUri = serverUri;
         mNetworkAccessIdentifier = nai;
@@ -104,7 +108,7 @@
     public OsuProvider(OsuProvider source) {
         if (source == null) {
             mOsuSsid = null;
-            mFriendlyName = null;
+            mFriendlyNames = null;
             mServiceDescription = null;
             mServerUri = null;
             mNetworkAccessIdentifier = null;
@@ -114,7 +118,7 @@
         }
 
         mOsuSsid = source.mOsuSsid;
-        mFriendlyName = source.mFriendlyName;
+        mFriendlyNames = source.mFriendlyNames;
         mServiceDescription = source.mServiceDescription;
         mServerUri = source.mServerUri;
         mNetworkAccessIdentifier = source.mNetworkAccessIdentifier;
@@ -134,8 +138,32 @@
         mOsuSsid = osuSsid;
     }
 
+    /**
+     * Return the friendly Name for current language from the list of friendly names of OSU
+     * provider.
+     *
+     * The string matching the default locale will be returned if it is found, otherwise the string
+     * in english or the first string in the list will be returned if english is not found.
+     * A null will be returned if the list is empty.
+     *
+     * @return String matching the default locale, null otherwise
+     */
     public String getFriendlyName() {
-        return mFriendlyName;
+        if (mFriendlyNames == null || mFriendlyNames.isEmpty()) return null;
+        String lang = Locale.getDefault().getLanguage();
+        String friendlyName = mFriendlyNames.get(lang);
+        if (friendlyName != null) {
+            return friendlyName;
+        }
+        friendlyName = mFriendlyNames.get("en");
+        if (friendlyName != null) {
+            return friendlyName;
+        }
+        return mFriendlyNames.get(mFriendlyNames.keySet().stream().findFirst().get());
+    }
+
+    public Map<String, String> getFriendlyNameList() {
+        return mFriendlyNames;
     }
 
     public String getServiceDescription() {
@@ -151,7 +179,7 @@
     }
 
     public List<Integer> getMethodList() {
-        return Collections.unmodifiableList(mMethodList);
+        return mMethodList;
     }
 
     public Icon getIcon() {
@@ -166,12 +194,14 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(mOsuSsid, flags);
-        dest.writeString(mFriendlyName);
         dest.writeString(mServiceDescription);
         dest.writeParcelable(mServerUri, flags);
         dest.writeString(mNetworkAccessIdentifier);
         dest.writeList(mMethodList);
         dest.writeParcelable(mIcon, flags);
+        Bundle bundle = new Bundle();
+        bundle.putSerializable("friendlyNameMap", (HashMap<String, String>) mFriendlyNames);
+        dest.writeBundle(bundle);
     }
 
     @Override
@@ -184,7 +214,8 @@
         }
         OsuProvider that = (OsuProvider) thatObject;
         return (mOsuSsid == null ? that.mOsuSsid == null : mOsuSsid.equals(that.mOsuSsid))
-                && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+                && (mFriendlyNames == null) ? that.mFriendlyNames == null
+                            : mFriendlyNames.equals(that.mFriendlyNames)
                 && TextUtils.equals(mServiceDescription, that.mServiceDescription)
                 && (mServerUri == null ? that.mServerUri == null
                             : mServerUri.equals(that.mServerUri))
@@ -196,14 +227,15 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mOsuSsid, mFriendlyName, mServiceDescription, mServerUri,
-                mNetworkAccessIdentifier, mMethodList, mIcon);
+        // mIcon is not hashable, skip the variable.
+        return Objects.hash(mOsuSsid, mServiceDescription, mFriendlyNames,
+                mServerUri, mNetworkAccessIdentifier, mMethodList);
     }
 
     @Override
     public String toString() {
         return "OsuProvider{mOsuSsid=" + mOsuSsid
-                + " mFriendlyName=" + mFriendlyName
+                + " mFriendlyNames=" + mFriendlyNames
                 + " mServiceDescription=" + mServiceDescription
                 + " mServerUri=" + mServerUri
                 + " mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
@@ -212,20 +244,22 @@
     }
 
     public static final Creator<OsuProvider> CREATOR =
-        new Creator<OsuProvider>() {
-            @Override
-            public OsuProvider createFromParcel(Parcel in) {
-                WifiSsid osuSsid = (WifiSsid) in.readParcelable(null);
-                String friendlyName = in.readString();
-                String serviceDescription = in.readString();
-                Uri serverUri = (Uri) in.readParcelable(null);
-                String nai = in.readString();
-                List<Integer> methodList = new ArrayList<>();
-                in.readList(methodList, null);
-                Icon icon = (Icon) in.readParcelable(null);
-                return new OsuProvider(osuSsid, friendlyName, serviceDescription, serverUri,
-                        nai, methodList, icon);
-            }
+            new Creator<OsuProvider>() {
+                @Override
+                public OsuProvider createFromParcel(Parcel in) {
+                    WifiSsid osuSsid = in.readParcelable(null);
+                    String serviceDescription = in.readString();
+                    Uri serverUri = in.readParcelable(null);
+                    String nai = in.readString();
+                    List<Integer> methodList = new ArrayList<>();
+                    in.readList(methodList, null);
+                    Icon icon = in.readParcelable(null);
+                    Bundle bundle = in.readBundle();
+                    Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+                            "friendlyNameMap");
+                    return new OsuProvider(osuSsid, friendlyNamesMap, serviceDescription,
+                            serverUri, nai, methodList, icon);
+                }
 
             @Override
             public OsuProvider[] newArray(int size) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 26bdb18..f09d864 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -20,6 +20,7 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.hotspot2.pps.Policy;
 import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -30,6 +31,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
@@ -324,6 +326,50 @@
     }
 
     /**
+     * The map of OSU service provider names whose each element is presented in different
+     * languages for the service provider, which is used for finding a matching
+     * PasspointConfiguration with a given service provider name.
+     */
+    private Map<String, String> mServiceFriendlyNames = null;
+
+    /**
+     * @hide
+     */
+    public void setServiceFriendlyNames(Map<String, String> serviceFriendlyNames) {
+        mServiceFriendlyNames = serviceFriendlyNames;
+    }
+
+    /**
+     * @hide
+     */
+    public Map<String, String> getServiceFriendlyNames() {
+        return mServiceFriendlyNames;
+    }
+
+    /**
+     * Return the friendly Name for current language from the list of friendly names of OSU
+     * provider.
+     * The string matching the default locale will be returned if it is found, otherwise the
+     * first string in the list will be returned.  A null will be returned if the list is empty.
+     *
+     * @return String matching the default locale, null otherwise
+     * @hide
+     */
+    public String getServiceFriendlyName() {
+        if (mServiceFriendlyNames == null || mServiceFriendlyNames.isEmpty()) return null;
+        String lang = Locale.getDefault().getLanguage();
+        String friendlyName = mServiceFriendlyNames.get(lang);
+        if (friendlyName != null) {
+            return friendlyName;
+        }
+        friendlyName = mServiceFriendlyNames.get("en");
+        if (friendlyName != null) {
+            return friendlyName;
+        }
+        return mServiceFriendlyNames.get(mServiceFriendlyNames.keySet().stream().findFirst().get());
+    }
+
+    /**
      * Constructor for creating PasspointConfiguration with default values.
      */
     public PasspointConfiguration() {}
@@ -362,6 +408,7 @@
         mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis;
         mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
         mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
+        mServiceFriendlyNames = source.mServiceFriendlyNames;
     }
 
     @Override
@@ -385,6 +432,10 @@
         dest.writeLong(mUsageLimitStartTimeInMillis);
         dest.writeLong(mUsageLimitDataLimit);
         dest.writeLong(mUsageLimitTimeLimitInMinutes);
+        Bundle bundle = new Bundle();
+        bundle.putSerializable("serviceFriendlyNames",
+                (HashMap<String, String>) mServiceFriendlyNames);
+        dest.writeBundle(bundle);
     }
 
     @Override
@@ -398,10 +449,10 @@
         PasspointConfiguration that = (PasspointConfiguration) thatObject;
         return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
                 && (mCredential == null ? that.mCredential == null
-                        : mCredential.equals(that.mCredential))
+                : mCredential.equals(that.mCredential))
                 && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
                 && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
-                        : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
+                : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
                 && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
                 && mUpdateIdentifier == that.mUpdateIdentifier
                 && mCredentialPriority == that.mCredentialPriority
@@ -411,7 +462,9 @@
                 && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
                 && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
-                && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes;
+                && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
+                && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
+                : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
     }
 
     @Override
@@ -419,7 +472,8 @@
         return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
                 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
                 mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
-                mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
+                mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
+                mServiceFriendlyNames);
     }
 
     @Override
@@ -463,6 +517,9 @@
             builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
                     .append("\n");
         }
+        if (mServiceFriendlyNames != null) {
+            builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
+        }
         return builder.toString();
     }
 
@@ -562,6 +619,10 @@
                 config.setUsageLimitStartTimeInMillis(in.readLong());
                 config.setUsageLimitDataLimit(in.readLong());
                 config.setUsageLimitTimeLimitInMinutes(in.readLong());
+                Bundle bundle = in.readBundle();
+                Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+                        "serviceFriendlyNames");
+                config.setServiceFriendlyNames(friendlyNamesMap);
                 return config;
             }
 
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index 36f66aa..e94b9e6 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -39,6 +39,7 @@
 import android.os.WorkSource;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Abstract class implementing IWifiManager with stub methods throwing runtime exceptions.
@@ -127,6 +128,12 @@
     }
 
     @Override
+    public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
+            List<OsuProvider> osuProviders) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
         throw new UnsupportedOperationException();
     }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 272f727..45e1720 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.MacAddress;
 import android.net.wifi.RttManager;
 import android.os.Build;
 import android.os.Handler;
@@ -50,6 +51,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
 import java.util.List;
 
 /**
@@ -105,7 +108,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+        mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
         when(mockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
                 mockApplicationInfo);
         when(mockContext.getOpPackageName()).thenReturn("XXX");
@@ -915,6 +918,8 @@
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
 
+        mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
         ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -948,6 +953,9 @@
         WifiAwareNetworkSpecifier ns =
                 (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierOpen(
                         peerHandle);
+        WifiAwareNetworkSpecifier nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager
+                .NetworkSpecifierBuilder().setDiscoverySession(publishSession.getValue())
+                .setPeerHandle(peerHandle).build();
 
         // validate format
         collector.checkThat("role", role, equalTo(ns.role));
@@ -955,9 +963,18 @@
         collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
 
+        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+
         // (4) request an encrypted (PMK) network specifier from the session
         ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
                 peerHandle, pmk);
+        nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(
+                        publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
 
         // validate format
         collector.checkThat("role", role, equalTo(ns.role));
@@ -966,9 +983,18 @@
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
         collector.checkThat("pmk", pmk , equalTo(ns.pmk));
 
+        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+        collector.checkThat("pmk", pmk , equalTo(nsb.pmk));
+
         // (5) request an encrypted (Passphrase) network specifier from the session
         ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
                 peerHandle, passphrase);
+        nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
+                .setPskPassphrase(passphrase).build();
 
         // validate format
         collector.checkThat("role", role, equalTo(ns.role));
@@ -977,6 +1003,12 @@
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
         collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
 
+        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+        collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase));
+
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
     }
@@ -1048,7 +1080,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientNullPmk() throws Exception {
-        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null);
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, false);
     }
 
     /**
@@ -1056,7 +1088,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception {
-        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null);
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, false);
     }
 
     /**
@@ -1064,7 +1096,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientNullPassphrase() throws Exception {
-        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null);
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, false);
     }
 
     /**
@@ -1073,7 +1105,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientTooShortPassphrase() throws Exception {
         executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null,
-                PASSPHRASE_TOO_SHORT);
+                PASSPHRASE_TOO_SHORT, false);
     }
 
     /**
@@ -1081,7 +1113,8 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientTooLongPassphrase() throws Exception {
-        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG);
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG,
+                false);
     }
 
     /**
@@ -1089,7 +1122,8 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testNetworkSpecifierWithClientNullPeer() throws Exception {
-        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID);
+        mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
     }
 
     /**
@@ -1098,11 +1132,75 @@
     @Test
     public void testNetworkSpecifierWithClientNullPeerLegacyApi() throws Exception {
         mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
-        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID);
+        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
+    }
+
+    /**
+     * Validate that a null PMK triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientNullPmkBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, true);
+    }
+
+    /**
+     * Validate that a non-32-bytes PMK triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientIncorrectLengthPmkBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, true);
+    }
+
+    /**
+     * Validate that a null Passphrase triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientNullPassphraseBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, true);
+    }
+
+    /**
+     * Validate that a too short Passphrase triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientTooShortPassphraseBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null,
+                PASSPHRASE_TOO_SHORT, true);
+    }
+
+    /**
+     * Validate that a too long Passphrase triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientTooLongPassphraseBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG,
+                true);
+    }
+
+    /**
+     * Validate that a null PeerHandle triggers an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierWithClientNullPeerBuilder() throws Exception {
+        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, true);
+    }
+
+    /**
+     * Validate that a null PeerHandle does not trigger an exception for legacy API.
+     */
+    @Test
+    public void testNetworkSpecifierWithClientNullPeerLegacyApiBuilder() throws Exception {
+        mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testNetworkSpecifierDeprecatedOnNewApi() throws Exception {
+        executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
     }
 
     private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk,
-            String passphrase) throws Exception {
+            String passphrase, boolean useBuilder) throws Exception {
         final int clientId = 4565;
         final int sessionId = 123;
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -1139,9 +1237,20 @@
 
         // (3) create network specifier
         if (doPmk) {
-            publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+            if (useBuilder) {
+                new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession(
+                        publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
+            } else {
+                publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+            }
         } else {
-            publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase);
+            if (useBuilder) {
+                new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession(
+                        publishSession.getValue()).setPeerHandle(peerHandle).setPskPassphrase(
+                        passphrase).build();
+            } else {
+                publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase);
+            }
         }
     }
 
@@ -1245,4 +1354,31 @@
             sessionCaptor.getValue().createNetworkSpecifierPassphrase(role, someMac, passphrase);
         }
     }
+
+    // WifiAwareNetworkInfo tests
+
+    @Test
+    public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException {
+        final Inet6Address inet6 = MacAddress.fromString(
+                "11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac();
+        // note: dummy scope = 5
+        final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5);
+
+        assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5");
+        WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped);
+
+        Parcel parcelW = Parcel.obtain();
+        cap.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        WifiAwareNetworkInfo rereadCap =
+                WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5");
+        assertEquals(cap.hashCode(), rereadCap.hashCode());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
index d3f91f0..89ecd0f 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
@@ -28,9 +28,10 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.OsuProvider}.
@@ -40,6 +41,15 @@
     private static final WifiSsid TEST_SSID =
             WifiSsid.createFromByteArray("TEST SSID".getBytes(StandardCharsets.UTF_8));
     private static final String TEST_FRIENDLY_NAME = "Friendly Name";
+    private static final Map<String, String> TEST_FRIENDLY_NAMES =
+            new HashMap<String, String>() {
+                {
+                    put("en", TEST_FRIENDLY_NAME);
+                    put("kr", TEST_FRIENDLY_NAME + 2);
+                    put("jp", TEST_FRIENDLY_NAME + 3);
+                }
+            };
+
     private static final String TEST_SERVICE_DESCRIPTION = "Dummy Service";
     private static final Uri TEST_SERVER_URI = Uri.parse("https://test.com");
     private static final String TEST_NAI = "test.access.com";
@@ -59,7 +69,9 @@
 
         parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
         OsuProvider readInfo = OsuProvider.CREATOR.createFromParcel(parcel);
+
         assertEquals(writeInfo, readInfo);
+        assertEquals(writeInfo.hashCode(), readInfo.hashCode());
     }
 
     /**
@@ -79,8 +91,8 @@
      */
     @Test
     public void verifyParcelWithFullProviderInfo() throws Exception {
-        verifyParcel(new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
-                TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON));
+        verifyParcel(new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
+                TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON));
     }
 
     /**
@@ -100,8 +112,8 @@
      */
     @Test
     public void verifyCopyConstructorWithValidSource() throws Exception {
-        OsuProvider source = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
-                TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+        OsuProvider source = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
+                TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
         assertEquals(source, new OsuProvider(source));
     }
 
@@ -112,10 +124,12 @@
      */
     @Test
     public void verifyGetters() throws Exception {
-        OsuProvider provider = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME,
+        OsuProvider provider = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
                 TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+
         assertTrue(TEST_SSID.equals(provider.getOsuSsid()));
         assertTrue(TEST_FRIENDLY_NAME.equals(provider.getFriendlyName()));
+        assertTrue(TEST_FRIENDLY_NAMES.equals(provider.getFriendlyNameList()));
         assertTrue(TEST_SERVICE_DESCRIPTION.equals(provider.getServiceDescription()));
         assertTrue(TEST_SERVER_URI.equals(provider.getServerUri()));
         assertTrue(TEST_NAI.equals(provider.getNetworkAccessIdentifier()));
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 775ce21..ee5a75e 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -166,6 +166,10 @@
         config.setUsageLimitStartTimeInMillis(124214213);
         config.setUsageLimitDataLimit(14121);
         config.setUsageLimitTimeLimitInMinutes(78912);
+        Map<String, String> friendlyNames = new HashMap<>();
+        friendlyNames.put("en", "ServiceName1");
+        friendlyNames.put("kr", "ServiceName2");
+        config.setServiceFriendlyNames(friendlyNames);
         return config;
     }
 
@@ -206,6 +210,18 @@
     }
 
     /**
+     * Verify parcel read/write for a configuration that doesn't contain a list of service names.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutServiceNames() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setServiceFriendlyNames(null);
+        verifyParcel(config);
+    }
+
+    /**
      * Verify parcel read/write for a configuration that doesn't contain HomeSP.
      *
      * @throws Exception